JZOJ 5398. 【NOIP2017提高A组模拟10.7】Adore

来源:互联网 发布:嗨氏黑历史知乎 编辑:程序博客网 时间:2024/06/04 18:57

题目

Description
小w 偶然间见到了一个DAG。
这个DAG 有m 层,第一层只有一个源点,最后一层只有一个汇点,剩下的每一层都有k 个节点。
现在小w 每次可以取反第i(1 < i < n - 1) 层和第i + 1 层之间的连边。也就是把原本从(i, k1) 连到(i + 1, k2) 的边,变成从(i, k2) 连到(i + 1, k1)。
请问他有多少种取反的方案,把从源点到汇点的路径数变成偶数条?
答案对998244353 取模。
Input
一行两个整数m, k。
接下来m - 1 行, 第一行和最后一行有k 个整数0 或1,剩下每行有k2 个整数0 或1,第(j- 1)* k + t 个整数表示(i, j) 到(i + 1, t)有没有边。
Output
一行一个整数表示答案。
Sample Input
5 3
1 0 1
0 1 0 1 1 0 0 0 1
0 1 1 1 0 0 0 1 1
0 1 1
Sample Output
4
Data Constraint
100% 的数据满足4 <= m <= 10^4, k <= 10。

题解

这道题很容易看错,是整行路径取反
我们发现这题的两个亮点。①k≤10,②奇偶性。
所以可以设装压DP。
f[i][j]表示做到第i行,此层状态(即二进制第k位从源点走(i,k)的方案数的奇偶性)为j的方案数。
cnt[i]表示二进制i的1的个数的奇偶性。
我们知道了第i层不取反的走到下一层的点的对应的方案数的奇偶性。
a[i]表示此行第i个点能否走到下一行的点(不取反)。
b[i]表示此行第i个点能否走到下一行的点(取反)。
a0表示不取反,下一行的状态。(a1表取反)
那么f[i][a0]+=f[i][s],f[i][a1]+=f[i][s]
a0=Σkj=1cnt[a[i]s]2j1
这表示什么意思?
如果第i+1行的某x个二进制位为1,说明到点(i+1,x)方案数为奇数。
走到(i,j)的方案数为奇数,且能够走到第i+1行的点(i+1,x),对走到(i+1,x)的方案数贡献为奇数。否则为偶数。
至于走到(i+1,x)的方案数则为上一行k个点走到(i+1,x)的方案数的总和。
所以二进制数a[i]&s的1的个数的奇偶性就代表走到(i+1,x)的方案数的奇偶性。
第1行和第n-1行要特殊一些,①不能将路径取反,②该行只有1个点。
第1行和第n-1行的转移与第2~n-2行类似。

总结一下
①虽然这是道很多人认为很简单的DP题,但我认为想起来不简单。
②值得推敲的:
每一行的取反/不取反可以通过DP的形式组成了一个个不重不漏的状态。
③即使我知道这是状压DP,但是算方案数的奇偶性还是挺恶心的一件事。
运算/状态转移方面有很大的问题。
解决方案:利用二进制的巧妙性。
(I)条件的合并:逻辑运算。
(II)奇数偶数加起来的和的奇偶性&二进制数的1的个数的奇偶性相结合。

代码

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>#define N 10010#define LL long long#define mo 998244353#define fo(i,a,b) for(i=a;i<=b;i++)using namespace std;LL _2[15];LL i,j,k,l,n,m,x,o,ans;LL s,a0,a1,a[15],b[15];LL f[N][1030],cnt[1030];int main(){    freopen("adore.in","r",stdin);    freopen("adore.out","w",stdout);    _2[1]=1;fo(i,2,14)_2[i]=(_2[i-1]*2)%mo;    scanf("%lld%lld",&n,&k);    s=0;    fo(i,1,k){        scanf("%lld",&x);        s|=x*_2[i];    }    f[0][s]=1;o=0;    fo(i,0,_2[k+1]-1)cnt[i]=cnt[i/2]^(i&1);    fo(i,2,n-2){        o=1-o;        memset(a,0,sizeof(a));        memset(b,0,sizeof(b));        memset(f[o],0,sizeof(f[o]));         fo(j,1,k)fo(l,1,k){            scanf("%lld",&x);            if(x)a[j]|=_2[l],b[l]|=_2[j];        }        fo(s,0,_2[k+1]-1)            if(f[1-o][s]){                a0=a1=0;                fo(j,1,k){                    a0|=cnt[s&a[j]]*_2[j];                    a1|=cnt[s&b[j]]*_2[j];                }                f[o][a0]=(f[o][a0]+f[1-o][s])%mo;                f[o][a1]=(f[o][a1]+f[1-o][s])%mo;            }    }    o=1-o;    memset(f[o],0,sizeof(f[o]));     a[1]=0;    fo(i,1,k){        scanf("%lld",&x);        a[1]|=x*_2[i];    }    fo(s,0,_2[k+1]-1)        if(f[1-o][s]){            a0=cnt[s&a[1]]*_2[1];            f[o][a0]=(f[o][a0]+f[1-o][s])%mo;        }    printf("%lld",f[o][0]);    return 0;}
原创粉丝点击