Beautiful Regional Contest

题目链接 http://codeforces.com/contest/1264/problem/A

题意

给出区域赛中 N 支队伍做出的题数,在一定规则下发奖时,金银铜牌奖牌数总和的最大值。
要求:

  1. 合法方案中每种奖牌至少一个
  2. 金牌数严格少于银牌和铜牌数,银牌和铜牌数间没有要求
  3. 金牌队伍解题数必须严格大于银牌队伍解题数,银铜、铜铁同理。
  4. 奖牌总数和不能超过队伍总数的一半(21支队伍时奖牌数至多10,26支队伍将牌至多13)。

题解

首先根据 要求4,去除一定不可能得奖的后一半人。
因为 要求2 只要求银铜的奖牌数多于金牌,所以直接使金牌数为最少(解题最多的那些队伍),然后从剩下的队伍中找到足够多的队伍得银牌(前缀和然后二分),剩下得铜牌即可。
注意中间发现无法满足要求时及时结束代码。

代码

/*
* Codeforces Round #604
* Beautiful Regional Contest
*/
#include <cstdio>
#include <algorithm>
using namespace std;
int N;
int ls[400010];
void work() {
scanf("%d", &N);
for (int i = 0; i < N; i++) {
scanf("%d", ls+i);
}
int j, nu;
nu = ls[0];
ls[0] = 1;
j = 0;
for (int i = 1; i < N; i++) {
if (ls[i] == nu) {
ls[j]++;
}
else {
nu = ls[i];
j++;
ls[j] = 1;
}
}
int sm = 0;
for (int i = j; i >= 0; i--) {
sm += ls[i];
if (sm*2 >= N) {
j = i;
break;
}
}
if (j < 3) {
printf("0 0 0\n");
return;
}
for (int i = 1; i < j; i++) {
ls[i] += ls[i-1];
}
int a, b, c;
a = ls[0];
int *it;
it = lower_bound(ls, ls+j, a*2+1);
if (it == ls+j)
a = b = c = 0;
else {
b = *it - a;
c = ls[j-1] - *it;
if (!a || !b || !c || a>=c) {
a = b = c = 0;
}
}
printf("%d %d %d\n", a, b, c);
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
work();
}
return 0;
}

Beautiful Sequence

题目链接 http://codeforces.com/contest/1264/problem/B

题意

给出 0, 1, 2, 3 四种数字的个数 a, b, c, d,要求构造数列,满足相邻数字相差为1。

题解

可以知道 0 只能和 1 相邻,当有多个 0 时,一定是通过 010101 这样的序列消耗掉 0 的,对于 3 同理,通过 232323 消耗。
优先考虑用 1 消耗尽可能多的 0,用 2 消耗尽可能多的 3。
剩下的 1 和 2 一定是通过 212121 的序列消耗的。
根据上述规则构成 010121212323 序列中的三部分 0101 2121 2323。
此外,恰好多出一个 1 和 2 时可以放在开头和结尾形成 1 0101 2121 2323 2。
多出 0 和 3 时,以 0 为例,构成 0101 0 时需要保证没有 2 和 3,因为 2 和 3 都不能接在 0 的后面。

按照如上分析实现代码即可。
实际做题过程中,通过理清思路可以让编码难度直线下降。
当前思路为,优先考虑 01 和 23 成对消耗,然后考虑剩余 12 成对消耗,最后处理多余数字的情况。这是因为成对消耗可以看作是化简问题的过程,处理多余数字是特判的过程。
如果混用这两部分的话,容易出错漏情况。

代码

/*
* Codeforces Round #604
* Beautiful Sequence
*/
#include <cstdio>
#include <algorithm>
using namespace std;
int a, b, c, d;
int main() {
scanf("%d %d %d %d", &a, &b, &c, &d);
int A, B, C, D;
int u;
A = a;
B = b;
C = c;
D = d; u = min(A, B);
A -= u;
B -= u;
u = min(C, D);
C -= u;
D -= u;
u = min(B, C);
B -= u;
C -= u;
if (A > 1 || B > 1 || C > 1 || D > 1)
goto be;
if ((A && (c||d)) || (D && (a||b)))
goto be;
he:
printf("YES\n");
if (B)
printf("1 ");
u = min(a, b);
for (int i = 0; i < u; i++)
printf("0 1 ");
if (A)
printf("0 ");
u = min(b - min(a, b), c - min(c, d));
for (int i = 0; i < u; i++)
printf("2 1 ");
if (D)
printf("3 ");
u = min(c, d);
for (int i = 0; i < u; i++)
printf("2 3 ");
if (C)
printf("2 ");
printf("\n");
return 0;
be:
printf("NO\n");
return 0;
}

一个思路不够清晰的代码

/*
* Codeforces Round #604
* Beautiful Sequence
*/
#include <cstdio>
#include <algorithm>
using namespace std;
int a, b, c, d;
int main() {
scanf("%d %d %d %d", &a, &b, &c, &d);
int A, B, C, D;
int u;
A = a;
B = b;
C = c;
D = d; // 从这里开始
u = min(A, B);
A -= u;
B -= u;
u = min(C, D);
C -= u;
D -= u; if (A > 1 || D > 1)
goto be;
if (A && (c||d))
goto be;
if (D && (a||b))
goto be;
int uBC;
uBC = u = min(B, C);
B -= u;
C -= u;
if (B > 1 || C > 1)
goto be;
// 到这里
he:
printf("YES\n");
if (B)
printf("1 ");
u = min(a, b);
for (int i = 0; i < u; i++)
printf("0 1 ");
if (A)
printf("0 ");
u = uBC;
for (int i = 0; i < u; i++)
printf("2 1 ");
if (D)
printf("3 ");
u = min(c, d);
for (int i = 0; i < u; i++)
printf("2 3 ");
if (C)
printf("2 ");
printf("\n");
return 0;
be:
printf("NO\n");
return 0;
}

