个人最怕字符串和DP,我从来没想过这题拿并查集写。
一开始我是填格子的思路,往每一个位置填0,1,最后不能确定的位置的个数进行2的幂次就是答案,之后发现没法填,就输出0。
正解是并查集,思想也是逐位分析,借助并查集这个工具。最巧妙的也是解题关键的地方就是把每个代表多位的密码拆成一位一位的,这样一来每个字母只代表一个位置。便可以逐位分析了。
先考虑无解的情况:
1:两段密文长度不一样。
2:某个位置上出现0对1或1对0。
之后我们考虑逐位分析:
在有解的情况下,对应位置所代表的数字一定是相同的,不同位置用同样字母表示那这两个位置也是相同的,这样我们便建立了不同位置相同字母和不同字母相同位置这两对关系,借助这两对关系我们可以得到字母间的等价关系。
利用并查集把相同位置的字母并到一个集合中,表示这两个字母代表相同的数字,最后我们会得到代表0的字母集合,代表1的字母集合,和n个即可以代表0又可以代表1的集合,这时候答案就是2n。
需要注意的一点是在最后统计集合时,只考虑在密文中出现过的密码,就像样例中b这个密码并没有出现,但是如果统计上了便会多出来50个不确定集合,也就无法得出正确解了。
// q.c // 居然又把并查集写挂了~ #include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<vector> using namespace std; const int N=27,K=107,M=10000+7; int n,cnt=1,ans,len[N],f[M],w[N][K]; char c,s1[M],s2[M]; vector<int> text1,text2; bool vis[M]; void reset() { for(int i=0;i<=cnt;i++) f[i]=i; } int find(int x) { return f[x]==x?x:f[x]=find(f[x]); } void solve(char *s,int &leng,vector<int> &a) { for(int i=0;i<leng;i++) { if(s[i]==‘0‘||s[i]==‘1‘) a.push_back(s[i]-‘0‘); else for(int j=1;j<=len[s[i]-‘a‘];j++) { vis[w[s[i]-‘a‘][j]]=true; a.push_back(w[s[i]-‘a‘][j]); } } leng=a.size(); } int main() { freopen("encrypt.in","r",stdin); freopen("encrypt.out","w",stdout); cin>>s1>>s2>>n; int len1=strlen(s1); int len2=strlen(s2); for(int i=1;i<=n;i++) { cin>>c; cin>>len[c-‘a‘]; for(int j=1;j<=len[c-‘a‘];j++) w[c-‘a‘][j]=++cnt; } solve(s1,len1,text1); solve(s2,len2,text2); if(len1!=len2) { cout<<0<<endl; return 0; } else { int x,y,fx,fy; reset(); for(int i=0;i<len1;i++) { x=text1[i],y=text2[i],fx=find(x),fy=find(y); if(fx!=fy) { if(fx==0&&fy==1||fy==0&&fx==1) { cout<<0<<endl; return 0; } if(fx==0||fx==1) f[fy]=fx; // 改根节点. else f[fx]=fy; } } for(int i=2;i<=cnt;i++) f[i]=find(i); for(int i=2;i<=cnt;i++) if(vis[i]&&f[i]==i) ans++; } cout<<(1<<ans)<<endl; return 0; }
没错我第一次交的时候又把并查集写挂了......