# Codeforces Round #369 (Div2) ABCDE

A: 暴力
B: 暴力
C: dp,可以暴力,也可以不暴力
D: 拓扑排序，dfs,方案计数
E: 数学，有意思

### A. Bus to Udayland

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAX_N = 1010;

int n;
char s[MAX_N][10];

int main()
{
while (~scanf("%d", &n)) {
int flag = 0;
for(int i = 0; i < n; ++i) {
scanf("%s", s[i]);
if (flag) continue;
if (s[i][0] == 'O' && s[i][1] == 'O') {
s[i][0] = s[i][1] = '+';
flag = 1;
} else if (s[i][3] == s[i][4] && s[i][3] == 'O') {
s[i][3] = s[i][4] = '+';
flag = 1;
}
}
if (flag == 0) printf("NO\n");
else {
printf("YES\n");
for (int i = 0; i < n; ++i) {
printf("%s\n", s[i]);
}
}
}
return 0;
}

### B. Chris and Magic Square

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <climits>
#include <cmath>
#include <ctime>
#include <cassert>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
const int MAX_N = 510;

int n;
ll mat[MAX_N][MAX_N], r[MAX_N], c[MAX_N], a, b, x, y;

void solve()
{
if (n == 1) {
printf("1\n");
return ;
}
ll ans;
if (x == n) ans = r[n - 1] - r[n];
else ans = r[x + 1] - r[x];
c[y] += ans;
r[x] += ans;
if (x == y) a += ans;
if (x == n - y + 1) b += ans;
int flag = 1;
if (a != b || ans < 0) flag = 0;
for (int i = 1; i <= n; ++i) {
if (a != r[i] || a != c[i]) {
flag = 0;
break;
}
}
if (flag && ans > 0) printf("%I64d\n", ans);
else printf("-1\n");
}

int main()
{
while (~scanf("%d", &n)) {
memset(mat, 0, sizeof(mat));
memset(r, 0, sizeof(r));
memset(c, 0, sizeof(c));
a = b = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
scanf("%I64d", &mat[i][j]);
if (i == j) a += mat[i][j];
if (i == n - j + 1) b += mat[i][j];
r[i] += mat[i][j];
c[j] += mat[i][j];
if (mat[i][j] == 0) {
x = i, y = j;
}
}
}
solve();
}
return 0;
}

### C. Coloring Trees

O(nmK)$O(n*m*K)$dp我是用dp[i][j]$dp[i][j]$表示将前i$i$棵树分成j$j$段的最小代价，还要记录一下最小代价时第i$i$个树的颜色id[i][j]$id[i][j]$和次小代价dpp[i][j]$dpp[i][j]$。然后状态转移。需要记录每棵树颜色为t$t$时分成j$j$段的最小代价：vis[i][t][j]$vis[i][t][j]$

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <climits>
#include <cmath>
#include <ctime>
#include <cassert>
#include <set>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
const int MAX_N = 110;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fll;

int n, m, K;
int color[MAX_N], id[MAX_N][MAX_N];
ll dp[MAX_N][MAX_N], dpp[MAX_N][MAX_N], w[MAX_N][MAX_N];
ll vis[MAX_N][MAX_N][MAX_N];

void update(int i, int j, int t, ll tmp)
{
if (tmp + w[i][t] < dp[i][j]) {
dpp[i][j] = dp[i][j];
dp[i][j] = tmp + w[i][t];
id[i][j] = t;
}  else if (tmp + w[i][t] < dpp[i][j]) {
dpp[i][j] = tmp + w[i][t];
}
}

