「模拟赛20191019」B 容斥原理+DP计数

题目描述

将\(n\times n\)的网格黑白染色,使得不存在任意一行、任意一列、任意一条大对角线的所有格子同色,求方案数对\(998244353\)取模的结果。

输入

一行一个整数\(n\)。

输出

一行一个整数表示答案对\(998244353\)取模的值。

样例

样例输入

3

样例输出

32

数据范围

对于\(100\%\)的数据,\(1\leq n\leq 300\)。

比第一题难了不知道多少……

这种东西怎么看都是容斥嘛。

我们先考虑对角线没有限制的情况:
枚举行和列有多少个是同色的,若行+列是奇数,则减去方案数,若行+列是偶数,则加上方案数,其他没有限制的点任意选,容斥一波即可(注意,若既有行又有列,则只能是同一颜色;但如果只有行或只有列则可以随意指定颜色,方案数应随之变动)。

那么有对角线该怎么办?

仍然容斥,\(0\)条对角线-\(1\)条对角线+\(2\)条对角线即是最终答案。其中\(0\)条对角线就是上述的算法。

考虑\(1\)条对角线的情况,不妨设是主对角线,令\(f_{i,j,k}\)表示考虑到前\(i\)行\(i\)列,选中了\(j\)行\(k\)列的方案数。然后考虑转移到\(i+1\),则枚举选\(0/1\)行,\(0/1\)列,分别转移。但是会存在一个问题,如果既没有选择行又没有选择列,会导致判断\((i,i)\)这个格子可以任选,但事实上主对角线被锁死了,所以要乘上\(\frac{1}{2}\)的系数。最后\(DP\)完后再根据\(j+k\)的奇偶性确定容斥系数,然后再乘上\(2^{(n-j)(n-k)}\)的系数表示未被考虑的格子的方案。

然后考虑\(2\)条对角线的情况,类似\(1\)条对角线,只是变成从中心开始一圈一圈往外\(DP\),讨论的情况更多(并且要特判,奇数的时候两条对角线颜色必须一样,偶数的时候两条对角线颜色不必一样),就没有什么差别了。

