Codeforces Round #604 (Div. 2)
https://codeforces.com/contest/1265
这场的2E是1C的不带checkpoints的版本。
A - Beautiful String
题意:给一个由'a','b','c','?'组成的字符串,把'?'填成前面三种其中之一使得字符串中没有连续两个相同的字符。
首先把本身就非法的、全部都是问号的,以及所有的单独的一个问号解决掉,那么剩下的字符串必定满足:至少有一个非问号字符,且没有两个连续的非问号字符,且没有单独的问号,这样是一定有解的,构造如下。
那么假如有:
"??aba???bc???bcb???"
那么等价于:
"a?aba??abc??cbcb??b"
意思是每段至少2个连续的问号里的第1个问号变成问号之后的第一个字符,若这段是最后一段问号则把最后一个问号变成其前一个。
剩下的一段都填两端没有的字符。
char s[MAXN + 5];
int nxt[MAXN + 5];
char ch[128][128], ch2[128][2];
void init() {
memset(ch, '?', sizeof(ch));
ch['a']['a'] = 'b';
ch['a']['b'] = 'c';
ch['a']['c'] = 'b';
ch['b']['a'] = 'c';
ch['b']['b'] = 'a';
ch['b']['c'] = 'a';
ch['c']['a'] = 'b';
ch['c']['b'] = 'a';
ch['c']['c'] = 'a';
ch2['a'][0] = 'b';
ch2['a'][1] = 'c';
ch2['b'][0] = 'a';
ch2['b'][1] = 'c';
ch2['c'][0] = 'a';
ch2['c'][1] = 'b';
}
void test_case() {
scanf("%s", s + 1);
int n = strlen(s + 1);
for(int i = 2; i <= n; ++i) {
if(s[i] != '?' && s[i - 1] != '?' && s[i] == s[i - 1]) {
puts("-1");
return;
}
}
//上面去除了本身非法的情况,剩下的是一定能构造的
bool suc = 1;
for(int i = 1; i <= n; ++i) {
if(s[i] != '?') {
suc = 0;
break;
}
}
if(suc) {
for(int i = 1; i <= n; ++i)
putchar('a' + (i & 1));
putchar('\n');
return;
}
//上面去除了全部都是'?'的情况
for(int i = 1; i <= n; ++i) {
if(s[i] == '?') {
if(i == 1)
s[1] = ch[s[2]][s[2]];
else if(i == n)
s[n] = ch[s[n - 1]][s[n - 1]];
else
s[i] = ch[s[i - 1]][s[i + 1]];
}
}
//上面去除了单独的'?'
if(s[n] == '?') {
int i = n - 1;
while(s[i] == '?')
--i;
s[n] = s[i];
}
//上面保证了末尾一定不是'?'
int pos = -1;
for(int i = n; i >= 1; --i) {
if(s[i] != '?') {
pos = i;
nxt[i] = -1;
} else
nxt[i] = pos;
}
for(int i = 1; i <= n; ++i) {
if((i == 1 || s[i - 1] != '?') && s[i] == '?') {
//每段的第一个问号,假如他前面
if(i >= 1 && s[i - 1] == s[nxt[i]])
continue;
else
s[i] = s[nxt[i]];
}
}
//上面保证了每个问号的前后都是相同的
for(int i = 1; i <= n; ++i) {
if(s[i] == '?') {
for(int j = i; j < nxt[i]; ++j)
s[j] = ch2[s[i - 1]][j & 1];
i = nxt[i] - 1;
}
}
puts(s + 1);
}
但是实际上!连续三个字符怎么会有重复的呢?随便填就完事了。签到题都是怎么暴力怎么来的,一定是自己想复杂了。
char s[MAXN + 5];
void test_case() {
scanf("%s", s + 1);
int n = strlen(s + 1);
for(int i = 2; i <= n; ++i) {
if(s[i] != '?' && s[i - 1] != '?' && s[i] == s[i - 1]) {
puts("-1");
return;
}
}
//上面去除了本身非法的情况,剩下的是一定能构造的
bool suc = 1;
for(int i = 1; i <= n; ++i) {
if(s[i] == '?') {
for(char c = 'a'; c <= 'c'; ++c) {
bool suc = 1;
s[i] = c;
if(i > 1 && s[i] == s[i - 1])
suc = 0;
if(i < n && s[i] == s[i + 1])
suc = 0;
if(suc)
break;
}
}
}
puts(s + 1);
}
B - Beautiful Numbers
题意:给一个数组是一个[1,n]的permutation。对每个m∈[1,n]问[1,m]是否连续存在在这个数组中。
题解:
首先,[1,1]一定存在。
然后向指定方向扩展到2,若经过2以外的数,把2标记为不存在。
3已经被扩展,或3在区间左右?是:否。
所以每次就问新的数是不是在已有区间中或者左右,是则包含,否则扩展到有为止。
int pos[MAXN + 5];
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1, ai; i <= n; ++i) {
scanf("%d", &ai);
pos[ai] = i;
}
int l = n + 1, r = 0;
for(int i = 1; i <= n; ++i) {
l = min(l, pos[i]);
r = max(r, pos[i]);
putchar('0' + (r - l + 1 == i));
}
putchar('\n');
}
事实上判断一下区间的长度就可以了。
C - Beautiful Regional Contest
题意:给一场Regional发奖牌,奖牌分为氪金牌g、卖银牌s和炼铜牌b三种。,要求满足以下条件:
1、g>0&&s>0&&b>0
2、g<s&&g<b
3、g+s+b<=n/2
同题数的同牌,每个等级都比下一等级至少多一题(区分度极高)。
在此基础上IUPC当然要扩大赛区规模,最好搞到400个队!这样就可以装满一列火车然后在火车站比赛了。主办方要求尽可能多发牌。
题解:
首先最麻烦的条件是“尽可能多发牌”,否则直接贪心:同第一名的全部金,然后发银直到比金多,然后发铜直到比金多。假如可以使得b>g&&g+s+b<=n/2则这个是发票数最小的一个解。
已知假如发牌数一定,把若奖牌跨至少两种题数,那么降级它会更好,因为g->s更容易使得g<s,而在s>=g的前提下可以尽可能补充b的数量。
所以直接找到最后一个可以发牌的人,这样发的奖牌是尽可能多的,也是最大可能满足g>0&&s>0&&b>0的,然后在此基础上发最少的g,最大可能满足g<s&&g<b,在s>=g后尽可能发b最大可能满足g<s&&g<b。显然这样是最容易符合题意的一种构造。
int a[MAXN + 5];
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
int last = n / 2;
if(a[last] == a[last + 1]) {
while(last - 1 >= 1 && a[last - 1] == a[last])
--last;
--last;
}
int g = 0, s = 0, b = 0, i = 1, j;
j = i;
for(; i <= last; ++i) {
if(a[i] == a[j])
++g;
else
break;
}
j = i;
for(; i <= last; ++i) {
if(a[i] == a[j])
++s;
else if(s <= g) {
j = i;
++s;
} else
break;
}
b = last - i + 1;
if(b <= g) {
printf("0 0 0\n");
return;
}
printf("%d %d %d\n", g, s, b);
}
D - Beautiful Sequence
题意:给出0,1,2,3,这4种数字的数量,适当排列他们使得相邻元素的差恰好为1。无解-1。
题解:
首先观察一个等价性:
0 1 2 3 2 1' 2' 3
等价于
0 1 2 1' 2' 3 2 3
也等价于
0 1' 2' 1 2 3 2 3
事实上一组元素可以类似这样穿行,但是不知道怎么严格证明。所以合法的构造等价于[0,1][1,2][2,3]这样堆在一起。
补充:证明:(1,2)的左边可以接(1,0),也可以接(1,2),也可以接(3,2),事实上满足奇偶就可以,右边同理。那么把原本的元素编组出这些接口之后,(1,2)可以在其中任意活动,所以不妨全部丢在中间,左边可能会有多出来的接口(0),右边可能有多出来的接口(3),最后一定满足:(0)(1,0)(1,0)...(1,2)(1,2)...(3,2)(3,2)(3)。注意合法的条件就是中间至少有一个(1,2)
所以说一开始的想法就是对的,直接这样贪。
所以可以枚举是0开头还是1开头,两种之一就是答案,或者分析他们的数量关系严格构造:以上面的例子为例把所有的[0,1](必须以1结尾)和[2,3](必须以2开头)取出,剩下的一定是若干对(2,1),若1多1个可以把这个1插在最前面的0的前面,若2多1个可以把这个2插在最后面的3后面。其他情况无解。
当然当时我是先讨论掉没有0或者没有3的简单情况的。
注意上面这个算法的条件是可以形成若干个这样的接口,而接口的条件要1不比0少且2不比3少。
假如不做这个分类讨论,则要改为判定接口是否成功生成,若生成则必须接上。(其实是真的煞笔,一定是要一半奇数一半偶数的,然后奇数偶数隔着放,肯定是尽可能把差值近的丢在一起,然后特判一下是不是有0到3的情况就行了)
int s[MAXN + 5];
void test_case() {
int a[4];
scanf("%d%d%d%d", &a[0], &a[1], &a[2], &a[3]);
if(a[0] > a[1] + 1 || a[3] > a[2] + 1) {
puts("NO");
return;
}
if(a[0] == a[1] + 1 && (a[2] + a[3] != 0)) {
puts("NO");
return;
}
if(a[3] == a[2] + 1 && (a[1] + a[0] != 0)) {
puts("NO");
return;
}
int n = a[0] + a[1] + a[2] + a[3];
if(a[1] - a[0] == a[2] - a[3] || a[1] - a[0] + 1 == a[2] - a[3]) {
int i;
for(i = 1; a[0]--; i += 2)
s[i] = 0;
for(; a[2]--; i += 2)
s[i] = 2;
for(i = 2; a[1]--; i += 2)
s[i] = 1;
for(; a[3]--; i += 2)
s[i] = 3;
puts("YES");
for(int i = 1; i <= n; ++i)
printf("%d%c", s[i], " \n"[i == n]);
return;
}
if(a[1] - a[0] == a[2] - a[3] + 1) {
int i;
for(i = 1; a[1]--; i += 2)
s[i] = 1;
for(; a[3]--; i += 2)
s[i] = 3;
for(i = 2; a[0]--; i += 2)
s[i] = 0;
for(; a[2]--; i += 2)
s[i] = 2;
puts("YES");
for(int i = 1; i <= n; ++i)
printf("%d%c", s[i], " \n"[i == n]);
return;
}
puts("NO");
return;
}
2E - Beautiful Mirrors
这个是1C的不带checkpoints的版本。
题意:有n个格子,你从1号格子开始走,在格子i上面有pi的概率往i+1走,若走到n+1就结束,否则回到1号重来。求结束的期望步数。
题解:设dp[i]表示从第i个格子开始走,到结束的期望步数,则答案为dp[1]。
若设 \(dp_{n+1}=0\) ,则对所有的i都有:
\(dp_{i}=p_{i}(1+dp_{i+1})+(1-p_{i})(1+dp_{1})\)
即:
\(dp_{i}=1+p_{i}dp_{i+1}+(1-p_{i})dp_{1}\)
特别的:
\(dp_{1}=1+p_{1}dp_{2}+(1-p_{1})dp_{1}\)
即:
\(dp_{1}=\frac{1}{p_{1}}+dp_{2}\)
然后:
\(dp_{2}=1+p_{2}dp_{3}+(1-p_{2})dp_{1}\)
即:
\(dp_{2}=1+p_{2}dp_{3}+(1-p_{2})(\frac{1}{p_{1}}+dp_{2})\)
\(dp_{2}=\frac{1+p_{1}-p_{2}}{p_{1}p_{2}}+dp_{3}\)
\(dp_{2}=\frac{q_2+p_{1}}{p_{1}p_{2}}+dp_{3}\)
再算出:
\(dp_{3}=\frac{q_3(2p_1+p_2)+p_1p_2}{p_{1}p_{2}p_3}+dp_{4}\)
太难算了。
问了一下群友,群友给了一个看不懂的方程。
假如设从1开始第一次走到第i个格子的期望天数是dp[i],那么
\(dp[1]=0\)
从第i个格子走到第i+1个格子,有p[i]的概率成功此时支付dp[i]+1,有q[i]的概率失败,此时已经支付了dp[i]+1,然后在1,从1到i+1的价格恰好就是dp[i+1]进入套娃。
假如粗暴一点,失败的就付出已支付的成本,然后转回来?
\(dp[i+1]=p[i](dp[i]+1)+q[i](dp[i]+1+dp[i+1])\)
\(p[i]dp[i+1]=p[i](dp[i]+1)+q[i](dp[i]+1)\)
\(dp[i+1]=\frac{1}{p[i]}(dp[i]+1)\)
过了第二个样例了?那就这样写。AC全靠猜?大力套样例?
ll p[MAXN + 5];
ll dp[MAXN + 5];
const ll inv100 = qpow(100, MOD - 2);
void test_case() {
int n;
scanf("%d", &n);
dp[1] = 0;
for(int i = 1; i <= n; ++i) {
scanf("%lld", &p[i]);
p[i] = p[i] * inv100 % MOD;
dp[i + 1] = qpow(p[i], MOD - 2) * (dp[i] + 1) % MOD;
}
printf("%lld\n", dp[n + 1]);
}