【NOIP2017提高A组模拟10.7】Adore

时间:2022-09-22 15:01:44

题目

小w 偶然间见到了一个DAG。
这个DAG 有m 层,第一层只有一个源点,最后一层只有一个汇点,剩下的每一层都有k 个节点。
现在小w 每次可以取反第i(1 < i < n - 1) 层和第i + 1 层之间的连边。也就是把原本从(i, k1) 连到(i + 1, k2) 的边,变成从(i, k2) 连到(i + 1, k1)。
请问他有多少种取反的方案,把从源点到汇点的路径数变成偶数条?
答案对998244353 取模。

状压dp

考虑用dp,
因为k<=10,而路径数只分奇偶,那可以用二进制来表示,
fi,s 第i层的路径数状态位s的方案数。
根据边转移就可以了,
但是这样时间复杂度位 O(n2kk2)
考虑优化,读入的每个点连出去的边都可以用二进制来表示,用位运算,这样就变成 O(nk2k) 的了。

#include <cmath>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
const int maxlongint=2147483647;
const int mo=998244353;
const int N=10005;
using namespace std;
int f[N][1034];
int n,m,b[N][12][12],mi[12],fb[N][12][12],bb[N][12],bc[N][12];
int read(int &n)
{
    char ch=' ';int q=0,w=1;
    for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
    if(ch=='-')w=-1,ch=getchar();
    for(;ch>='0' && ch<='9';ch=getchar())q=(q<<1)+(q<<3)+ch-'0';n=q*w;return n;
}
int main()
{
    mi[0]=1;
    for(int i=1;i<=11;i++) mi[i]=mi[i-1]*2;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) read(b[1][1][i]),bb[1][1]+=b[1][1][i]*mi[i-1];
    for(int i=2;i<=n-2;i++) 
        for(int j=1;j<=m;j++)
            for(int k=1;k<=m;k++) read(b[i][j][k]),fb[i][k][j]=b[i][j][k],bb[i][j]+=b[i][j][k]*mi[k-1],bc[i][k]+=fb[i][k][j]*mi[j-1];
    for(int i=1;i<=m;i++) read(b[n-1][i][1]),bb[n-1][i]+=b[n-1][i][1];
    f[1][1]=1;
    for(int i=1;i<=n-1;i++)
        for(int j=0;j<=mi[m]-1;j++)
        {
            int t=f[i][j];
            if(t)
            {
                int tt=0;
                for(int k=1;k<=m;k++)
                    tt^=bb[i][k]*bool(mi[k-1]&j);
                f[i+1][tt]=(1ll*f[i+1][tt]+t)%mo;
                if(i==1 || i==n-1) continue;
                tt=0;
                for(int k=1;k<=m;k++)
                    tt^=bc[i][k]*bool(mi[k-1]&j);
                f[i+1][tt]=(1ll*f[i+1][tt]+t)%mo;
            }
        }
    printf("%lld",f[n][0]);
}