bzoj 3916 题解,20200224-G
Algha_Porthos
2020-02-24 00:17:07
题意:
有三个好朋友喜欢在一起玩游戏,A君写下一个字符串S,B君将其复制一遍得到T,C君在T的任意位置(包括首尾)插入一个字符得到U.现在你得到了U,请你找出S.
2<=N<=2000001
## 理解
阅读题意后发现,我们需要找到那个为奇数个的字母。如果有多个或者没有奇数个的字母,说明有锅,输出无解。
然后愉快地开始枚举那个字母,然后哈希。注意要分5种情况:第一个,最后一个,中间那个,第一个->中间那个,中间那个->最后一个。后两种情况哈希需要分段。记录下合法的插入位置,最后再看有几个合法的插入位置。
然后提交,发现WA。
发现需要求得是不同的S字符串,而非不同的插入位置。然后判重。
提交,发现TLE。
为什么会TLE?因为我用的是暴力判重,最坏复杂度$\Theta(N^2)$
最后得出重要论断:由于每一次只有一个插入位置,所以要么是后面半段,要么是前面半段。如图。
![pic1](http://47.103.204.220/picture_bed/uploads/2020/02/bzoj3916_1_.png)
所以我们只需要开两个bool,分别记录前/后半段是否可能作为答案,然后最后判断是否重复就好了。于是顺利把判重降到了$\Theta(N)$
## 代码
```
#include<bits/stdc++.h>
#define int long long
using namespace std;
char c[2100001];
int n,nn;
int a[2100001];
int lst[100];
int pos[100],npos=0,univ;
unsigned long long pw[2100001],hsh[2100001];
int flag[10];
int geths(int a,int b){
return hsh[b]-hsh[a-1]*pw[b-a+1];
}
bool check(int a1,int b1,int a2,int b2){
return geths(a1,b1)==geths(a2,b2)?1:0;
}
signed main(){
cin>>nn;
cin>>(c+1);
n=strlen(c+1);
pw[0]=1;
for(int i=1;i<=n;++i){
a[i]=c[i]-'A'+1;
pw[i]=pw[i-1]*29;
hsh[i]=hsh[i-1]*29+a[i];
lst[a[i]]++;
}
for(int i=1;i<=26;++i){
if(lst[i]%2==1){
pos[++npos]=i;
}
}
if(npos>1){
puts("NOT POSSIBLE");
return 0;
}
univ=pos[1];
for(int i=1;i<=n;++i){
if(a[i]==univ){
if(i==1){
if(check(2,(n+1)/2,(n+1)/2+1,n))
flag[2]=1;
}
else if(i==n){
if(check(1,(n+1)/2-1,(n+1)/2,n-1))
flag[1]=1;
}
else if(i<(n+1)/2){
if(check(1,i-1,(n+1)/2+1,(n+1)/2+i-1)&&check(i+1,(n+1)/2,(n+1)/2+i,n))
flag[2]=1;
}
else if(i==(n+1)/2){
if(check(1,i-1,i+1,n))
flag[1]=flag[2]=1;
}
else if(i>(n+1)/2){
if(check(i+1,n,i-(n+1)/2+1,(n+1)/2-1)&&check((n+1)/2,i-1,1,i-(n+1)/2))
flag[1]=1;
}
}
}
if(!flag[1]&&!flag[2]){
puts("NOT POSSIBLE");
}
else{
if(flag[1]&&flag[2]){
for(int i=(n+1)/2+1,j=1;i<=n&&j<=n/2;++i,++j)
if(c[i]!=c[j]){
puts("NOT UNIQUE");
return 0;
}
}
if(flag[2]){
for(int i=(n+1)/2+1;i<=n;++i)
putchar(c[i]);
puts("");
}
else if(flag[1]){
for(int i=1;i<=n/2;++i)
putchar(c[i]);
puts("");
}
}
return 0;
}
```