原文链接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;
}