zoukankan      html  css  js  c++  java
  • Codeforces Round #626 (Div. 2, based on Moscow Open Olympiad in Informatics)

    A. Even Subset Sum Problem

    题意:给n个数,找个非空子集使其和为偶数,输出子集大小和元素下标,不存在输出-1。

    思路:如果有偶数就取一个偶数,否则取两个奇数,否则不合法。

     1 #include<bits/stdc++.h>
     2 #define LL long long
     3 #define dl double
     4 void rd(int &x){
     5  x=0;int f=1;char ch=getchar();
     6  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
     7  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
     8 }
     9 void lrd(LL &x){
    10  x=0;int f=1;char ch=getchar();
    11  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    12  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
    13 }
    14 const int INF=1e9;
    15 const LL LINF=1e18;
    16 const int N=105;
    17 using namespace std;
    18 int T;
    19 int n,a[N];
    20 int main(){
    21 // freopen("in.txt","r",stdin);
    22  rd(T);
    23  while(T--){
    24   rd(n);for(int i=1;i<=n;i++)rd(a[i]);
    25   int id1=0,id2=0;
    26   for(int i=1;i<=n;i++)if(a[i]%2==0)id1=i;
    27   if(id1)printf("%d
    %d
    ",1,id1);
    28   else {
    29    for(int i=1;i<=n;i++)
    30     if(a[i]%2){
    31      if(id1)id2=i;else id1=i;
    32     }
    33    if(id1 && id2)printf("%d
    %d %d
    ",2,id1,id2);
    34    else printf("-1
    ");
    35   }
    36  }
    37  return 0;
    38 }
    39 /**/
    View Code

    B. Count Subrectangles

    题意:给两个01数组a,b,长度分别为n,m(4e4),记录cij=ai*bj,c形成一个n*m的矩阵,问这个矩阵里面所有大小为k(n*m)的全为1的子矩阵有多少个。

    思路:对k分解质因数来得到子矩阵的长和宽,k=p*q,找这样的子矩阵本质上就是找a中长度为p的连续1子串和b中长度为q的连续1子串。一个长度为p+r的连续1串可以有r+1个长度为p的连续1串。以a为例,预处理出所有连续1串的长度到vector中,排序,再记录后缀和,每次查询lower_bound,整个后缀都会贡献答案。

     1 #include<bits/stdc++.h>
     2 #define LL long long
     3 #define dl double
     4 void rd(int &x){
     5  x=0;int f=1;char ch=getchar();
     6  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
     7  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
     8 }
     9 void lrd(LL &x){
    10  x=0;int f=1;char ch=getchar();
    11  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    12  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
    13 }
    14 const int INF=1e9;
    15 const LL LINF=1e18;
    16 const int N=4e4+10;
    17 using namespace std;
    18 int n,m,k;
    19 int a[N],b[N];
    20 vector<int>S1,S2;
    21 int s1[N],s2[N];
    22 LL ans;
    23 void work(int x,int y){
    24  if(lower_bound(S1.begin(),S1.end(),x) == S1.end())return ;
    25  if(lower_bound(S2.begin(),S2.end(),y) == S2.end())return ;
    26  int id1=lower_bound(S1.begin(),S1.end(),x)-S1.begin(),id2=lower_bound(S2.begin(),S2.end(),y)-S2.begin();
    27  ans+=(s1[id1]-(S1.size()-id1)*(x-1))*(s2[id2]-(S2.size()-id2)*(y-1));
    28 }
    29 int main(){
    30 // freopen("in.txt","r",stdin);
    31  rd(n);rd(m);rd(k);
    32  for(int i=1;i<=n;i++)rd(a[i]);
    33  for(int i=1;i<=m;i++)rd(b[i]);
    34  int now=0;
    35  for(int i=1;i<=n+1;i++){
    36   if(a[i])now++;
    37   else {
    38    if(now)S1.push_back(now);
    39    now=0;
    40   }
    41  }
    42  for(int i=1;i<=m+1;i++){
    43   if(b[i])now++;
    44   else {
    45    if(now)S2.push_back(now);
    46    now=0;
    47   }
    48  }
    49  sort(S1.begin(),S1.end());sort(S2.begin(),S2.end());
    50  for(int i=S1.size()-1;i>=0;i--)s1[i]=s1[i+1]+S1[i];
    51  for(int i=S2.size()-1;i>=0;i--)s2[i]=s2[i+1]+S2[i];
    52  int t=sqrt(k);
    53  for(int i=1;i<=t;i++){
    54   if(k % i)continue;
    55   work(i,k/i);
    56   if(i != k/i)work(k/i,i);
    57  }
    58  printf("%lld
    ",ans);
    59  return 0;
    60 }
    61 /**/
    View Code

    反思:这题wa了n次,具体原因是没开longlong,为什么没开longlong呢?因为我以为k=1是极限情况,那个不爆int,而事实上k=1并不是极限情况。以后多个心眼看看是不是算错了上界。

    C. Unusual Competitions

    题意:给长度为n(1e6)的括号序列,每次可以选择一个区间,将它们按任意顺序重新排列,操作的代价为区间长度,问最少需要多少代价可以使得最终的括号序列合法,做不到输出-1。

    思路:考虑左括号和右括号数目一不一样,不一样一定不合法,一样一定合法,因为至少可以对整个区间进行重新排列。计'('为1,')'为-1,然后求出前缀和s,如果所有的s都非负,那么序列合法(常用结论),那么对于每个负数区间,我们都可以通过一次操作使其合法,区间左端点的s为-1,右端点为0。而我们也必须这么做,否则永远存在负数的s,答案便很容易统计。

     1 #include<bits/stdc++.h>
     2 #define LL long long
     3 #define dl double
     4 void rd(int &x){
     5  x=0;int f=1;char ch=getchar();
     6  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
     7  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
     8 }
     9 void lrd(LL &x){
    10  x=0;int f=1;char ch=getchar();
    11  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    12  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
    13 }
    14 const int INF=1e9;
    15 const LL LINF=1e18;
    16 const int N=1e6+10;
    17 using namespace std;
    18 int n;
    19 char s[N];
    20 int ans;
    21 int cnt;
    22 int main(){
    23 // freopen("in.txt","r",stdin);
    24  rd(n);scanf("%s",s+1);
    25  for(int i=1;i<=n;i++)if(s[i] == '(')cnt++;
    26  if(cnt*2 != n)printf("-1
    ");
    27  else {
    28   bool flg=0;
    29   int now=0;
    30   cnt=0;
    31   for(int i=1;i<=n;i++){
    32    if(s[i] == '(')now++;
    33    if(s[i] == ')')now--;
    34    if(now < 0)flg=1;
    35    cnt++;
    36    if(now == 0){
    37     if(!flg){
    38      cnt=0;
    39     }
    40     else ans+=cnt,flg=0,cnt=0;
    41    }
    42   }
    43   printf("%d
    ",ans);
    44  }
    45  return 0;
    46 }
    47 /**/
    View Code

    反思:括号序列问题一定要往前缀和这个方向去想。

    D. Present

    题意:给n(4e5)个数字ai(1~1e7),求两两和的异或和。

    思路:异或相关题目一般都是按位处理,从低位到高位,发现加和时对当前位造成影响的只有低位,于是把当前位(第k位)及更低的位全部取出来,这些数加和是有上限的,也就是2^(k+1)-2,可以发现使得当前位为1的只能是2^(k-1)~2^k-1和2^(k-1)+2^k~2^(k+1)-2这两个区间内的数,问题也就转换为找出有多少个两两组合落在这个区间里面,二分即可。

     1 #include<bits/stdc++.h>
     2 #define LL long long
     3 #define dl double
     4 void rd(int &x){
     5  x=0;int f=1;char ch=getchar();
     6  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
     7  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
     8 }
     9 void lrd(LL &x){
    10  x=0;int f=1;char ch=getchar();
    11  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    12  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
    13 }
    14 const int INF=1e9;
    15 const LL LINF=1e18;
    16 const int N=4e5+10; 
    17 using namespace std;
    18 int n,a[N];
    19 vector<int>S;
    20 int ans;
    21 int main(){
    22 // freopen("in.txt","r",stdin);
    23  rd(n);for(int i=1;i<=n;i++)rd(a[i]);
    24  for(int now=1;now<=25;now++){
    25   S.clear();
    26   for(int i=1;i<=n;i++)S.push_back(a[i] % (1<<now));
    27   sort(S.begin(),S.end());LL cnt=0;
    28   int L1=(1<<now-1),R1=(1<<now)-1,L2=(1<<now-1)+(1<<now),R2=(1<<now+1)-2;
    29   for(int i=0;i<n;i++){
    30    int l1=lower_bound(S.begin(),S.end(),L1-S[i])-S.begin(),r1=upper_bound(S.begin(),S.end(),R1-S[i])-S.begin()-1;
    31    int l2=lower_bound(S.begin(),S.end(),L2-S[i])-S.begin(),r2=upper_bound(S.begin(),S.end(),R2-S[i])-S.begin()-1;
    32    l1=max(l1,i+1);if(l1 <= r1)cnt+=(r1-l1+1);
    33    l2=max(l2,i+1);if(l2 <= r2)cnt+=(r2-l2+1);
    34   }
    35   if(cnt & 1)ans|=(1<<now-1);
    36  }
    37  printf("%d
    ",ans);
    38  return 0;
    39 }
    40 /**/
    View Code

    反思:一开始我做这道题只是关心当前位,通过1,0的个数算出答案,然而这没有考虑低位造成的影响,于是我记录了当前位1和1组合的情况对下一位造成的影响,然而这仍是不对的,因为造成影响并不只是相邻位之间的,而是所有低位都可能对高位造成影响,有"连锁反应"。

    E. Instant Noodles

    题意:给一张二分图,n(5e5)*2个点,m(5e5)条边,右半部分的n个点有权值,对于左面的点的某个集合S,记录N(S)为与其相邻的右面点的集合,f(S)为N(S)中所有点的权值和,空集记为0,求所有f(S)的gcd是多少。

    思路:先给出结论,将右面的点按其相连的点的集合进行分组,完全一样的分到一组,每组记录权值和,合并成一个新的点,不与任何点相连的点无意义,被丢出集合,这些新的点的权值的gcd就是答案。下面给出证明。首先分到一个组的点必定是同时选同时不选的,所以完全可以合并成一个点。N(S)必是这些点的组合,那么这些点的gcd,设其为p,一定可以整除所有f(S)(注意到这里并不能得出结论,因为最终的N(S)可能不包含单个点的情况,而目前情况无法判断所有单个点都不是N(S)的情况下gcd是否还是p,也就是说此时的p只能说是最终gcd的一个因子)。接下来证明这些点除以p后f(S)的gcd为1,便可以证明最终的gcd为p。用了一种很常用的证明方法,即证明对于任意q,都存在一个f(S)使得q不能整除f(S),这样便可以说明所有f(S)的gcd为1。考虑把左面所有的点都选上,那么N(S)就是右面所有点的集合,如果它不能被q整除,那么就证明完毕了,接下来考虑它能被q整除,我们选取一个度数最小的且不能被q整除的点v,这样的点一定存在,因为所有的点除以p之后gcd为1。我们选取S集合为不和这个点相邻的所有点,那么不存在于N(S)的点有两种,v以及相邻的点为v相邻的点的子集的点,而这些点度数一定小于v的度数,所以能被k整除,而v这个点不能被k整除,所以不存在于N(S)的点的和不能被k整除,因为所有点之和能被k整除,所以N(S)部分的点不能被k整除。证毕。

     1 #include<bits/stdc++.h>
     2 #define LL long long
     3 #define dl double
     4 void rd(int &x){
     5  x=0;int f=1;char ch=getchar();
     6  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
     7  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
     8 }
     9 void lrd(LL &x){
    10  x=0;int f=1;char ch=getchar();
    11  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    12  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
    13 }
    14 const int INF=1e9;
    15 const LL LINF=1e18;
    16 const int N=5e5+10;
    17 const int mod=1e9+7;
    18 using namespace std;
    19 int T;
    20 int n,m;
    21 map<int,LL>S;
    22 LL c[N];
    23 vector<int>f[N];
    24 LL gcd(LL x,LL y){return !y?x:gcd(y,x%y);}
    25 int main(){
    26 // freopen("in.txt","r",stdin);
    27  rd(T);
    28  while(T--){
    29   rd(n);rd(m);S.clear();
    30   for(int i=1;i<=n;i++)lrd(c[i]),f[i].clear();
    31   for(int i=1;i<=m;i++){
    32    int x,y;rd(x);rd(y);
    33    f[y].push_back(x);
    34   }
    35   for(int i=1;i<=n;i++)sort(f[i].begin(),f[i].end());
    36   for(int i=1;i<=n;i++){
    37    if(!f[i].size())continue;
    38    int hs=0;
    39    for(int j=0;j<f[i].size();j++)hs=(1ll*hs*(n+1)+f[i][j])%mod;
    40    if(S.find(hs) == S.end())S[hs]=c[i];
    41    else S[hs]+=c[i];
    42   }
    43   map<int,LL>::iterator it;
    44   LL ans=0;
    45   for(it=S.begin();it != S.end();it++)if(ans)ans=gcd(ans,it->second);else ans=it->second;
    46   printf("%lld
    ",ans);
    47  }
    48  return 0;
    49 }
    View Code

    反思:0与x的gcd就是x而不是0,这一点看gcd的求解过程应该就可以理解。另外就是这种证明gcd为1的思想,之前证明本原多项式已经用过类似的方法了,就是转化为证明对于任意q都存在一个数字不能被q整除。

    F. Reality Show

    题意:有n(2e3)个人,每个人有一个好斗值li,以及招募这个人的花费si,初始好斗值最高为m(2e3),每个好斗值对应一个数字ci(+-5e3),给出1~n+m的好斗值对应的ci。每个人按顺序进入面试,如果这个人的好斗值严格大于前面所有被录取的人的好斗值,那么他一定被淘汰,否则淘汰与否由你决定。选好人之后按以下方式进行,依次进入舞台,根据自己的好斗值得到c的收益,任何时候台上如果有两个好斗值相同的人,那么这两个人其中一个退出舞台,另外一个好斗值+1,并再获得当前好斗值对应的c的收益,问如何选择录取的人使得最终收益最大。

    思路:考虑dp。整个过程有点像2048,两个碰一个再获得额外收益。由于初始好斗值最高为m,最终好斗值最多只会略大于m。所以完全可以把好斗值放入dp的一维中。可以发现选好人之后的入场顺序完全不会影响结果,所以没必要顺序选人。我们考虑倒序选人,好处会在后面体现出来。这里是借鉴榜一大佬的思路,因为答案真的没看明白。首先,可以把si改成cli-si,每个入选的人都至少会带来表演的收益cli,这样si就表示这个人表演带来的净收益,方便后面计算。倒序选人的话,好斗值必须满足不减。f[i][j]表示到当前位,选择的所有人中好斗值小于等于i且等于i的有j个人时的最大收益,枚举完所有人之后对f数组中所有数字取最大值即可。这里f[i][j]中包含两种情况。一种是真的有好斗值为i的人,一种是通过"碰"可以得到好斗值为i的人,不妨称之为真和假。对于当前枚举的人,如果不选择他,那么对所有f没有任何影响。如果选择他,他会更新哪些值呢?不妨设u为他的好斗值。fij中i<u的情况完全不需要考虑,因为与fij的定义不符,而对于i>u的情况,对于里面"真"的部分,不符合好斗值不减这一条件,所以不会更新,而对于"假"的部分,比如f[u+1][1]里面有"假"的部分,并且这个"假"的部分是由一堆好斗值<=u的人凑出来的,那么他一定会有相同的值在f[u][2]这个地方,而如果"假"的部分都是由>u的凑出来的,便和真没有什么区别了。所以事实上我们只需要更新i=u的情况。然后便可以枚举所有可能的j,这里通过记录截至目前为止好斗值为i的人最多有多少个来得到枚举的上界,一定要倒序枚举,因为枚举f[u][j]后会对f[u][j+1]进行更新。除了对f[i][j+1]进行更新,我们还需要考虑他"碰"了之后得到的"假"的情况,也就是继续更新f[i+1][(j+1)/2],f[i+2][(j+1)/4]...而这种方式最多更新log次,总体复杂度是控制在n^2log以内的,并且跑不满。这部分更新完之后,还需要对所有的f[i][0]进行更新,只需要用f[i-1][0]和f[i-1][1]来更新,因为f[i][2]只能对应假的f[i][1]而不能对应f[i][0]了。这部分是n^2。总体复杂度是n^2log,上界很松。cf上只跑了70ms。我们不妨再看一下如果不是倒序枚举会出现什么情况,至少以同样的dp方式来看的话,对于u需要更新>=u的部分中“真”的部分或是尽管假但仍然是由大于等于u的数字碰出来的部分,而我们并不能将这些部分分开来计算。至于是否有其他方法能使得正序可做不得而知,但至少这种倒序应该是一种常用的固定套路。应该是出现在这种"碰"的情况的时候使用,因为倒序时需要的是i>u中假的部分,而这部分可以在=u处找到,反观正序,需要的是i>u中真的部分,这部分却无法单独被拿出来更新,也无法在=u处找到。

     1 #include<bits/stdc++.h>
     2 #define LL long long
     3 #define dl double
     4 void rd(int &x){
     5  x=0;int f=1;char ch=getchar();
     6  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
     7  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
     8 }
     9 void lrd(LL &x){
    10  x=0;int f=1;char ch=getchar();
    11  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    12  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
    13 }
    14 const int INF=1e9;
    15 const LL LINF=1e18;
    16 const int N=2050;
    17 using namespace std;
    18 int n,m;
    19 int l[N],s[N],c[N<<1];
    20 int f[N<<1][N];
    21 int ans,mx[N];
    22 int main(){
    23 // freopen("in.txt","r",stdin);
    24  rd(n);rd(m);
    25  for(int i=1;i<=n;i++)rd(l[i]);
    26  for(int i=1;i<=n;i++)rd(s[i]);
    27  for(int i=1;i<=n+m;i++)rd(c[i]);
    28  for(int i=1;i<=n;i++)s[i]=c[l[i]]-s[i];//直接招募这个人可以赚到多少钱
    29  memset(f,-0x7f,sizeof(f));
    30  for(int i=1;i<=m+20;i++)f[i][0]=0;
    31  for(int i=n;i>=1;i--){
    32   int u=l[i];
    33   for(int j=mx[u];j>=0;j--){//必须倒序 
    34    int x=f[u][j]+s[i],y=j+1;
    35    for(int o=u;y;){
    36     mx[o]=max(mx[o],y);
    37     f[o][y]=max(f[o][y],x);
    38     o++;y/=2;x+=c[o]*y;
    39    }
    40    ans=max(ans,x);
    41   }
    42   for(int i=1;i<=m+20;i++)f[i][0]=max(f[i][0],max(f[i-1][0],f[i-1][1]));
    43  }
    44  printf("%d
    ",ans);
    45  return 0;
    46 }
    47 /*真/假两种情况,可以发现都不需要转移*/
    View Code

    反思:对于一道dp题目,我们只要能保证dp数组涵盖了答案,并且能够确定转移方式,确定初始值便可以得到答案。另外需要了解这种倒序枚举简化dp的思路,具体也说不太清楚,大概是要求某一个值递减,并且所设的dp数组表示<=i的情况,而且i会对>=i的部分进行更新,这时候可以使用倒序枚举的方法。

  • 相关阅读:
    PAT (Advanced Level) Practice 1054 The Dominant Color (20 分)
    PAT (Advanced Level) Practice 1005 Spell It Right (20 分) (switch)
    PAT (Advanced Level) Practice 1006 Sign In and Sign Out (25 分) (排序)
    hdu 5114 Collision
    hdu4365 Palindrome graph
    单链表查找最大值、两个递增的链表合并并且去重
    蓝桥杯-最短路 (SPFA算法学习)
    蓝桥杯-最大最小公倍数
    Codeforces-470 div2 C题
    蓝桥杯-地宫取宝
  • 原文地址:https://www.cnblogs.com/hyghb/p/12452118.html
Copyright © 2011-2022 走看看