\(Code:\)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
#define mod 998244353
#define inv2 499122177
#define inv16 935854081
int n, f[2][305][305];
int C[305][305], mul[100005];
void Add(int &a, int b){a = (a + b) % mod;}
int Solve0()
{
    int ans = 0;
    for (int i = 0; i <= n; i++)
        for (int j = 0; j <= n; j++)
        {
            int k;
            if (!i || !j)
                k = i ^ j;
            else
                k = 1;
            int x = (n - i) * (n - j) + k;
            ans = (ans + (ll)C[n][i] * C[n][j] % mod * mul[x] % mod * (1 - (i + j) % 2 * 2) % mod) % mod;
        }
    return ans;
}
int Solve1()
{
    memset(f, 0, sizeof(f));
    int z = 0;
    f[0][0][0] = 2;
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j <= i; j++)
        {
            for (int k = 0; k <= i; k++)
            {
                Add(f[z ^ 1][j][k], (ll)f[z][j][k] * inv2 % mod);
                Add(f[z ^ 1][j][k + 1], -f[z][j][k]);
                Add(f[z ^ 1][j + 1][k], -f[z][j][k]);
                Add(f[z ^ 1][j + 1][k + 1], f[z][j][k]);
                f[z][j][k] = 0;
            }
        }
        z ^= 1;
    }
    int ans = 0;
    for (int i = 0; i <= n; i++)
        for (int j = 0; j <= n; j++)
            Add(ans, (ll)f[z][i][j] * mul[(n - i) * (n - j)] % mod);
    return ans;
}
int Solve2()
{
    memset(f, 0, sizeof(f));
    int z = 0;
    if (n & 1)
    {
        f[0][0][0] = 1;
        f[0][0][1] = f[0][1][0] = -2;
        f[0][1][1] = 2;
    }
    else
        f[0][0][0] = 2;
    for (int k = n & 1; k < n; k += 2)
    {
        for (int i = 0; i <= k; i++)
        {
            for (int j = 0; j <= k; j++)
            {
                Add(f[z ^ 1][i][j], (ll)f[z][i][j] * inv16 % mod);
                Add(f[z ^ 1][i + 1][j], -(ll)f[z][i][j] * inv2 % mod);
                Add(f[z ^ 1][i][j + 1], -(ll)f[z][i][j] * inv2 % mod);
                Add(f[z ^ 1][i + 2][j], f[z][i][j]);
                Add(f[z ^ 1][i][j + 2], f[z][i][j]);
                Add(f[z ^ 1][i + 2][j + 1], -(ll)f[z][i][j] * 2ll % mod);
                Add(f[z ^ 1][i + 1][j + 2], -(ll)f[z][i][j] * 2ll % mod);
                Add(f[z ^ 1][i + 1][j + 1], f[z][i][j] * 2ll % mod);
                Add(f[z ^ 1][i + 2][j + 2], f[z][i][j]);
                f[z][i][j] = 0;
            }
        }
        z ^= 1;
    }
    int ans = 0;
    for (int i = 0; i <= n; i++)
        for (int j = 0; j <= n; j++)
            if (!j && !i && (!(n & 1)))
                Add(ans, mul[n * (n - 2) + 2]);
            else
                Add(ans, (ll)f[z][i][j] * mul[(n - i) * (n - j)] % mod);
    return ans;
}
int main()
{
    scanf("%d", &n);
    C[0][0] = 1;
    for (int i = 1; i <= n; i++)
    {
        C[i][0] = 1;
        for (int j = 1; j <= i; j++)
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
    }
    mul[0] = 1;
    for (int i = 1; i <= n * n + 1; i++)
        mul[i] = mul[i - 1] * 2 % mod;
    int ans = Solve0();
    ans = (ans - 2ll * Solve1()) % mod;
    ans = (ans + Solve2()) % mod;
    if (ans < 0)ans += mod;
    printf("%d\n", ans);
}

原文地址:https://www.cnblogs.com/ModestStarlight/p/11725082.html

时间: 2024-08-25 09:43:31

「模拟赛20191019」B 容斥原理+DP计数的相关文章

「模拟赛20180406」膜树 prufer编码+概率

题目描述 给定一个完全图,保证\(w_{u,v}=w_{v,u}\)且\(w_{u,u}=0\),等概率选取一个随机生成树,对于每一对\((u,v)\),求\(dis(u,v)\)的期望值对\(998244353\)取模. 输入 第一行一个数\(n\) 接下来\(n\)行,每行\(n\)个整数,第\(i\)行第\(j\)个整数表示\(w_{i,j}\) 输出 输出共\(n\)行,每行\(n\)个整数,第\(i\)行第\(j\)个整数表示\(dis(i,j)\)的期望值 样例 样例输入 4 0 1

「模拟8.21」山洞(矩阵优化DP)

