UOJ#39. 【清华集训2014】简单回路 动态规划 插头DP

时间:2021-08-31 16:49:04

原文链接www.cnblogs.com/zhouzhendong/p/UOJ39.html

前言

老年选手没有码力。太久没更博了强行更一发。

题解

这题一看就是个插头DP,于是我们考虑用括号序列来表示状态。

关于插头DP,推荐一篇博客:https://www.cnblogs.com/zinthos/p/3897854.html

我们发现,(m=6) 时,不同的插头状态数只有 (C_1binom 62 C_2binom 64 C_3 binom 66 = 50) 个。(其中 (C_x)(x) 对括号组成的可能的括号序列数)

(pre_{i,S}) 表示前 (i) 行,没有闭环,第 (i) 行的插头情况为 (S) 的方案数。

(suf_{i,S}) 表示后 (n-i 1) 行,没有闭环,第 (i) 行的插头情况为 (S) 的方案数。

(ans_{i,j}) 表示 ((i,j))((i 1,j)) 之间的竖边的答案。

那么,假设 (S,T) 是一对拼在一起刚好是一个环的插头状态,那么,如果 ((i,j)leftrightarrow (i 1,j)) 是某 (S,T) 的插头衔接处的竖边,那么 (ans_{i,j}) 就包含了相应的 (pre_{i,S}cdot suf_{i 1,T}) 种方案。

接下来考虑如何求 (pre)(suf)

考虑预处理转移,设当前插头为 (S),枚举新的一行中有哪些横边被经过,然后计算出转移到的状态即可。

具体实现过于复杂,只能用 (s** t) 来形容。

代码

#include <bits/stdc  .h>
#define clr(x) memset(x,0,sizeof (x))
#define For(i,a,b) for (int i=(a);i<=(b);i  )
#define Fod(i,b,a) for (int i=(b);i>=(a);i--)
#define fi first
#define se second
#define pb(x) push_back(x)
#define mp(x,y) make_pair(x,y)
using namespace std;
typedef long long LL;
typedef pair <int,int> pii;
LL read(){
    LL x=0,f=0;
    char ch=getchar();
    while (!isdigit(ch))
        f=ch=='-',ch=getchar();
    while (isdigit(ch))
        x=(x<<1) (x<<3) (ch^48),ch=getchar();
    return f?-x:x;
}
const int N=1005,M=10,S=55,mod=1e9 7;
void Add(int &x,int y){
    if ((x =y)>=mod)
        x-=mod;
}
int n,m;
int a[N];
void in_a(){
    int k=read(),x,y;
    while (k--)
        x=read(),y=read(),a[x]|=1<<y;
}
int id[N],val[S][M],scnt=0,can[S];
vector <pii> trans[S];
vector <int> opp[S];
void getmatch(int *s,int *p){
    static int st[M];
    int top=0;
    For(i,1,m)
        if (s[i]==1)
            st[  top]=i;
        else if (s[i]==-1)
            p[i]=st[top],p[st[top]]=i,top--;
}
void get_trans(int x,int e){
    static int f[M],p[M],d[M],res[M],vis[M];
    clr(f),clr(p),clr(res),clr(vis);
    int *s=val[x];
    For(i,1,m-1)
        f[i]=e>>(i-1)&1;
    int zzy=0;
    For(i,1,m){
        d[i]=(s[i]!=0) f[i-1] f[i];
        if (s[i]!=0&&f[i-1]&&f[i])
            return;
        if (d[i])
            zzy|=1<<i;
    }
    getmatch(s,p);
    For(i,1,m){
        if (vis[i]||d[i]!=1)
            continue;
        int j=i;
        while (j==i||d[j]==2){
            int nxt=0;
            vis[j]=1;
            if (p[j]&&!vis[p[j]])
                nxt=p[j];
            else if (f[j-1]&&!vis[j-1])
                nxt=j-1;
            else if (f[j]&&!vis[j 1])
                nxt=j 1;
            if (!nxt)
                return;
            j=nxt;
        }
        vis[j]=1;
        res[min(i,j)]=1,res[max(i,j)]=-1;
    }
    For(i,1,m)
        if (d[i]&&!vis[i])
            return;
    int Res=0;
    Fod(i,m,1)
        Res=Res*3 res[i] 1;
    trans[x].pb(mp(id[Res],zzy));
}
int check_opposite(int *a,int *b){
    static int pa[M],pb[M],vis[M];
    clr(vis);
    For(i,1,m)
        if ((a[i]!=0)^(b[i]!=0))
            return 0;
    getmatch(a,pa),getmatch(b,pb);
    int cnt=0;
    For(i,1,m)
        if (a[i]!=0)
            cnt  ;
    For(i,1,m)
        if (a[i]!=0){
            int x=i;
            For(t,1,cnt){
                vis[x]=1,x=(t&1?pa:pb)[x];
                if (t<cnt&&vis[x])
                    return 0;
            }
            return 1;
        }
}
void getval(int m){
    static int tmp[M];
    int p3=pow(3,m);
    For(i,0,p3-1){
        clr(tmp);
        int t=i,s=0,flag=1,empty=1,Can=0;
        For(j,1,m){
            tmp[j]=t%3-1;
            if (s==1&&Can!=-1)
                Can|=1<<j;
            s =tmp[j];
            if (s==1&&Can!=-1)
                Can|=1<<j;
            if (s>1)
                Can=-1;
            empty&=s==0;
            flag&=s>=0;
            t/=3;
        }
        flag&=s==0;
        flag&=!empty;
        if (flag){
            id[i]=  scnt;
            For(j,1,m)
                val[scnt][j]=tmp[j];
            can[scnt]=Can;
        }
    }
    For(i,1,scnt){
        For(e,0,(1<<(m-1))-1)
            get_trans(i,e);
        For(j,1,scnt)
            if (check_opposite(val[i],val[j]))
                opp[i].pb(j);
    }
}
int pre[N][S],suf[N][S];
int ans[N][M];
int main(){
    n=read(),m=read();
    in_a();
    getval(m);
    For(i,1,n){
        For(j,1,scnt){
            if (can[j]!=-1&&!(can[j]&a[i]))
                Add(pre[i][j],1);
            int v=pre[i-1][j];
            if (!v)
                continue;
            for (auto t : trans[j])
                if (!(t.se&a[i]))
                    Add(pre[i][t.fi],v);
        }
    }
    Fod(i,n,1){
        For(j,1,scnt){
            if (can[j]!=-1&&!(can[j]&a[i]))
                Add(suf[i][j],1);
            int v=suf[i 1][j];
            if (!v)
                continue;
            for (auto t : trans[j])
                if (!(t.se&a[i]))
                    Add(suf[i][t.fi],v);
        }
    }
    For(i,1,n-1)
        For(j,1,scnt)
            for (auto o : opp[j]){
                int v=(LL)pre[i][j]*suf[i 1][o]%mod;
                For(k,1,m)
                    if (val[j][k]!=0)
                        Add(ans[i][k],v);
            }
    int q=read();
    while (q--){
        int x=read(),y=read();
        printf("%dn",ans[x][y]);
    }
    return 0;
}