Codeforces Round #638 (Div. 2)
A. Phoenix and Balance
题意:n堆硬币,n为偶数,分别有2^1,2^2,...,2^n个,将n/2堆划为一组,使得两组的个数差最小。
思路:2^n比其他的加起来还要大。先把他独自成组,为了减小最终的差,与其一组的应该尽可能选较小的,也就是2^1 ~ 2^(n/2-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 using namespace std; 17 int T; 18 int n; 19 int pw[33]; 20 int main(){ 21 // freopen("in.txt","r",stdin); 22 rd(T); 23 pw[0]=1;for(int i=1;i<=30;i++)pw[i]=pw[i-1]*2; 24 while(T--){ 25 rd(n); 26 int ans=pw[n]; 27 for(int i=n-1;i>=n/2;i--)ans-=pw[i]; 28 for(int i=1;i<n/2;i++)ans+=pw[i]; 29 printf("%d\n",ans); 30 } 31 return 0; 32 } 33 /**/
B. Phoenix and Beauty
题意:给一个长为n(100)的序列ai(1<=ai<=n),任意插入若干个1~n之间的数字,使得其任意长度为k的子串的和相等,最终串的长度不得大于10000,任意输出一个最终的序列。不合法输出-1。
思路:之前有过类似的构造题,大体上就是把n拓展为n^2级别的,每个长n的序列对应原序列的一个数字。我们可以发现任意长度为k的子串的和相等,其实等价于a[i]=a[i+k]对任意i成立。也就是说最终串中不同数的个数至多有k个,如果多于k个则一定不合法。考虑一种构造方案,依次将ai中的不同的数字罗列出来,再加上任意数字补足k个,然后将这个长k的串复制n份。这样每一份长k的串对应原序列的一个数。而这个最终序列也就可以通过原序列插入一些数字得到。长度为nk。
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=1e4+10; 17 using namespace std; 18 int T; 19 int n,k; 20 int a[N]; 21 bool is[N]; 22 int main(){ 23 // freopen("in.txt","r",stdin); 24 rd(T); 25 while(T--){ 26 rd(n);rd(k); 27 for(int i=1;i<=n;i++)is[i]=0,rd(a[i]); 28 for(int i=1;i<=n;i++)is[a[i]]=1; 29 int cnt=0;for(int i=1;i<=n;i++)cnt+=is[i]; 30 if(cnt > k)printf("-1\n"); 31 else { 32 printf("%d\n",k*n); 33 for(int i=1;i<=n;i++){ 34 int tmp=k-cnt; 35 for(int j=1;j<=n;j++) 36 if(is[j])printf("%d ",j); 37 else if(tmp)printf("%d ",j),tmp--; 38 } 39 puts(""); 40 } 41 } 42 return 0; 43 } 44 /**/
C. Phoenix and Distribution
题意:给一个长度为n(1e5)的字符串(只包含26个小写字母),将其任意分割为k个非空字符串(只要分割出的k个字符串中各字母数量之和与原字符串各字母数量相同即可)。比如abab可以划分为ba和ab两个字符串或者a和a和b和b四个字符串。求一种分割方式使得分割出的字符串中字典序最大的那个字符串字典序最小,输出这个字符串即可。
思路:我们只关心26个小写字母各有多少个而不关心初始字符串到底是什么。首先,我们关注最小的那个字符,我们肯定尽量把它作为每个字符串的第一位。如果这个字符不够k个,那么最终答案一定是一个字符,即第k小的那个字符,因为最终字符串不能是空的,所以必须有一个字符,于是必须将前k小的字符串依次分配给他们,而如果最小的不够k个,那么未分配到最小字符的串,它们的字典序恒大于已分配到最小字符的串,我们可以把剩下的所有字母都放到某一个已分配到最小字符的串的后面。如果够k个,那么先给每一个字符串分配一个。然后如果此时剩下的字符只有一种,就用类似于抽屉原理的方法分配,使得最终的串尽可能短,其形式为abbbb这样,ab可以相同。而如果剩下多种字符串,我们可以直接将其按大小顺序放到任意一个字符串的后面,因为不这样做的话,会导致比较大的字符最终位置前移,前移后得到的字符串的字典序一定大于按其大小顺序放到任意一个字符串的后面的字典序。
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=1e5+10; 17 using namespace std; 18 int T; 19 int n,k; 20 char s[N]; 21 int cnt[26]; 22 int main(){ 23 // freopen("in.txt","r",stdin); 24 rd(T); 25 while(T--){ 26 rd(n);rd(k);scanf("%s",s); 27 memset(cnt,0,sizeof(cnt)); 28 for(int i=0;i<n;i++)cnt[s[i]-'a']++; 29 int tmp=0;for(int i=0;i<26;i++)if(cnt[i]){tmp=i;break;} 30 if(cnt[tmp] < k){ 31 k-=cnt[tmp]; 32 for(int i=tmp+1;i<26;i++) 33 if(cnt[i] >= k){putchar('a'+i);break;} 34 else k-=cnt[i]; 35 } 36 else { 37 putchar('a'+tmp);cnt[tmp]-=k;n-=k; 38 int sum=0,id=0;for(int i=0;i<26;i++)if(cnt[i])sum++,id=i; 39 if(sum == 1)for(int i=1;i<=(n-1)/k+1;i++)putchar('a'+id); 40 else { 41 for(int i=0;i<26;i++) 42 for(int j=1;j<=cnt[i];j++) 43 putchar('a'+i); 44 } 45 } 46 puts(""); 47 } 48 return 0; 49 } 50 /**/
D. Phoenix and Science
题意:第一天有1个质量为1的细菌,每天白天你可以选择一些细菌进行分裂,分裂为两个质量减半的细菌,每天晚上所有细菌质量+1,问最少多少天可以使得所有细菌的质量之和为n(1e9)。输出每天分裂多少个细菌。
思路:我和答案的思路不同,先说一下答案更优秀的思路。
答案:如果白天有x个细菌,那么晚上增长时可以有x~2x个细菌,我们构造一个序列表示每天增长的质量,也可以认为是当天分裂后的细菌数目。补充一个第0天使得研究更加舒服。第0天的变化是0->1,于是a0=1,a0<=a1<=2a0,a1<=a2<=2a1....以此类推。而前缀和si就表示第i天过后的细菌质量总和。容易想到当这个序列为1,2,4...,2^i时会以最快的速度增长,我们取其<=n时最大的i,再将n-si插入原序列并进行排序,得到的这个序列依旧符合ai<=ai+1<=2ai的条件。而ai+1-ai就表示第i+1天分裂的细菌数目。
我:其实正确性是不太好说明的,但是又肯定是对的。倘若我们知道了最终的天数,那么我们就可以知道第i天分裂出的细菌在这些天增长的总质量。所以二分天数。可以注意到这个天数不会太长,是log级别的,而我又不太能说明,于是在数据范围允许的情况下尽可能的取大了。判断的时候则是当前天能分裂则分裂,有些类似于二进制下从高位向低位枚举的那种感觉,来凑出n,也有可能出现>n的情况(初始的那一个细菌长了x天后比n大了),也同样是合法的。二分出合法的情况中最短的那个天数即可。
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=1e3+10; 17 using namespace std; 18 int T; 19 int n; 20 int ans[N]; 21 bool check(int x){ 22 int now=1,sum=x+1; 23 for(int i=1;i<=x;i++){ 24 int l=0,r=now; 25 while(l != r){ 26 int mid=(l+r>>1)+1; 27 if(1ll*mid*(x-i+1)+sum <= n)l=mid; 28 else r=mid-1; 29 } 30 ans[i]=l;now+=l;sum+=l*(x-i+1); 31 } 32 return sum >= n; 33 } 34 int main(){ 35 // freopen("in.txt","r",stdin); 36 rd(T); 37 while(T--){ 38 rd(n); 39 int l=1,r=1e3; 40 while(l != r){ 41 int mid=l+r>>1; 42 if(check(mid))r=mid; 43 else l=mid+1; 44 } 45 check(l); 46 printf("%d\n",l); 47 for(int i=1;i<=l;i++)printf("%d ",ans[i]); 48 puts(""); 49 } 50 return 0; 51 } 52 /**/
反思:仅考虑我的代码,这种在check过程中构造最终解的二分,一定要在二分结束后再次进行check,否则可能会出现最终的l压根没有被check就被卡出来了的情况。
E. Phoenix and Berries
题意:n(500)堆果子,每堆有红果子ai个和蓝果子bi个。一个筐子可以装k(500)个果子,要求果子来自同一堆或者果子颜色相同。问最多能装多少筐果子。
思路:考虑只按颜色装果子,所有红果子装一起,所有蓝果子装在一起,假设剩下x个红果子,y个蓝果子。如果x+y<k,则剩下的红果子和蓝果子加起来都装不了一筐子,那么这种装法就已经构造出了一组最优解。而如果可以凑出来一筐。我们不排除拿出这x+y个果子,再拿出一些已经装进筐子里的果子,将它们按同一堆装在一起的方式重新组合后能多装出来一筐的情况(也只可能多出来一筐)。于是我们需要知道可否通过按堆装筐的方式使得装起来的红果子总数对k取模后<x,蓝果子对k取模(这个数与红果子对y取模后的数之和为k,因为是k个k个取得)后<y。令f[i][j]表示前i堆,红果子对k取模后有j个的情况是否存在。转移时枚举当前堆取多少红果子即可。
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=505; 17 using namespace std; 18 int n,k; 19 int a[N],b[N]; 20 LL sum_a,sum_b; 21 bool f[N][N]; 22 int main(){ 23 // freopen("in.txt","r",stdin); 24 rd(n);rd(k); 25 for(int i=1;i<=n;i++)rd(a[i]),rd(b[i]),sum_a+=a[i],sum_b+=b[i]; 26 int flg=0; 27 if(sum_a % k + sum_b % k >= k){ 28 f[0][0]=1; 29 for(int i=0;i<n;i++){ 30 for(int j=0;j<k;j++){ 31 if(!f[i][j])continue; 32 f[i+1][j]=1; 33 for(int o=1;o<=min(k-1,a[i+1]);o++) 34 if(k-o <= b[i+1])f[i+1][(j+o)%k]=1; 35 } 36 } 37 for(int i=1;i<=sum_a%k;i++) 38 if(f[n][i] && k-i <= sum_b%k)flg=1; 39 } 40 printf("%lld\n",sum_a/k+sum_b/k+flg); 41 return 0; 42 } 43 /**/