标签:har 没有 add 处理 答案 inline long blog ||
原文链接www.cnblogs.com/zhouzhendong/p/UOJ39.html
老年选手没有码力。太久没更博了强行更一发。
这题一看就是个插头DP,于是我们考虑用括号序列来表示状态。
关于插头DP,推荐一篇博客:https://www.cnblogs.com/zinthos/p/3897854.html
我们发现,\(m=6\) 时,不同的插头状态数只有 \(C_1\binom 62 + C_2\binom 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("%d\n",ans[x][y]);
}
return 0;
}
UOJ#39. 【清华集训2014】简单回路 动态规划 插头DP
标签:har 没有 add 处理 答案 inline long blog ||
原文地址:https://www.cnblogs.com/zhouzhendong/p/UOJ39.html