Beautiful Mirrors with queries

题目链接 https://codeforces.com/contest/1264/problem/C

题意

N 个节点依次排列,每天尝试通过一个节点,每个节点有 \(P_i\) 的概率通过,下一天尝试第 i+1 个节点,如果没通过,下一天尝试通过前面最近的存档点。
求成功通过最后一个节点需要天数的期望。
Q 次询问,每次给出一个编号 u,修改 u 是否为检查点,并且输出修改后的答案。
1 号节点始终为存档点。
题中 \(P_i\) 以 \(\frac{p_i}{100}\) 的形式给出。

题解

如果没有检查点的修改,显然可以通过如下递推式计算答案,其中 \(F_i\) 表示从起点走到 i 号点需要天数的期望。
\[ F_i = F_{i-1} + P_i*1 + (1-P_i)(F_i+1) \]
在经过了前 \(i-1\) 个节点之后,\(P_i\) 的概率一次尝试并通过,\(1-P_i\)的概率退回存档点重新开始。

或者可以用一个通项公式来求,\(E\) 表示通过全部节点的期望天数。
\[ E = P_1(1+P_2(1+\dots)+(1-P_2)(1+E)) + (1-P_1)(1+E) \]
化简式子:
\[ \begin{aligned}
E =& (P_1 + P_1P_2 +\dots+ P_1\dots P_N)\\
&+[(1-P_1)(1+E) + P_1(1-P_2)(1+E) +\dots+ P_1\dots P_{N-1}(1-P_N)(1+E)]\\
=& (P_1 + P_1P_2 +\dots+ P_1\dots P_N) + (1-P_1\dots P_N)(1+E)\\
=& (1 + P_1 + P_1P_2 +\dots+ P_1\dots P_{N-1}) + (1-P_1\dots P_N)E
\end{aligned} \]

\[
E = \frac{A}{B} = \frac{1+P_1+P_1P_2+\dots+P_1\dots P_{N-1}}{P_1P_2\dots P_N}
\]

对于多个检查点的情况,可以发现,检查点分开的两段可以单独处理
即存在 x 号节点是检查点时,可以分别计算 \(E_{1,x-1}\) 和 \(E_{x,N}\) 然后求和就是总答案。
更一般的,有:
\[
E_{u,v} = \frac{A_{u,v}}{B_{u,v}} = \frac{1+P_u+P_uP_{u+1}+\dots+P_u\dots P_{v-1}}{P_uP_{u+1}\dots P_v}
\]
分别计算检查点隔开的每一段即可。
在 a, c 之间添加新的检查点 b 时,删除 (a, c-1),增加 (a, b-1), (b, c-1)。
删除时相反。
可以用 set 直接维护检查点。

代码

/*
* Codeforces Round #604
* Beautiful Mirrors with queries
*/
#include <cstdio>
#include <set>
using namespace std;
#define Nmax 200010
typedef long long LL;
LL MD = 998244353;
LL fpw(LL a, LL b) {
LL ans = 1;
while (b) {
if (b & 1)
ans = ans * a % MD;
a = a * a % MD;
b >>= 1;
}
return ans;
}
inline LL re(LL a) {
return fpw(a, MD - 2);
}
int N, Q;
LL P[Nmax];
LL mt[Nmax];
LL sm[Nmax];
LL ans;
set<int> st;
LL getans(int a, int b) {
LL p;
p = (((sm[b-2]-sm[a-1])%MD+MD)%MD
* re(mt[a-1]) %MD + 1)%MD
* re(mt[b-1]*re(mt[a-1])%MD) %MD;
return p;
}
void init() {
LL hdr;
hdr = re(100);
mt[0] = 1;
sm[0] = 0;
for (int i = 1; i <= N; i++) {
P[i] = P[i] * hdr % MD;
mt[i] = mt[i-1] * P[i] % MD;
sm[i] = sm[i-1] + mt[i] % MD;
}
st.insert(1);
st.insert(N+1);
ans = (sm[N-1]+1)%MD * re(mt[N]) % MD;
}
int main() {
scanf("%d %d", &N, &Q);
for (int i = 1; i <= N; i++) {
scanf("%lld", P+i);
}
init();
int x, y, z;
set<int>::iterator it;
for (int i = 0; i < Q; i++) {
scanf("%d", &x);
if (st.count(x)) {
it = st.find(x);
it--;
y = *it;
it++;
it++;
z = *it;
st.erase(x);
ans = ((ans-getans(y, x))%MD+MD)%MD;
ans = ((ans-getans(x, z))%MD+MD)%MD;
ans = (ans+getans(y, z))%MD;
}
else {
st.insert(x);
it = st.find(x);
it--;
y = *it;
it++;
it++;
z = *it;
ans = ((ans-getans(y, z))%MD+MD)%MD;
ans = (ans+getans(y, x))%MD;
ans = (ans+getans(x, z))%MD;
}
printf("%lld\n", ans);
}
return 0;
}
05-11 18:29