Educational Codeforces Round 140 (Rated for Div. 2)
这场状态还可以,但是 C C C 没出来属实是 n t nt nt 了,明明是平常练过的区间 d p dp dp
C. Count Binary Strings
Problem Summary
题目大意如下:你需要计算一个长度为 n n n 的 01 01 01 串 s s s, 1 ⩽ n ⩽ 100 1 \leqslant n \leqslant 100 1⩽n⩽100,有多少满足题目给定的对于每个区间的 a i , j a_{i, j} ai,j 的要求。
a i , j a_{i, j} ai,j 的定义 :
-
若 a i , j = 1 a_{i, j} = 1 ai,j=1,那么 s [ i . . . j ] s[i...j] s[i...j] 的元素都应该相同。
-
若 a i , j = 2 a_{i, j} = 2 ai,j=2, 那么 s [ i . . . j ] s[i...j] s[i...j] 这段区间至少要有两个不同元素。
-
若 a i , j = 0 a_{i, j} = 0 ai,j=0,那么 s [ i . . . j ] s[i...j] s[i...j] 这段区间没有任何要求。
Solution
这是一道比较典的区间 d p dp dp 问题,状态定义如下:
f ( u , i , j ) = { u = 0 = > s i = 0 , s [ j + 1... i ] = 0 , s [ j ] = 1 u = 1 = > s i = 1 , s [ j + 1... i ] = 1 , s [ j ] = 0 f(u, i, j) = \left\{ \begin{aligned} u = 0 \space => \space s_i = 0, \space s[j + 1...i] = 0, \space s[j] = 1 \\ u = 1 \space => \space s_i = 1, \space s[j + 1...i] = 1, \space s[j] = 0 \end{aligned} \right. f(u,i,j)={u=0 => si=0, s[j+1...i]=0, s[j]=1u=1 => si=1, s[j+1...i]=1, s[j]=0
即枚举在 i i i 之前的断点,满足 [ j + 1 , i ] [j + 1, i] [j+1,i] 这个区间内的元素都相同, j j j 与 i i i 不同。
于是就有状态转移方程(这里采用刷表法):
f ( 0 , i + 1 , j ) + = f ( 0 , i , j ) f(0, i + 1, j) \space += f(0, i, j) f(0,i+1,j) +=f(0,i,j)
f ( 1 , i + 1 , j ) + = f ( 1 , i , j ) f(1, i + 1, j) \space += f(1, i, j) f(1,i+1,j) +=f(1,i,j)
f ( 0 , i + 1 , i ) + = f ( 1 , i , j ) f(0, i + 1, i) \space += f(1, i, j) f(0,i+1,i) +=f(1,i,j)
f ( 1 , i + 1 , i ) + = f ( 0 , i , j ) f(1, i + 1, i) \space += f(0, i, j) f(1,i+1,i) +=f(0,i,j)
前两个是相等区间 [ j + 1 , i ] [j + 1, i] [j+1,i] 接上一个元素相同的 i + 1 i + 1 i+1,后两个是接上一个不同的 i + 1 i + 1 i+1。
所以这里需要 O ( n 3 ) O(n^3) O(n3),第三维是枚举断点是否合法,详见代码。
Code
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
#include <cmath>
#include <unordered_set>
#include <set>
#include <unordered_map>
#include <map>
#include <stack>
#include <assert.h>
#define endl '\n'
#define x first
#define y second
#define ls u << 1
#define rs u << 1 | 1
#define l(x) tr[x].s[0]
#define r(x) tr[x].s[1]
#define pb push_back
#define ppb pop_back()
#define all(x) x.begin(), x.end()
#define debug(x) cout << (#x) << ' ' << x << endl
#define fup(i, a, b) for (int i = a; i <= b; i ++ )
#define fdn(i, a, b) for (int i = a; i >= b; i -- )
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef double db;
typedef long double LD;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
typedef pair<double, double> PDD;
typedef pair<string, int> PSI;
typedef pair<char, int> PCI;
const int N = 110, M = N << 1, MOD = 998244353, INF = 0x3f3f3f3f;
const db eps = 1e-8;
const db PI = acos(-1);
const int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int n, m;
int a[N][N];
LL dp[2][N][N];
LL f[N], g[N];
LL qmi(LL a, LL b)
{
LL res = 1;
while (b)
{
if (b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
// 下面俩函数是组合数的,没啥用,可以删了,比赛的时候想歪掉了
void init()
{
f[0] = g[0] = g[1] = 1;
fup(i, 2, n) g[i] = (MOD - MOD / i) * g[MOD % i] % MOD;
fup(i, 1, n) f[i] = f[i - 1] * i % MOD, g[i] = g[i - 1] * g[i] % MOD;
}
LL C(int n, int m)
{
if (n < m || m < 0) return 0;
return f[n] * g[m] % MOD * g[n - m] % MOD;
}
void add(LL &a, LL b)
{
a = (a + b) % MOD;
}
void solve()
{
cin >> n;
fup(i, 1, n) fup(j, i, n) cin >> a[i][j];
if (a[1][1] != 2) dp[0][1][0] = dp[1][1][0] = 1; // 边界条件设置断点为 0
LL res = 0;
fup(i, 1, n) fup(j, 0, i - 1)
{
bool ok = true;
fup(k, 1, i)
{
if (a[k][i] == 1) // 这里的要求是 k 到 i 都应该相同
{
if (j >= k) // 但是如果断点打到了 k 及其后面就不合法
{
ok = false;
break;
}
}
else if (a[k][i] == 2) // 这里的要求是 k 到 i 这段区间至少有两个不同元素
{
if (j < k) // 但是如果断点打到了 k 之前也不合法
{
ok = false;
break;
}
}
}
if (!ok) continue;
add(dp[0][i + 1][j], dp[0][i][j]);
add(dp[1][i + 1][i], dp[0][i][j]);
add(dp[1][i + 1][j], dp[1][i][j]);
add(dp[0][i + 1][i], dp[1][i][j]);
if (i == n)
{
add(res, dp[0][i][j]);
add(res, dp[1][i][j]);
}
}
cout << res << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T -- ) solve();
return 0;
}
D. Playoff
Problem Summary
给定一个 [ 1 , 2 , 3 , 4 , . . . , 2 n − 1 , . . . , 2 n ] [1,2,3,4,...,2^{n - 1},...,2^n] [1,2,3,4,...,2n−1,...,2n] 的排列,表示每个人(下标从 1 1 1 到 2 n 2^n 2n)的技巧值。现在让他们按顺序两两之间比赛,即下标 1 1 1 的和下标 2 2 2 的打,下标 3 3 3 和下标 4 4 4 的打,以此类推,一共进行 2 n − 1 2^{n - 1} 2n−1 场比赛,决出 2 n − 1 2^{n - 1} 2n−1 个胜者,这下第一轮完成。第二轮如法炮制,决出 2 n − 2 2^{n - 2} 2n−2 个胜者进入下一轮。比赛一共进行 n n n 轮,直到最后剩一名胜者。第 i i i 轮有 2 n − i 2^{n - i} 2n−i 场比赛。
对一个 01 01 01 串 s s s,长度为 n n n( 1 ⩽ n ⩽ 18 1 \leqslant n \leqslant 18 1⩽n⩽18), s [ i ] = 0 s[i] = 0 s[i]=0 表示第 i i i 轮的比赛中,技巧值小的胜出, s [ i ] = 1 s[i] = 1 s[i]=1 表示第 i i i 轮的比赛中,技巧值大的胜出。
现给定一个 01 01 01 串 s s s,输出所有排列中所有可以获得最终胜利的技巧值。
Solution
这是个思维题,我们需要很快从样例猜出最后的结果是连续的一段区间。
首先,一个技巧值 x x x 可以胜利,就表示它需要经过整个 01 01 01 串的筛选,也就是经过 s [ i ] = 1 s[i] = 1 s[i]=1 的时候,它需要是较大的那个, s [ i ] = 0 s[i] = 0 s[i]=0 的时候,它需要是较小的那个。因此我们可以统计整个串有多少个 1 1 1 和 0 0 0,设 1 1 1 的出现次数为 c n t cnt cnt,则 0 0 0 的出现次数为 n − c n t n - cnt n−cnt。当 x x x 第一次遇到 1 1 1,就说明 x x x 需要遇到一个比它小的数 y y y,第二次遇到 1 1 1,不光意味着 x x x 需要遇到一个比它小的数,这个比 x x x 小的数 y y y 也要经过第一次遇到的 1 1 1 赢下来以后再和 x x x 进行对局,所以此时小于 x x x 的数就从 1 1 1 个变成了 1 + 2 1 + 2 1+2 个,以此类推, x x x 的左边至少要有 1 + 2 + . . . + 2 c n t − 1 1 + 2 + ... + 2^{cnt - 1} 1+2+...+2cnt−1 个数,才能保证 x x x 能赢到最后,也就是 2 c n t − 1 2^{cnt} - 1 2cnt−1 个数,所以 x > 2 c n t − 1 x \gt 2^{cnt} - 1 x>2cnt−1,即 x ⩾ 1 + ( 2 c n t − 1 ) x \geqslant 1 + (2^{cnt} - 1) x⩾1+(2cnt−1) 。对 0 0 0 也同理,它需要让大于 x x x 的数有 2 n − c n t − 1 2^{n - cnt} - 1 2n−cnt−1 个,所以有 x ⩽ 2 n − ( 2 n − c n t − 1 ) x \leqslant 2^n - (2^{n - cnt} - 1) x⩽2n−(2n−cnt−1)。
接下来证明在上述左右区间内的任意一个技巧值都能构造出一种方案赢。首先左端点 l = 2 c n t l = 2^{cnt} l=2cnt 一定 ⩽ 2 n − 2 n − c n t + 1 \leqslant 2^n - 2^{n - cnt} + 1 ⩽2n−2n−cnt+1,这个化简如下: 移项并提取公因式: ( 2 c n t − 1 ) × ( 2 n − c n t − 1 ) ⩾ 0 移项并提取公因式:(2^{cnt} - 1) \times (2^{n - cnt} - 1) \geqslant 0 移项并提取公因式:(2cnt−1)×(2n−cnt−1)⩾0 由 c n t ⩾ 0 , n ⩾ c n t ,显然得证 由 \space cnt \geqslant 0, \space n \geqslant cnt,显然得证 由 cnt⩾0, n⩾cnt,显然得证 不妨设在 [ l , r ] [l, r] [l,r] 内有一个技巧值 y y y 不能由任意一种排列使得它最后获胜,那么显然它一定不能挺过某一个 1 1 1 或者 0 0 0,这也就意味着在遇到 1 1 1 时,已经不存在任何一个数能比他小了,与他匹配的只能是比它大的数,但这显然与这个区间的位置不符,因为 y ⩾ 2 c n t y \geqslant 2^{cnt} y⩾2cnt,所以无论怎样,在 n n n 层的排布下,其中有 1 1 1 的那些层显然可以每一层都安排一个比他小的数,矛盾,对 0 0 0 同理。
综上,只要先把左右端点先设置为 l = 1 l = 1 l=1 和 r = 2 n r = 2^n r=2n,然后计算出 1 1 1 的个数,利用上述公式即可。
Code
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
#include <cmath>
#include <unordered_set>
#include <set>
#include <unordered_map>
#include <map>
#include <stack>
#include <assert.h>
#define endl '\n'
#define x first
#define y second
#define ls u << 1
#define rs u << 1 | 1
#define l(x) tr[x].s[0]
#define r(x) tr[x].s[1]
#define pb push_back
#define ppb pop_back()
#define all(x) x.begin(), x.end()
#define debug(x) cout << (#x) << ' ' << x << endl
#define fup(i, a, b) for (int i = a; i <= b; i ++ )
#define fdn(i, a, b) for (int i = a; i >= b; i -- )
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef double db;
typedef long double LD;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
typedef pair<double, double> PDD;
typedef pair<string, int> PSI;
typedef pair<char, int> PCI;
const int N = 100010, M = N << 1, MOD = 1e9 + 7, INF = 0x3f3f3f3f;
const db eps = 1e-8;
const db PI = acos(-1);
const int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int n, m;
string s;
void solve()
{
cin >> n >> s;
int l = 1, r = 1 << n;
int cnt = 0;
for (auto c: s) cnt += c - '0';
l += (1 << cnt) - 1;
r -= (1 << n - cnt) - 1;
fup(i, l, r) cout << i << ' ';
cout << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T -- ) solve();
return 0;
}