void solve()
{
memset(dp, 0x3f, sizeof(dp));
memset(vis, 0x3f, sizeof(vis));
memset(id, 0, sizeof(id));
if (color[1]) {
dp[1][1] = 0, id[1][1] = color[1];
vis[1][color[1]][1] = 0;
} else {
for (int t = 1; t <= m; ++t) {
vis[1][t][1] = w[1][t];
update(1, 1, t, 0);
}
}
ll tmp;
for (int i = 2; i <= n; ++i) {
if (color[i]) { // 当前颜色确定
for (int j = 1; j <= min(K, i); ++j) {
if (color[i - 1]) {
if (color[i] == color[i - 1]) dp[i][j] = dp[i - 1][j];
else dp[i][j] = dp[i - 1][j - 1];
vis[i][color[i]][j] = dp[i][j];
} else {
if (id[i - 1][j - 1] == color[i]) tmp = dpp[i - 1][j - 1];
else tmp = dp[i - 1][j - 1];
if (id[i - 1][j] == color[i]) tmp = min(tmp, dp[i - 1][j]);
tmp = min(tmp, vis[i - 1][color[i]][j]); // 这个判断很重要!
vis[i][color[i]][j] = tmp;
update(i, j, 0, tmp);
}
id[i][j] = color[i];
}
} else { // 当前颜色不确定
for (int j = 1; j <= min(K, i); ++j) {
if (color[i - 1]) { // 前一颗树颜色确定
for (int t = 1; t <= m; ++t) {
if (t == color[i - 1]) {
tmp = dp[i - 1][j];
} else {
if (id[i - 1][j - 1] == t) tmp = dpp[i - 1][j - 1];
else tmp = dp[i - 1][j - 1];
if (id[i - 1][j] == t) tmp = min(tmp, dp[i - 1][j]);
}
tmp = min(tmp, vis[i - 1][t][j]); // 这个判断很重要!
vis[i][t][j] = tmp + w[i][t];
update(i, j, t, tmp);
}
} else { // 前一棵树颜色不确定
for (int t = 1; t <= m; ++t) { // 枚举当前树的颜色
if (id[i - 1][j - 1] == t) tmp = dpp[i - 1][j - 1];
else tmp = dp[i - 1][j - 1];
if (id[i - 1][j] == t) tmp = min(tmp, dp[i - 1][j]);
tmp = min(tmp, vis[i - 1][t][j]); // 这个判断很重要!
vis[i][t][j] = tmp + w[i][t];
update(i, j, t, tmp);
}
}
}
}
}
if (dp[n][K] == INF) dp[n][K] = -1;
printf("%I64d\n", dp[n][K]);
}

int main()
{
while (~scanf("%d%d%d", &n, &m, &K)) {
int pre = 0, cnt = 0;
for (int i = 1; i <= n; ++i) {
scanf("%d", &color[i]);
if (color[i] && color[i] != pre) {
cnt ++;
}
pre = color[i];
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
scanf("%I64d", &w[i][j]);
}
}
if (K > n || cnt > K) printf("-1\n");
else solve();
}
return 0;
}

O(nm2K)$O(n*m^{2}*K)$dp就没什么好讲的了。不过这种dp跑了300+ms，而上面的只有30+ms。

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
using namespace std;
typedef long long ll;
const int MAX_N = 110;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fll;

int n, m, K;
int color[MAX_N];
ll w[MAX_N][MAX_N], dp[MAX_N][MAX_N][MAX_N];

void solve()
{
memset(dp, 0x3f, sizeof(dp));
if (color[1]) dp[1][color[1]][1] = 0;
else {
for (int j = 1; j <= m; ++j) {
dp[1][j][1] = w[1][j];
}
}
for (int i = 2; i <= n; ++i) {
for (int j = 1; j <= min(j, K); ++j) {
if (color[i]) {
for (int p = 1; p <= m; ++p) {
if (p == color[i]) dp[i][color[i]][j] = min(dp[i][color[i]][j], dp[i - 1][p][j]);
else dp[i][color[i]][j] = min(dp[i][color[i]][j], dp[i - 1][p][j - 1]);
}
} else {
for (int t = 1; t <= m; ++t) {
for (int p = 1; p <= m; ++p) {
if (p == t) dp[i][t][j] = min(dp[i][t][j], dp[i - 1][p][j] + w[i][t]);
else dp[i][t][j] = min(dp[i][t][j], dp[i - 1][p][j - 1] + w[i][t]);
}
}
}
}
}
ll ret = INF;
for (int i = 1; i <= m; ++i) {
ret = min(ret, dp[n][i][K]);
}
if (ret == INF) ret = -1;
printf("%I64d\n", ret);
}

int main()
{
while (~scanf("%d%d%d", &n, &m, &K)) {
for (int i = 1; i <= n; ++i) {
scanf("%d", &color[i]);
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
scanf("%I64d", &w[i][j]);
}
}
solve();
}
return 0;
}

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
const int MAX_N = 200010;
const ll mod = 1e9 + 7;

int n;
int to[MAX_N], degree[MAX_N], vis[MAX_N];
ll pw2[MAX_N];
queue<int> que;

void init()
{
pw2[0] = 1;
for (int i = 1; i < MAX_N; ++i) {
pw2[i] = pw2[i - 1] * 2 % mod;
}
}

