Dashboard - Codeforces Round 1025 (Div. 2) - Codeforces
A 首先,$n-1$ 场比赛不可能出现 $n$ 个胜场,所以必然会有 0;另外,如果中间人次出现 0,那么他两侧数字必须为 1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void solve () { int n; cin >> n; std::vector<int > a (n) ; for (int i = 0 ; i < n; i++) { cin >> a[i]; } if (std::count (a.begin (), a.end (), 1 ) == n) { cout << "YES\n" ; return ; } for (int i = 0 ; i < n; i++) { if (a[i] == 0 ) { if (i > 0 && a[i - 1 ] == 0 || i + 1 < n && a[i + 1 ] == 0 ) { cout << "YES\n" ; return ; } } } cout << "NO\n" ; }
B 按照贪心的策略,唯一不确定的就是开始时刻是横切还是纵切,可以选择收益最大的,也可以两种情况均尝试一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 void solve () { int n, m, a, b; cin >> n >> m >> a >> b; int ans = INF; { int n1 = std::min (a, n - a + 1 ), m1 = m; int cnt = 0 ; while (n1 > 1 ) { if (n1 & 1 ) n1++; n1 /= 2 ; cnt++; } while (m1 > 1 ) { if (m1 & 1 ) m1++; m1 /= 2 ; cnt++; } ans = std::min (ans, cnt); } { int n1 = n, m1 = std::min (b, m - b + 1 ); int cnt = 0 ; while (n1 > 1 ) { if (n1 & 1 ) n1++; n1 /= 2 ; cnt++; } while (m1 > 1 ) { if (m1 & 1 ) m1++; m1 /= 2 ; cnt++; } ans = std::min (ans, cnt); } cout << ans + 1 << "\n" ; }
C1 限制在 7 步以内:
取一次digit操作,x 落在区间 1 $\sim$ 72,再取一次digit操作,x 落在区间 1 $\sim$ 15,执行add操作减到 1 为止,此时mul(n),得到数字 n。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 bool add (i64 y) { cout << "add " << y << std::endl; int t; cin >> t; return t == 1 ; } bool mul (i64 y) { cout << "mul " << y << std::endl; int t; cin >> t; return t == 1 ; } bool div (i64 y) { cout << "div " << y << std::endl; int t; cin >> t; return t == 1 ; } void digit () { cout << "digit" << std::endl; int t; cin >> t; } void solve () { int n; cin >> n; digit (); digit (); add (-8 ); add (-4 ); add (-2 ); add (-1 ); mul (n); cout << "!" << std::endl; int t; cin >> t; }
C2 限制在4步以内:
十进制下能被9整除的数字其各数位之和能被9整除
先将 x 执行mul(9),以保证其能被 9 整除,并且范围在 1 $\sim$ 999999999 之间,取digit操作,范围落在 1 $\sim$ 81 上,再取一次digit操作,范围落在 1 $\sim$ 16 上,此段能被 9 整除的数字只有 9,因此此时 x 即为 9,操作 add(n-9),得到 n。
1 2 3 4 5 6 7 8 9 10 11 void solve () { int n; cin >> n; mul (9 ); digit (); digit (); add (n - 9 ); cout << "!" << std::endl; int t; cin >> t; }
C3 对每个数操作的次数保证最小:注意到 ,对每个数字mul(1e9-1)再digit后值都是 81,因此再add(n-81)即可,对于 $n=81$ 的情况只需要前两次操作。
1 2 3 4 5 6 7 8 9 10 11 12 void solve () { int n; cin >> n; mul (1e9 - 1 ); digit (); if (n != 81 ) { add (n - 81 ); } cout << "!" << std::endl; int t; cin >> t; }
D 分层图最短路的好题:
从 0 点出发,一些点可以走偶数步到达,一些点可以走奇数步到达,用 $dis[i][j],i\in{0,1,2,…,n-1},j\in{0,1}$ 来表示从 0 点出发,经过偶数0(奇数1)的路径长度,能够到达 $i$ 的最短路。
若存在 $u\rightarrow v$,则状态转移方程: $$ dp[v][0]=min(dp[v][0],dp[u][1]+1)\ dp[v][1]=min(dp[v][1],dp[u][0]+1) $$ 由于向下一层节点转移的过程中固定+1,因此可使用bfs 进行递推。再根据能够走的所有长度,统计出能够走的最大偶数长度和最大奇数长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 void solve () { int n, m, l; cin >> n >> m >> l; std::vector<int > a (l) ; for (int &x : a) { cin >> x; } std::vector<std::vector<int >> g (n); std::vector<std::vector<int >> dis (n, std::vector <int >(2 , INF)); std::vector<std::vector<bool >> vis (n, std::vector <bool >(2 )); for (int i = 0 ; i < m; i++) { int u, v; cin >> u >> v; u--; v--; g[u].push_back (v); g[v].push_back (u); } std::queue<PII> q; dis[0 ][0 ] = 0 ; vis[0 ][0 ] = true ; q.push ({0 , 0 }); while (!q.empty ()) { auto [u, p] = q.front (); q.pop (); int t = p ^ 1 ; for (int v : g[u]) { if (!vis[v][t]) { vis[v][t] = true ; dis[v][t] = dis[u][p] + 1 ; q.push ({v, t}); } } } int cntodd = 0 , minodd = INF; i64 sumeven = 0 , sumodd = 0 ; for (int x : a) { if (x & 1 ) { cntodd++; sumodd += x; minodd = std::min (minodd, x); } else { sumeven += x; } } if (cntodd & 1 ) { i64 t1 = sumeven + sumodd - minodd, t2 = sumodd + sumeven; sumeven = t1; sumodd = t2; } else { i64 t1 = sumeven + sumodd, t2 = cntodd == 0 ? -1 : sumodd + sumeven - minodd; sumeven = t1; sumodd = t2; } for (int i = 0 ; i < n; i++) { bool ok = false ; for (int j = 0 ; j < 2 ; j++) { if (dis[i][j] != INF && (j == 0 ? sumeven : sumodd) >= dis[i][j]) { ok = true ; break ; } } cout << (ok ? '1' : '0' ); } cout << "\n" ; }
E 发现如果选择 $i$ 位置,不会对 $i+1$ 及其之后的位置产生影响,因此选择从后往前dp求解。
设 $dp[i][j]$ 为对于 $i,i+1,i+2,…,n-1$ 位置,应用了 $j$ 次操作,能得到的操作序列总数。
如果 $i$ 位置初始为 0,则肯定对 $i$ 位置的操作分布在总操作序列的奇数位置上 $1,3,5,…,$,不一定连续,因为 $i$ 位置操作后变为 1,必须等到 $i+1\sim n-1$ 位置操作奇数次之后,才能重新对 $i$ 位置操作,因此两次对 $i$ 的操作在序列中的位置奇偶性相同;$i$ 初始位置为 1 时同理。
设当前总操作次数为 $j+c\le k$,其中对当前位置操作次数为 $c$ 次,对 $i+1\sim n-1$ 的操作次数为 $j$ 次,则总的操作序列长度为 $j+c$。若 $s[i]$ 初始为 0,则有 $\lceil\frac{j+c}{2}\rceil$ 个奇数位置,反之则有 $\lfloor\frac{j+c}{2}\rfloor$ 个偶数位置,可简单表示为 $(j+c+(s[i]==0))/2$,取其中 $c$ 个位置,为 $\binom{(j+c+(s[i]==0))/2}{c}$ 个选择。 $$ dp[i][j+c]\leftarrow dp[i][j+c]+dp[i+1][j]\cdot\binom{(j+c+(s[i]==0))/2}{c} $$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void solve () { int n, k; cin >> n >> k; string s; cin >> s; std::vector<std::vector<mint>> dp (n, std::vector <mint>(k + 1 )); dp[n - 1 ][0 ] = 1 ; dp[n - 1 ][1 ] = s.back () == '0' ; for (int i = n - 2 ; i >= 0 ; i--) { for (int j = 0 ; j <= k; j++) { for (int t = 0 ; t + j <= k; t++) { dp[i][t + j] += dp[i + 1 ][j] * comb.binom ((t + j + (s[i] == '0' )) / 2 , t); } } } cout << dp[0 ][k] << "\n" ; }