题目所在比赛的地址在这里呀
A. Bark to Unlock
·述大意:
输入一个目标串。然后输入n(1<=n<=100)个串,询问是否可以通过这些串收尾相接或者它本身拼出目标串(比如ab rt 可拼出ab rt br ta),输出就是 Yes或者No。提到的串长度都为2。
·分析:
直接按照题目说的,一种是自己本身等于目标串,一种是两个串拼出了目标串所以前者O(n)后者O(n2),就没啦。
1 #include<stdio.h> 2 #define go(i,a,b) for(int i=a;i<=b;i++) 3 int n;char c[5],s[102][5]; 4 int main() 5 { 6 scanf("%s %d",c,&n); 7 go(i,1,n){scanf("%s",s[i]);if(s[i][0]==c[0]&&s[i][1]==c[1]){puts("YES");return 0;}} 8 go(i,1,n)go(j,1,n)if(s[i][1]==c[0]&&s[j][0]==c[1]){puts("YES");return 0;} 9 puts("NO");return 0; 10 }//Paul_Guderian
B. Race Against Time
皮盘WA了…
·述大意:
一个时钟,输入h,m,s,t1,t2表示当前的时间是h时m分s秒(当然是12小时制),t1表示主人公所在钟面的位置,t2表示终点,此时时间静止。保证人和终点不会与指针重合。询问主人公是否可以在不经过任何指针的情况下到达t2(逆时针顺时针均可)。(1≤h≤12,0≤m,s≤59,1≤t1,t2 ≤12,t1≠t2).输出yes或者no就可以了。t1t2的单位是小时。
·分析:
直接想想其实就是三个指针将钟面分成三部分,询问t1t2是否在同一块。首先根据小学知识我们先统一单位。为了精确我们统一成钟面60格为单位,这里不是指的具体时间,因为此时时间静止,我们只关注各个指针的位置。然后我们以12时刻的位置为起点,就可以将钟面拉成一条线,那么我们要做的就是看一看区间(设t1<t2)[t1,t2)有多少个指针就行了。如果指针数为0或者3,那么当然是Yes啦(3意味着可以走另一边到达)。
当然这道题HACK点就在区间的开闭上。为什么是左闭右开?因为题目中提到不重合关系,所以如果有一个输入的指针在t1,就表明它是在t1向前一丢丢处,比如时针就是因为分针走了一段所以移动了一丢丢,因此为左闭。右开就同理了,指针偏出了t2所以不用计算在内。
1 #include<stdio.h> 2 int h,m,s,t1,t2; 3 int main() 4 { 5 scanf("%d%d%d%d%d",&h,&m,&s,&t1,&t2); 6 (h*=5)%=60;(t1*=5)%=60;(t2*=5)%=60;t1>t2?t1^=t2^=t1^=t2:1; 7 puts(((t1<=h&&h<t2)+(t1<=m&&m<t2)+(t1<=s&&s<t2))%3==0?"YES":"NO");return 0; 8 }//Paul_Guderian
C. Qualification Rounds
·述大意:
输入n,k(1≤n ≤105,1≤k ≤4), 表示n个题,k个队。接下来n行输入n个01串,第i行的01串,表示第i道题每个队是否做过,1表示做过,0表示没做过。询问是否存在一个题集(至少包含一个题,同一个题不能多选),使得每个队做过的题目数最不超过这个题集题目数的一半。如果存在输出YES否则NO。
·分析:
第一个遇到的问题一定是这样确定题集的题目个数。当然在比赛的时候没有足够的时间去推结论,所以就先胡乱地靠直觉丢出一个结论:如果存在符合要求的题集,那么一定可以有由一道题或者两道题构成。
基于上述调皮的结论,分开讨论。怎么才能满足一道题都可以呢?当然是这道题没有队伍做过(也就是0/1),这个判断就很简单。
对于两道题构成题集的情况如何处理?我们尝试枚举两道题,然后看看是否有一个队两道题都做过,如果是那么这个不合法,否则就合法了。但是有点小小失望地发现时间不能承受:O(n2)。因此考虑优化。
于是我们思考能否只枚举一道题,然后通过一个较快的方式找到满足条件的另一道题(当然如果没有就算了)。然后举例讨论:
归纳地说,要为当前枚举的这道题找到一个能够和它组成符合条件题集的题,那么他们状态的1必须在不同的位置上,也就是如果这题的某几位是1,那么另一道题这几位一定为0才行,当然其余位是没有限制的。但是想一想某几位为0,为1什么的可不可以用一个状态数组存下来,咋一看不行,其实由于这里的k非常小(k<=4),所以是可以枚举状态(集合)的。
因此,令s表示一个长度为k的二进制数(比如1010),num[s]表示满足在s的有1的位置上都是0的题目个数(比如0101)。那么我们每次枚举一个题(记状态为si),就看一看num[si]是否大于零,如果大于0说明存在满足条件的题可以组成合法题集,直接输出就可以了。
最后呢就是预处理num数组,方法是对于每个读入的si,枚举子集si'然后进行num[si']++就可以啦。
1 #include<stdio.h> 2 #define go(i,a,b) for(int i=a;i<=b;i++) 3 int n,k,a[100003],_,num[100003],S; 4 int main() 5 { 6 scanf("%d%d",&n,&k); 7 go(i,1,n) 8 { 9 go(j,1,k)scanf("%d",&_),(a[i]<<=1)+=_;S=a[i]^((1<<4)-1); 10 for(int s=S;s;s=S&(s-1))num[s]++; 11 if(!a[i]){puts("YES");return 0;} 12 } 13 go(i,1,n)if(num[a[i]]){puts("YES");return 0;};puts("NO");return 0; 14 }//Paul_Guderian
D. Huge Strings
·述大意:
输入n,m(1<=n,m<=100)表示有n个01串。接下来输入这n个串。然后输入m个操作,第i个操作输入ai,bi表示将编号为ai和bi的串拼接起来(ai在左边)得到编号为n+i的串,并且每次操作后输出最大的k值满足新和成的串的子串包含所有的长度为k的01串。注意,合成原料可以是以前合成出来的串,但是保证aibi不同。读入的串总长度不超过100。
·分析:
似乎很难入手,因此就先想想暴力方法。
在没有合成的时候,怎样计算一个串满足条件的最大K值?最最最暴力的方法应该是从小到大枚举k,然后取出该串所有长度为k的子串,最后使用map维护去重记录个数,如果个数等于2k就继续向大的k重复上述步骤,否则就说明K值最大为k-1。当然了,这里的思想就是找出所有子串,看是否含有所有长度为k的01串。类似的,反过来,也就是我们枚举每个长度为k的01串,看是否存在于该串的子串中,如果都在,那也是满足的。(两种暴力显得很美妙)
上面说了一大包可能会使你感到失落,原因是它仅适用于没有合成的时候,而且它们还是BruteForce。别灰心,好的思想总会发光!
然后我们思考两串拼接的情况。为了便于讨论,我们依旧借用上文暴力方法的一个思想——先固定k,然后看k是否满足。两个串拼起来,长度为k的串最多会增加多少种呢(最多指的是没有重复)?如图:
因此,每次增加最多会出现k个新串(就是左端点从p1移到a串末尾共k个串),这照应了出题人的一句话:
……Once we obtain a new string as a concatenation of two old ones, the only new substrings can arise on the border of these two strings……
到目前为止还是很暴力,为啥我们总是说这些方法暴力?是什么最影响时间复杂度?我们列举一下,一下操作很耗时间:枚举k大小,枚举k长度子串,map映射子串/在串中寻找子串……我们发现其实时间复杂度是和k有很大关系的。但是到头来我们其实并不知道k的范围……
我们尝试弄出k的范围。
根据上文讨论的两种情况,可以得出(记住所有原串加起来最多100):
①在所有的原串中,长度为k的子串在原串中最多100个。
②每次拼接最多增加k个新串,最多合并100次,则会增加串k*100个。
因此长度为k的串在最优情况下(即没有重复)则会大约有100+k*100个。
此时如果k是合法的,必须像题目说的那样,要包含所有长度为k的01串。
也就是:
100+k*100>=2k
然后我们发现从小到大第一个不满足的是k=10。因此k的范围实际上是小于等于9的。
由于每次拼接我们发现只会在乎边界上长度k的区域,所以保存串的时候只需要保存长度为10的前后缀用于拼接时产生新串就可以了。
最终的做法是二分k值(k太小了,直接从小到大枚也都可以),然后我们记录当前合成的串的左右来源,并且此处记录拼接产生的新串(恩,这里你可以使用较快的unordered_map维护判重),然后沿着左右串继续分成左右串并重复上述操作,知道最后dfs到的串是输入的原串就停止。这样做可以缩去不必要的部分从而加速,由于dfs对于每个串访问一次,合并时长度为20(左右加起来),所有原串加起来不超过100,时间很稳定。
Wow!暴力变成了正解!
1 #include<stdio.h> 2 #include<iostream> 3 #include<unordered_map> 4 #define go(i,a,b) for(int i=a;i<=b;i++) 5 using namespace std; 6 const int N=305;string a[N][2],s,_; 7 int n,m,l,r,lim,ans,L[N],R[N],len[N],cnt; 8 bool vis[N];unordered_map<string,int>S; 9 void dfs(int I,int k) 10 { 11 if(vis[I])return;vis[I]=1; 12 if(L[I]==0){go(i,0,len[I]-k)S[s=a[I][0].substr(i,k)]==0?S[s]++,cnt++:1;return;} 13 14 int Len=(_=a[L[I]][1]+a[R[I]][0]).length(); 15 go(i,0,Len-k)S[s=_.substr(i,k)]==0?S[s]++,cnt++:1; 16 17 dfs(L[I],k);dfs(R[I],k); 18 } 19 bool check(int k,int n) 20 { 21 go(i,1,n+m)vis[i]=0;S.clear();cnt=0; 22 dfs(n,k);return cnt==(1<<k); 23 } 24 int main() 25 { 26 scanf("%d",&n);go(i,1,n) 27 { 28 cin>>a[i][0];a[i][1]=a[i][0]; 29 L[i]=R[i]=0;len[i]=a[i][0].length(); 30 } 31 scanf("%d",&m);go(i,n+1,n+m) 32 { 33 scanf("%d%d",L+i,R+i); 34 35 a[i][0]=a[L[i]][0]; 36 if(len[L[i]]<=10)a[i][0]=a[i][0]+a[R[i]][0]; 37 if(a[i][0].length()>=10)a[i][0]=a[i][0].substr(0,10); 38 39 a[i][1]=a[R[i]][1]; 40 if(len[R[i]]<=10)a[i][1]=a[L[i]][1]+a[i][1]; 41 if(a[i][1].length()>=10)a[i][1]=a[i][1].substr(a[i][1].length()-10,10); 42 43 go(l,0,10)if(!check(l,i)){printf("%d ",l-1);break;} 44 } 45 return 0; 46 }//Paul_Guderian
当然这道题不止这一种解法。但都是基于k<10这个结论。另外两种:
(一)奇妙法
在CF的提交记录上可以清晰看见有很多人使用了这个方法,时间仅需15ms,可以算是最快的方法了,但是其中关于字符串长度的截取的取值500的正确性如何证明性大米饼至今没有找到方法(某学长似乎出了一组数据WA了这个解法)。
1 #include<string> 2 #include<stdio.h> 3 #include<iostream> 4 #include<algorithm> 5 #define go(i,a,b) for(int i=a;i<=b;i++) 6 using namespace std;const int N=404; 7 string c[N],s;int ans[N],n,m; 8 int Brute_Force() 9 { 10 go(i,1,10)go(k,0,1<<i) 11 { 12 s="";go(j,0,i-1)(1<<j)&k?s+='1':s+='0'; 13 if(c[n].find(s)==-1)return i-1; 14 } 15 } 16 int main() 17 { 18 ios::sync_with_stdio(0); 19 cin>>n;go(i,1,n)cin>>c[i],ans[i]=0; 20 cin>>m;while(m--) 21 { 22 int a,b;cin>>a>>b;c[++n]=c[a]+c[b]; 23 if(c[n].size()>1000)c[n]=c[n].substr(0,500)+c[n].substr(c[n].size()-500,500); 24 ans[n]=max(Brute_Force(),max(ans[a],ans[b]));cout<<ans[n]<<endl; 25 } 26 return 0; 27 }//Paul_Guderian
(二)官方题解
由于k很小,可以直接使用数组或者bitset维护当前串含有的子串的状态,两串合并时一同合并状态即可完成转移。关于如何同时维护每种长度的串是否出现时bitset有一些构造技巧(in the code)。
1 #include<bits/stdc++.h> 2 #define go(i,a,b) for(int i=a;i<=b;i++) 3 #define ro(i,a,b) for(int i=a;i>=b;i--) 4 using namespace std;const int N=205,lim=11; 5 struct C{int len;string L,R;bitset<32566>vis;}s[N]; 6 string S;int st[N],n,Add,m,a,b; 7 int main() 8 { 9 st[1]=0;go(i,2,20)st[i]=st[i-1]+(1<<(i-1)); 10 11 scanf("%d",&n); 12 go(i,1,n){cin>>S;s[i].len=S.length();s[i].vis.reset(); 13 go(j,0,s[i].len-1){Add=0; 14 go(k,j,s[i].len-1){if(k>=j+lim)break;Add=(Add*2)+S[k]-'0'; 15 s[i].vis.set(st[k-j+1]+Add);}} 16 if(s[i].len>lim)s[i].L=S.substr(0,lim),s[i].R=S.substr(s[i].len-lim),s[i].len=lim; 17 else s[i].L=s[i].R=S;} 18 19 scanf("%d",&m); 20 go(i,n+1,n+m) 21 { 22 cin>>a>>b;S=s[a].R+s[b].L;s[i].len=s[a].len+s[b].len;s[i].vis=s[a].vis|s[b].vis; 23 go(j,0,(int)S.size()-1){Add=0; 24 go(k,j,s[i].len-1){if(k>=j+lim)break;Add=(Add*2)+S[k]-'0';s[i].vis.set(st[k-j+1]+Add);}} 25 if(s[i].len>lim) 26 { 27 if(s[a].len<lim)s[i].L=S.substr(0,lim);else s[i].L=s[a].L; 28 if(s[b].len<lim)s[i].R=S.substr((int)S.size()-lim);else s[i].R=s[b].R; 29 s[i].len=lim; 30 } 31 else s[i].L=s[i].R=S; 32 int res=0; 33 ro(k,lim-1,0) 34 { 35 int flag=1; 36 go(j,st[k],st[k]+(1<<k)-1)flag&=s[i].vis[j]; 37 if(flag){res=k;break;} 38 } 39 cout<<res<<endl; 40 } 41 return 0; 42 }//Paul_Guderian
Life's a little bit messy. We all make mistakes. No matter what type of animal you are, change starts with you.————Judy·Hopps