暴力: 正解: 考虑循环矩阵,f[i][j]表示从i点到j点的方案数 我们发现n很小,我们预处理出n次的f[i][j] 然后在矩阵快速幂中,我们要从当前的f[i][j]*f[j][k]-->fir[i][j] 但是此时的循环为三层 我们考虑转移式子的意义在0-n次从i-j,在n+1到2×n转移至j 这样此时的j-k其实可以把他看作从0开始走j-k步本质上是一样的 然后还有一个特判,就不讲了 for(int j=0;j<n;++j) { ff[now][j]=(ff[now][j]+ff[las

LiberOJ #6210. 「美团 CodeM 决赛」tree 树形DP

题目链接:点这里 题解: 需要证明,所求的路径一定是全部权值都为1或者,路径上权值至多有一个为2其余为1且权值2在路径中央. 然后树形DP 设定dp[i][0/1] 以1为根的情况下,以i 节点下子树走分别全1和 走一次2和剩余全走1 的最长链 每遍历一次子树,统计一次答案 下面给出代码 #include<bits/stdc++.h> using namespace std; #pragma comment(linker, "/STACK:102400000,102400000&qu

(计数器)NOIP模拟赛(神奇的数位DP题。。)

没有原题传送门.. 手打原题QAQ [问题描述]     一本书的页数为N,页码从1开始编起,请你求出全部页码中,用了多少个0,1,2,-,9.其中-个页码不含多余的0,如N=1234时第5页不是0005,只是5. [输入]        一个正整数N(N≤109),表示总的页码. [输出]        共十行:第k行为数字k-1的个数. 这道题是一道很有意思的DP题. 我们先来看一看这道题目 就是求1~n这么多个数中有多少个X数字. 然后我们来看一看一个例子: 在1~10这10个数中,每个数

模拟赛 提米树 题解 (DP+思维)

题意: 有一棵棵提米树,满足这样的性质: 每个点上长了一定数量的Temmie 薄片,薄片数量记为这个点的权值,这些点被标记为 1 到 n 的整数,其 中 1 号点是树的根,没有孩子的点是树上的叶子. 定义\((a,b)\)是一对相邻的叶子,当且仅当没有其它的叶子节点在 DFS 序上在a,b 之间. 每对相邻的叶子都会产生一个代价,代价为 a 到 b 路径上(不包含 a,b)的点中,最大点权值. 提米树可以提供决心,一棵提米树能提供的决心的数量是树上所有叶子上长的 Temmie 薄片数量和,减去所

3.29省选模拟赛 除法与取模 dp+组合计数

LINK:除法与取模 鬼题.不过50分很好写.考虑不带除法的时候 其实是一个dp的组合计数. 考虑带除法的时候需要状压一下除法操作. 因为除法操作是不受x的大小影响的 所以要状压这个除法操作. 直接采用二进制状压是不明智的 2的个数最多为13个 2^13也同样到达了1e4的复杂度. 考虑 hash状压 即 2的个数有x个 那么我们就有状态w表示2还有x个. 这样做的原因是把一些相同的东西给合并起来 而并非分散开来.即有多个2直接记录有多少个即可. 可以发现 这样做不同的除数最多只有5个 状态量较

「模拟8.21」虎

正解贪心考场只骗到了70分 做法一: 现将没有限制的边缩掉然后连边, 这样我们直接采用贪心的做法,因为每个边最多只会被反一次, 那么从叶子节点向上对于一个需要修改的边没直接令他向上直到不能修改 注意处理连在lca上有两条链的现象 1 #include<bits/stdc++.h> 2 #define MAXN 2100000 3 using namespace std; 4 struct node{int to;int n;}e[MAXN]; 5 int tot,head[MAXN]; 6 i

[CF245H] Queries for Number of Palindromes (容斥原理dp计数)

题目链接:http://codeforces.com/problemset/problem/245/H 题目大意:给你一个字符串s,对于每次查询,输入为一个数对(i,j),输出s[i..j]之间回文串的个数. 容斥原理: dp[i][j] = dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]; if( str[i]==str[j] 并且 str[i+1..j-1]是回文串 ) dp[i][j]++; 代码: 1 #include <cstdio> 2 #include &l

LibreOJ #514. 「LibreOJ β Round #2」模拟只会猜题意

二次联通门 : LibreOJ #514. 「LibreOJ β Round #2」模拟只会猜题意 /* LibreOJ #514. 「LibreOJ β Round #2」模拟只会猜题意 本想打个暴力找找规律 结果交上去就A了... 读入所有数 处理出前缀和 然后枚举区间长度 处理处1~n的答案 后O(1)查询即可 复杂度O(n^2 + m) */ #include <iostream> #include <cstring> #include <cstdio> voi