zoukankan      html  css  js  c++  java
  • 【Codeforces Round 438 A B C D 四个题】

    题目所在比赛的地址在这里

    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)。因此考虑优化。

          于是我们思考能否只枚举一道题,然后通过一个较快的方式找到满足条件的另一道题(当然如果没有就算了)。然后举例讨论:

                      image

                归纳地说,要为当前枚举的这道题找到一个能够和它组成符合条件题集的题,那么他们状态的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的串最多会增加多少种呢(最多指的是没有重复)?如图:

    image

         因此,每次增加最多会出现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

  • 相关阅读:
    css overflow失效的原因
    css3过渡动画 transition
    css3动画 2D 3D transfrom
    百度前端学院第7-8天笔记,百度前端学院系统维护,所以转战仿京东项目。
    position 的absolute会使display变成inline-block
    css 布局 flex
    sqli-labs lesson5-6 布尔盲注 报错注入 延时注入
    sqli-labs lesson1-4
    有关SQL注入的一些小知识点
    DVWA(三):SQL injection 全等级SQL注入
  • 原文地址:https://www.cnblogs.com/Paul-Guderian/p/7639020.html
Copyright © 2011-2022 走看看