int TopoSort()
{
int ret = 0;
while (!que.empty()) que.pop();
for (int i = 1; i <= n; ++i) {
if (degree[i] == 0) que.push(i);
}
while (!que.empty()) {
int cur = que.front();
que.pop();
ret++;
if (--degree[to[cur]] == 0) que.push(to[cur]);
to[cur] = 0;
}
//  printf("TopoSort = %d\n", ret);
return ret;
}

void dfs(int u, int& depth)
{
if (to[u] == 0) return;
depth++;
int v= to[u];
to[u] = 0;
dfs(v, depth);
}

void wyr()
{
ll ret = pw2[TopoSort()];
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= n; ++i) {
if (to[i]) {
int d = 0;
dfs(i, d);
//  printf("i = %d d = %d\n", i, d);
ret = ret * (pw2[d] - 2) % mod;
}
}
printf("%I64d\n", ret);
}

int main()
{
init();
while (~scanf("%d", &n)) {
memset(degree, 0, sizeof (degree));
for (int i = 1; i <= n; ++i) {
scanf("%d", &to[i]);
degree[to[i]]++;
}
wyr();
}
return 0;
}

### E. ZS and The Birthday Paradox

2n$2^n$天(日期)和k$k$个人，求至少有两个人的生日在同一天的概率？用分数表示。

• 你要想到把这个式子简化一下：(2n1)(2n2)(2n(k1))2n(k1)$\frac{(2^n-1)* (2^n-2)* \cdots * (2^n-(k-1))}{2^{n* (k-1)}}$
注意到分母只是2的幂，那么分子和分母的最大公约数也只能是2的幂。
• 你要想到当2nt(t<2n)$2^n-t(t<2^n)$时找到最大的p$p$使得2p|(2nt)$2^p|(2^n-t)$等价于找到最大的p$p$使得2p|t$2^p|t$

• 你要想到求分子的最大的2的幂次的因子相当于求(k1)!$(k-1)!$的最大的2的幂次的因子。
• 你要想到上面一步是等于：i=1k12i$\sum_{i=1}^{}{\frac{k-1}{2^i}}$
• 你要想到求出来分子分母的最大公约数后还要用到逆元（因为有模数）。
• 你要想到当k2n$k\geq 2^n$的时候答案恒为1/1。
• 你要想到因为分子是连续的一串数相乘，那么分别对mod取余后结果肯定也是连续的。当k1mod$k-1\geq mod$时，取余的结果肯定会出现0，所以分子肯定是0.当k1mod$k-1\leq mod$时，可以递推暴力算。因为mod只有1e6+3，而且是个素数。
• 你要想到分母的幂是n(k1)$n*(k-1)$是会爆long long的，你可以这样：quick_pow(quick_pow(2, n), k - 1)
或者根据费马小定理:
2mod1mod(mod)

对幂次进行降幂：(n%(mod1))((k1)%(mod1))%(mod1)$(n\% (mod - 1)) * ((k - 1) \% (mod - 1)) \% (mod - 1)$，这样子就不会爆了。

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
using namespace std;
typedef long long ll;
const ll mod = 1000003;

int num[mod + 10], sum[mod + 10];

ll quick_pow(ll a, ll b)
{
ll ret = 1, tmp = a;
while (b) {
if (b & 1) ret = ret * tmp % mod;
tmp = tmp * tmp % mod;
b >>= 1;
}
return ret;
}

ll work(ll x)
{
--x;
ll ret = 0, base = 2;
while (x / base) {
ret += (x / base);
base <<= 1;
}
return ret;
}

int main()
{
ll n, K;
while (~scanf("%I64d%I64d", &n, &K)) {
if (n <= 63 && K > (1LL << n)) {
printf("1 1\n");
continue;
}
ll base = quick_pow(2, n);
ll B = quick_pow(quick_pow(2, mod - 2), work(K)); // 逆元
ll A = 1; // numerator
if (K - 1 >= mod) A = 0;
else {
for (int i = 1; i <= K - 1; ++i) {
A = A * (base - i) % mod;
}
}
A = A * B % mod;
B = B * quick_pow(base, K - 1) % mod; // denominator
A = ((B - A) % mod + mod) % mod;
printf("%I64d %I64d\n", A, B);
}
return 0;
}

• 本文已收录于以下专栏：

举报原因： 您举报文章：Codeforces Round #369 (Div2) ABCDE 色情 政治 抄袭 广告 招聘 骂人 其他 (最多只允许输入30个字)