Codeforces round 638赛后解题报告
A. Phoenix and Balance
这道题是一个很明显的贪心策略,我们都知道:
∑ i = 0 k 2 i = 2 k + 1 − 1 \sum_{i=0}^{k} 2^i=2^{k+1}-1 i=0∑k2i=2k+1−1
所以在在这道题中,我们有:
∑ i = 1 n − 1 2 i < 2 n \sum_{i=1}^{n-1} 2^i <2^n i=1∑n−12i<2n
所以我们的 2 k 2^k 2k 比其他数的和都要大,所以贪心策略很明显了,就是最大的数带上前 n 2 − 1 \frac{n}{2}-1 2n−1 个数,其他数一起,最后就得到了答案。
a n s = 2 n + ∑ i = 1 n 2 − 1 2 i − ∑ n 2 n − 1 2 i ans=2^n+\sum_{i=1}^{\frac{n}{2}-1} 2^i - \sum_{\frac{n}{2}}^{n-1} 2^i ans=2n+i=1∑2n−12i−2n∑n−12i
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
int n,t;
signed main() {
cin>>t;
while(t--) {
n=read();
int ans=0,minn=0x7fffffffffffffff;
int sum=(1<<(n+1))-2,s=(1<<n);
for(int i=1;i<=n/2-1;i++) {
s+=(1<<i);
}
sum-=s;
cout<<abs(sum-s)<<endl;
}
return 0;
}
B. Phoenix and Beauty
这道题很有意思。
首先我们来理解一下题意:“An array is beautiful if all its subarrays of length k have the same sum. ”这句话的意思也就是告诉你,这个数组的形式一定是这样的:
c 1 , c 2 , c 3 . . . , c k − 1 , c k , c 1 , c 2 , c 3 , . . . c k − 1 , c k , c 1 , c 2 . . . c_1,c_2,c_3...,c_{k-1},c_k,c_1,c_2,c_3,...c_{k-1},c_k,c_1,c_2... c1,c2,c3...,ck−1,ck,c1,c2,c3,...ck−1,ck,c1,c2...
只有这种轮换式的数组满足他的要求,这个应该是很好想到的,也是这道题的一个关键。这里一定要理解。
接着,我们发现,如果原来的数组中不同的数的个数大于 k k k,那么一定是无法配成上述的数组的。直接输出 − 1 -1 −1。
再来想一个问题,如果我们现在知道了我们的 c 1 , c 2 , c 3 . . . , c k − 1 , c k c_1,c_2,c_3...,c_{k-1},c_k c1,c2,c3...,ck−1,ck。那么我们的答案数组一定很好求。所以我们这道题就转化成了求 c c c 数组。求的方法很简单,就是先把所有的不同的数填进去,然后在把剩下的空位随便填一些数,就可以了。因为这样保证了我们一定有解。至于说题目里说答案数组的长度 m ≤ 1 0 4 m\leq 10^4 m≤104。我们可以证明,答案数组的长度最坏情况下为 n ⋅ k ≤ 1 0 4 n\cdot k\leq 10^4 n⋅k≤104所以不用担心具体证明可以参考这道题,思想是差不多的:CopyCopyCopyCopyCopy。
下面介绍两种已求出 c c c 数组,求答案数组的方法。
复制
这个方法就是把 c c c 数组复制 k k k 遍即可,CopyCopyCopyCopyCopy的想法就一样了。
比赛咋就没想到这种方法呢
正经求解
我们对着 a a a 数组扫一遍。如果我们现在需要的数正好是目前指针指向的 a a a 数组里的数,我们就直接把这个数放到里面来,并且指针指向下一个 a a a 里的数。否则我们就把这个数放到 b b b 数组里,指针不动。这个想法我给代码吧。
主要是比赛里只想到这种qwq
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
const int maxn=1e4+10;
int n,t,m,a[maxn],b[maxn],c[maxn],k;
bool ex[maxn];
signed main() {
t=read();
while(t--) {
fill(ex,ex+n+1,0);
n=read();k=read();
m=0;
int num=0;
for(int i=1;i<=n;i++) {
a[i]=read();
if(!ex[a[i]]) {
ex[a[i]]=1;
num++;
}
}
if(num>k) {
cout<<-1<<endl;
continue;
}//判断-1
int q=1,mark=0x7fffffffffffffff;
for(int i=1;i<=n;i++) {
if(ex[i]) {
c[q++]=i;
mark=i;
}
}
while(q<=k) {
c[q++]=mark;
}
q--;
//构造c数组。答案数组是b
int now=1,con=0;//指针是now
num=1;
bool flag=1;
while(1) {
if(a[now]==c[num]) {
b[++m]=a[now];
now++;//第一种情况
}
else {
b[++m]=c[num];//第二种情况
}
num++;
if(num==k+1) {
num=1;
}//开启新的循环
if(now>n) {
break;
}//已经找到答案,退出循环。
}
cout<<m<<endl;
for(int i=1;i<=m;i++) {
cout<<b[i]<<" ";
}//输出
cout<<endl;
}
return 0;
}
C. Phoenix and Distribution
我们先来看题目中的一个条件
a i a_i ai should be non-empty
所以无论如何我们都要先把第一位填上。在填数值前要排序就不说了。
我们把第一位填完后,就要开始像,如何保证最大的最小呢?有的人可能会想到“斗地主发牌”的方式,一个一个发,十分快乐,但是第二组样例就把你的想法击得粉碎了。
5 2
baacb
这组的答案是 a b b c abbc abbc 但是“斗地主发牌”算出来的是 a b c abc abc。相差甚远。所以我们要想为什么。
因为说我们要让最大的最小,所以我们尽可能让低位上的字符最小,所以我们要把“斗地主发牌”里放到其他串里的捞回来,放到一个串里,使得这一串使我们最后的答案。因此我们发现这时候“一条龙”的方法最好。
可惜了,第一组样例就把你的 AC 梦击碎。
4 2
baba
答案是
a
b
ab
ab,可是一条龙算出来的是
a
b
b
abb
abb。真是悲剧。那么什么时候要“一条龙”,什么时候要“斗地主发牌”呢?我们不难发现如果第一位上的字符填完了后面的没填的字符里只有一种字符了,那么我们一条龙时的长度太大,也不行,这时候为了让长度最短,我们还是要“斗地主发牌”。
所以现在两种情况非常分明了。我们也通过了样例,但是很可惜:
当时我也很崩溃,但是随便造了一组数据,就查出问题所在了。
6 3
aabbbb
答案是 b b b,但是我们输出了 b b bb bb。因为我们采取了“斗地主发牌”。那为什么这里有是采用“一条龙”呢?因为我们发现如果第一位上已经不相同了,那么后面无论是“斗地主”还是“一条龙”都没有关系了。所以我们让他长度最短,这个程序就没有问题了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
const int maxn=1e5+10;
int n,k;
string a[maxn];
char s[maxn];
signed main() {
int t=read();
while(t--) {
for(int i=1;i<=k;i++ ) {
a[i]="";
}
n=read();k=read();
for(int i=1;i<=n;i++) {
cin>>s[i];
}
sort(s,s+n+1);
int now=1;
for(int i=1;i<=k;i++) {
a[i]+=s[now++];
}
int sum[30]={};
int q=0;
for(int i=now;i<=n;i++) {
if(!sum[s[i]-'a']) {
q++;
}
sum[s[i]-'a']++;
}
if(s[1]!=s[k]) {
cout<<s[k]<<endl;
}
else {
if(q==1) {
int t=1;
for(;now<=n;now++) {
a[t]+=s[now];
t++;
if(t==k+1) {
t=1;
}
}
}
else {
for(;now<=n;now++) {
a[1]+=s[now];
}
}
string ans=a[1];
for(int i=2;i<=k;i++) {
if(a[i]>ans) {
ans=a[i];
}
}
cout<<ans<<endl;
}
}
return 0;
}
D. Phoenix and Science
这道题很有意思的~
首先我们发现, d d d 的值其实很好想。我们先考虑在第 d d d 天的最多数量(长度),也就是每个晚上所有的都分裂。
1 | 2 | 3 | 4 | 5 | … | d |
---|---|---|---|---|---|---|
1 | 3 | 7 | 15 | 31 | … | 2 d − 1 2^d-1 2d−1 |
所以说给定 n n n,我们可以求出 d = log n d=\log n d=logn。这一点很好想到,但是我们的难点就在如何去构造答案。
我们还可以知道一点,如果我们在第
i
i
i 天分裂
p
i
p_i
pi 个病毒,我们对最终
n
n
n 的贡献为
(
d
−
i
+
1
)
⋅
p
i
(d-i+1)\cdot p_i
(d−i+1)⋅pi。而且我们要明确,病毒的防治要越早越好我们的病毒越早分裂越好控制,到后面数量多了,反而不好控制,所以我们尽量让病毒在前期分裂。
所以整个算法的思路就很简单了,我们尽量在每一天分裂最多的病毒,也就是 min ( l e f t d − i + 1 , h o l d ) \min (\frac{left}{d-i+1},hold) min(d−i+1left,hold), l e f t left left 表示我们还要多少的长度, n o w now now 表示我们现在有多少病毒。如果最后我们处理下来 n ≠ 0 n\neq 0 n=0,就代表无解,输出 − 1 -1 −1 即可,否则输出我们的答案数组。
做个总结,我们的宗旨就是尽量在前期控制,如果是从后往前推很容易脱节,找不到答案,那就失败了。所以行前往后在这里反而是一种最好的选择。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
int n,p[100001];
signed main() {
int t=read();
while (t--) {
n=read();
int d=0,tot=0,t=1;
for (;tot<n;d++,t<<=1) {
tot+=t;
}
d--;
n=n-1-d;
int hold=1,k;
for (int i=d;i>=1;--i) {
k=min(n/i,hold);
p[d-i+1]=k;
n-=k*i;
hold+=k;
}
if (n!=0) {
cout<<-1<<endl;
}
else {
cout<<d<<endl;
for(int i=1;i<=d;++i)
cout<<p[i]<< " ";
cout<<endl;
}
}
return 0;
}