【Educational Codeforces Round 140 (Rated for Div. 2) C D 题解】

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 1n100,有多少满足题目给定的对于每个区间的 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,...,2n1,...,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} 2n1 场比赛,决出 2 n − 1 2^{n - 1} 2n1 个胜者,这下第一轮完成。第二轮如法炮制,决出 2 n − 2 2^{n - 2} 2n2 个胜者进入下一轮。比赛一共进行 n n n 轮,直到最后剩一名胜者。第 i i i 轮有 2 n − i 2^{n - i} 2ni 场比赛。

对一个 01 01 01 s s s,长度为 n n n 1 ⩽ n ⩽ 18 1 \leqslant n \leqslant 18 1n18), 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 ncnt。当 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+...+2cnt1 个数,才能保证 x x x 能赢到最后,也就是 2 c n t − 1 2^{cnt} - 1 2cnt1 个数,所以 x > 2 c n t − 1 x \gt 2^{cnt} - 1 x>2cnt1,即 x ⩾ 1 + ( 2 c n t − 1 ) x \geqslant 1 + (2^{cnt} - 1) x1+(2cnt1) 。对 0 0 0 也同理,它需要让大于 x x x 的数有 2 n − c n t − 1 2^{n - cnt} - 1 2ncnt1 个,所以有 x ⩽ 2 n − ( 2 n − c n t − 1 ) x \leqslant 2^n - (2^{n - cnt} - 1) x2n(2ncnt1)

接下来证明在上述左右区间内的任意一个技巧值都能构造出一种方案赢。首先左端点 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 2n2ncnt+1,这个化简如下: 移项并提取公因式: ( 2 c n t − 1 ) × ( 2 n − c n t − 1 ) ⩾ 0 移项并提取公因式:(2^{cnt} - 1) \times (2^{n - cnt} - 1) \geqslant 0 移项并提取公因式:(2cnt1)×(2ncnt1)0 由  c n t ⩾ 0 ,   n ⩾ c n t ,显然得证 由 \space cnt \geqslant 0, \space n \geqslant cnt,显然得证  cnt0, ncnt,显然得证 不妨设在 [ 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} y2cnt,所以无论怎样,在 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;
}
  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值