首页 > 其他分享 >CF1767C Count Binary Strings 题解

CF1767C Count Binary Strings 题解

时间:2023-09-20 10:14:18浏览次数:47  
标签:Count Binary le int 题解 sum texttt mx dp

CF1767C Count Binary Strings 题解

Foreword

感谢 @樱雪喵@swiftc 两位大佬的耐心指导。

洛谷

Codeforces

Description

有一个长度为 \(n\) 的 01 串 \(s\)(下标从 \(1\) 开始)和一些限制 \(a_{i,j}(1 \le i \le j \le n)\)。

\(a_{i,j}\) 的含义如下:

\(a_{i,j}=\) 含义
\(0\) 没有限制
\(1\) 对于所有的 \(i \le p \le q \le j\) 均有 \(s_p=s_q\)
\(2\) 存在 \(i \le p \le q \le j\) 使得 \(s_p \neq s_q\)

求可能的 \(s\) 的个数。答案对 \(998244353\) 取模。

对于 \(100\%\) 的数据,\(2 \le n \le 100\),\(0 \le a_{i,j} \le 2\)。

Solution

这种计数类问题大概率是组合数学或者 dp,然而这道题限制条件很多,组合数学大概是做不成了,那么考虑 dp。

首先一个很显然的情况是如果 \(a_{i,i} = 2\) 那么无解。接下来考虑对 \(a_{i,j} = 1\) 的限制,我们把这些需要区间内全相同的合并成一个块,对块内每个位置的限制等价于对整个块的限制,使用并查集维护即可,注意每个块合并的时候都合并到最前面的位置。

int fa[222];
void init()
{
    for (int i = 1; i <= n; i++)
        fa[i] = i;
}
int find(int u)
{
    if (fa[u] == u)
        return fa[u];
    else
        return fa[u] = find(fa[u]);
}
void merge(int u, int v)
{
    if (find(u) == find(v))
        return;
    if (find(u) < find(v))
        fa[find(v)] = find(u);
    else
        fa[find(u)] = find(v);
}

主函数中:

init();
for (int i = 1; i <= n; i++)
{
    for (int j = i; j <= n; j++)
    {
        read(nums[i][j]);
        if (nums[i][j] == 1)
        {
            for (int k = i + 1; k <= j; k++)
            {
                merge(i, k);
            }
        }
    }
}

之后,我们很容易想到,对于每个块 \(p\),我们找到对于 \(i \in p\),\(a_{i,j} = 2\) 这样的限制中最大的 \(j\),只要满足这条限制,在前面的限制也就都满足了,求出 \(mx_{i}\) 表示块 \(i\) 之前最后一个能满足从 \(mx_{i}\) 到 \(i\) 与 \(i\) 填的数全相同的块。

DP 的思路有两种,一种是一维的,另一种是二维的。我开始写的一维,然而没有考虑到一些问题写挂了,尝试了二维通过之后又回到了一维。

这里先从二维 DP 开始讲。

考虑某个位置和前一位是否相同,有:

\[\begin{cases} dp_{i,j} = dp_{i - 1,j} & \texttt{if } s_{i} = s_{i - 1} \\ dp_{i,i} = \sum_{j = mx_{i}}^{i - 1} dp_{i - 1,j} & \texttt{if } s_{i} \neq s_{i - 1} \end{cases}\]

即如果要求和前一位相同,不会有新的贡献,否则累加贡献。

直接判断是否可行并转移即可。

dp[1][1] = 1;
for (int i = 2; i <= n; i++)
{
    if (find(i) == i)
    {
        for (int j = 1; j < i; j++)
        {
            (dp[i][i] += dp[i - 1][j]) %= 998244353;
        }
    }
    for (int j = mx[find(i)]; j < i; j++)
    {
        (dp[i][j] += dp[i - 1][j]) %= 998244353;
    }
}

int res = 0;
for (int i = mmx; i <= n; i++)
{
    (res += dp[n][i]) %= 998244353;
}
writeln(2 * res % 998244353);

接下来考虑一维 DP 怎么做。

\[\begin{cases} dp_{i,j} = dp_{i - 1,j} & \texttt{if } s_{i} = s_{i - 1} \\ dp_{i,i} = \sum_{j = mx_{i}}^{i - 1} dp_{i - 1,j} & \texttt{if } s_{i} \neq s_{i - 1} \end{cases}\]

上面的方程实际上就是 相同的地方 复制了 前面第一个不同的地方 /kk

定义 \(k\) 为 \(i\) 之前第一个不同的。有

\[dp_{i,i} = \sum_{k}\sum_{j = mx_{i}}^{i - 1} dp_{k,j} \ \texttt{ if } s_{i} \neq s_{i - 1} \]

也就是我们每次从 \(k\) 转移并且累加一下。

注意到由于 \(s_{k} \neq s_{k - 1}\),我们之前计算 \(dp_{k,j'}\) 的时候应该只更新了 \(dp_{k,k}\)。

因此实际上我们得到的是 \(dp_{i,i} = \sum_{k}dp_{k,k} \ \texttt{ if } mx_{i} \leq k \leq i - 1\)。

优化掉第二维,有 $dp_{i} = \sum_{k = mx_{i}}^{i - 1}dp_{k} $。

注意:这里有个问题,假设有按顺序 \(A,B,C,D\) 四个块,如果限制 \(B,C\) 不能相同,那么显然我们无法从 \(A\) 到 \(D\) 全部相同,因此我们 \(mx\) 还要取一个前缀 \(\operatorname{max}\)

由于我们合并块的编号并不连续,这不利于我们 dp,因此将块的编号离散化一下即可。

// 由于 fa 更新的时候不是 1,2,3,4 这样,而是每块第一个的编号
// 我们把 fa[i] 离散化,pos 表示是第几个,rea 表示第 i 个的实际 fa
for (int i = 1; i <= n; i++)
{
    if (find(i) == i)
    {
        rea[++rea[0]] = i;
        pos[i] = rea[0];
    }
}

之后枚举上一个不同的点转移就可以了,方程:

for (int i = 1; i <= n; i++)
{
    if (find(i) != i)
    {
        continue;
    }
    ++cnt;
    // 现在只有无限制和要求出现不同了
    // 我们可以枚举上一个不同的位置

    for (int j = pos[mx[rea[cnt]]]; j < cnt; j++)
    {
        (dp[cnt] += dp[j]) %= 998244353;
    }
}

把所有 \(0\),\(1\) 交换不会违反限制,因此答案要乘 \(2\)。

Codes

一维完整代码。

// Problem: C. Count Binary Strings
// Contest: Educational Codeforces Round 140 (Rated for Div. 2)
// URL: https://codeforces.com/contest/1767/problem/C
// Memory Limit: 512 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;
#define int long long
void read(int &p)
{
    p = 0;
    int k = 1;
    char c = getchar();
    while (c < '0' || c > '9')
    {
        if (c == '-')
        {
            k = -1;
        }
        c = getchar();
    }
    while (c >= '0' && c <= '9')
    {
        p = p * 10 + c - '0';
        c = getchar();
    }
    p *= k;
    return;
}
void write_(int x)
{
    if (x < 0)
    {
        putchar('-');
        x = -x;
    }
    if (x > 9)
    {
        write_(x / 10);
    }
    putchar(x % 10 + '0');
}
void writesp(int x)
{
    write_(x);
    putchar(' ');
}
void writeln(int x)
{
    write_(x);
    puts("");
}
int n, nums[200][200];
int fa[222];
void init()
{
    for (int i = 1; i <= n; i++)
    {
        fa[i] = i;
    }
}
int find(int u)
{
    if (fa[u] == u)
    {
        return fa[u];
    }
    else
    {
        return fa[u] = find(fa[u]);
    }
}
void merge(int u, int v)
{
    if (find(u) == find(v))
    {
        return;
    }
    if (find(u) < find(v))
    {
        fa[find(v)] = find(u);
    }
    else
    {
        fa[find(u)] = find(v);
    }
}
int dp[222];
int mx[222];
int rea[222];
int pos[222];
signed main()
{
    read(n);
    init();
    for (int i = 1; i <= n; i++)
    {
        for (int j = i; j <= n; j++)
        {
            read(nums[i][j]);
            if (nums[i][j] == 1)
            {
                for (int k = i + 1; k <= j; k++)
                {
                    merge(i, k);
                }
            }
        }
    }
    for (int i = 1; i <= n; i++)
    {
        if (nums[i][i] == 2)
        {
            puts("0");
            return 0;
        }
    }
    // 对于每一个需要找到最晚需要不同的,这样能满足前面的所有条件
    for (int i = 1; i <= n; i++)
    {
        mx[i] = 0;
    }
    for (int i = 2; i <= n; i++)
    {
        for (int j = i - 1; j; j--)
        {
            if (nums[j][i] == 2)
            {
                if (find(i) == find(j))
                {
                    puts("0");
                    exit(0);
                }
                mx[find(i)] = max(mx[find(i)], find(j));
                break;
            }
        }
    }
    for (int i = 1; i <= n; i++)
    {
        mx[i] = max(mx[i], mx[i - 1]);
    }

    int cnt = 0;
    dp[0] = 1;
    for (int i = 1; i <= n; i++)
    {
        if (find(i) == i)
        {
            rea[++rea[0]] = i;
            pos[i] = rea[0];
        }
    }

    for (int i = 1; i <= n; i++)
    {
        if (find(i) != i)
        {
            continue;
        }
        ++cnt;
        // 现在只有无限制和要求出现不同了
        // 我们可以枚举上一个不同的位置
        // 由于 fa 更新的时候不是 1,2,3,4 这样,而是每块第一个的编号
        // 我们把 fa[i] 离散化,pos 表示是第几个,rea 表示第 i 个的实际 fa
        for (int j = pos[mx[rea[cnt]]]; j < cnt; j++)
        {
            (dp[cnt] += dp[j]) %= 998244353;
        }
    }
    //   cout << cnt << endl;
    writeln(2 * dp[cnt] % 998244353);
    return 0;
}

标签:Count,Binary,le,int,题解,sum,texttt,mx,dp
From: https://www.cnblogs.com/yuhang-ren/p/17716586.html

相关文章

  • 探索 WPF 的 ITabletManager.GetTabletCount 在 Win11 系统的底层实现
    本文将和大家介绍专为WPF触摸模块提供的ITabletManager的GetTabletCount方法在Windows11系统的底层实现本文属于WPF触摸相关系列博客,偏系统底层介绍,更多触摸博客请看WPF触摸相关大家都知道在Windows7系统,有专门的笔和触摸服务提供触摸消息的支持。而WPF是从V......
  • 【POJ 3278】Catch That Cow 题解(深度优先搜索)
    农夫约翰已被告知一头逃亡奶牛的位置,并想立即抓住她。他从一条数字线上的N点(0≤N≤100000)开始,奶牛在同一条数字线上的K点(0≥K≤100000)。农夫约翰有两种交通方式:步行和坐车。*步行:FJ可以在一分钟内从任何点X移动到点X-1或X+1*坐车:FJ可以在一分钟内从任何X点移动到2×X点。如果奶......
  • 「2019 集训队互测 Day 3」操作序列计数 题解
    简化题意:对于每一个$L$,求出有多少个长度为$L+1$的非负整数序列$a$,满足$\sum_{i=0}^{L}a_ik^i\leqn$,并且$a_{L}>0$。我们注意题目要求的和是小于等于一个数,这不太方便。我们可以把它转化成和等于一个数的形式,其实就是和为$nk$的方案数,这就相当于在最后的和后面乘上一......
  • select count(*) 和 select count(1) 以及 select count(column) 的区别
      1. 一般情况下,SelectCount(*)和SelectCount(1)两者的返回结果是一样的  2. 假如表沒有主键(PK),那么count(1)比count(*)快,如果有主键PK的話,那count(主键)最快,如果你的表只有一个字段的话那count(*)就是最快的  3.count(*)跟count(1)的结果一样,都包括对NU......
  • 【题解】CF1817 合集
    CF1817AAlmostIncreasingSubsequence考虑几乎上升的序列的长度,就是我们的区间长度减去\(a_{i-2}\geqa_{i-1}\geqa_i\)的对数,然后维护即可,当然个人感觉自己的代码有点长,可以考虑看洛谷题解代码code:#include<bits/stdc++.h>usingnamespacestd;constintNN=2e5+......
  • CF840C 题解
    一、题目描述:给你一个长度为$n$的序列$a_1\sima_n$,$0\lea_i\le1\times10^9$。求有多少种$1\simn$的全排列$b$,使得对于$i\in[2,n],a_{b_i}\timesa_{b_{i-1}}$不是完全平方数。本题中完全平方数的定义:若存在某个整数$k$,使得$k^2=x$,则$x$是一个......
  • 9.18CF1817题解
    9.18CF1817题解A.AlmostIncreasingSubsequence题意给定长度为\(n\)一个序列\(a\)以及\(q\)次询问,每次询问给出\(l\)和\(r\),找出序列\(a\)在\([l,r]\)内最长的几乎递增子序列。对于几乎递增的定义:如果一个序列中不存在连续的三个数\(x\),\(y\),\(z\),使得\(x\g......
  • mysql count()函数
    count(expr)函数的参数expr可以是任意的表达式,该函数用于统计在符合搜索条件的记录总数;count(expr)函数执行效率从低到高排序为:count(非主键字段)<count(主键)<count(1)≈count(*);对于count(1)和count(*),效率相当,建议尽量使用count(*),因为MySQL优化器会选择最小......
  • 题解 AGC058B 【Adjacent Chmax】
    postedon2022-08-1500:08:56|under题解|sourceproblem一个长为\(n\)的排列\(P\),每次可以选择一个\(i\),令\(v=\max(P_i,P_{i+1})\),使\(P_i=P_{i+1}=v\),求若干次操作后有多少种不同的序列。\(1\leqn\leq5000\)。solution显然地,对于一个\(P_i\),它要么被完全覆盖......
  • AT_arc165_d 题解
    AT_arc165_d[ARC165D]SubstringComparison题解Links洛谷AtCoderDescription给定正整数\(n,m\)和\(m\)个形如\((A_{i},B_{i},C_{i},D_{i})\)的限制条件。判断是否存在一个长度为\(n\)的序列\(P\)满足\(\foralli\in[1,m]\),\(P_{A_{i}\dotsB_{i}}\)字典序......