原题链接
https://agc017.contest.atcoder.jp/tasks/agc017_f
Description
给出一个N行的三角形,第i行有i个点
在(X,Y)这个点,可以走向(X+1,Y),也可以走向(X+1,Y+1)
现在需要找出M条路径,这M条路径不能交叉,即第i条路径的所到的每一个点的都必须在第i-1条路径同一行到达的点的右边(可以相同)
同时给出K条限制,形如Ai,Bi,Ci,表示第Ai条路径在第Bi行向下必须+1或者必须不+1
两条路径可以是完全相同的
求有多少种合法的选法选出M条路径
N,M<=20
K<=(N-1)M
Solution
我们发现一条路径可以用一个二进制数表示
要求就是第i个数每一位的前缀1数量必须大于等于第i-1个数的
考虑状压DP
设
Fi,S,T
F
i
,
S
,
T
表示做到第i条路径,第i-1条路径的数为S ,第i条路径为T
直接转移一下,用个求和什么优化一下
大概是
O(m22n)
O
(
m
2
2
n
)
明显会超时
换一种思路
我们只记录第i-1条路径的状态,然后按位转移
设 Fi,j,S F i , j , S 表示当前做到第i条路径的第j位
我们发现如果S全部记录的是第i-1条路径的状态,那么在转移到i+1的时候我们无法得知第i条路径的状态,GG
思考我们转移到底需要什么
我们需要第i-1条和第i条前j位用了多少个1,后面就是上一条的状态
然后我们发现,如果第i-1条路径这一位是1,并且前面的状态都完全相同,那么代表这一位也必须选1
以下就真的是人类智慧了
S的前j位表示第i条路径的状态,后面n-j位表示第i-1条路径的状态,即第i条路径能走的最靠左的走法
然后S的前j位的1的个数与第i-1条路径的前j位的1的个数相同(但状态不一定相同)
现在我们只需要考虑转移
找到第j位以后第一个S为0的位(因为如果为1那么只能选1没有贡献)p
现在这一位选0和选1分开考虑,如果选0,那么S不变
如果选1,那么将S的第p位改成1,将第p位以后第一个1的位改为0,j转移到p,后面的都不变
举个例子, Fi,2,101011 F i , 2 , 101011 转移到 Fi,4,101011 F i , 4 , 101011 和 Fi,4,101101 F i , 4 , 101101
转移到最后了就统计一下加到i+1去
然后一个状态的第i为的后面第一个0/1的位置可以
O(n2n)
O
(
n
2
n
)
预处理
然后发现第一维i空间上并没有用,直接去掉
以上我们都还没有考虑限制的问题
既然要保证满足限制,我们在第p为分类选0/1的时候要特判
还有一种情况,从j转移到p的时候我们知道第j+1位到第p-1位都是1,那么如果有一个限制是这一条路径在这之间必须选一个0那么就GG了,这种情况可以用一个循环判断(实际上不太影响复杂度),可能也是我跑的慢的原因
最后复杂度是 O(n22n) O ( n 2 2 n ) 解决问题
Code
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define LL long long
#define mo 1000000007
#define M 1048580
#define N 21
using namespace std;
int f[N][M];
int n,m,l,a[N][N],cf[N],qs[M][N][2];
int main()
{
cin>>n>>m>>l;
memset(a,255,sizeof(a));
cf[0]=1;
fo(i,1,n) cf[i]=cf[i-1]*2;
fo(i,1,l)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
a[x][y]=z;
}
n--;
fo(c,0,cf[n]-1)
{
int w1=0,w2=0;
fod(i,n,0)
{
qs[c][i][0]=w1,qs[c][i][1]=w2;
if(i>0)
{
if(c&cf[i-1]) w2=i;
else w1=i;
}
}
}
f[0][0]=1;
LL ans;
fo(i,1,m)
{
fo(j,0,n-1)
{
fo(c,0,cf[n]-1)
{
if(!f[j][c]) continue;
int p=qs[c][j][0];
if(p&&p<=n)
{
fo(k,j+1,p-1)
{
if(a[i][k]==0)
{
f[j][c]=0;
break;
}
}
if(a[i][p]!=1)
{
(f[p][c]+=f[j][c]);
if(f[p][c]>=mo) f[p][c]-=mo;
}
if(a[i][p]!=0)
{
int q=qs[c][p][1];
if(q&&q<=n)
{
int v=(c|cf[p-1])-cf[q-1];
(f[p][v]+=f[j][c]);
if(f[p][v]>=mo) f[p][v]-=mo;
}
else
{
int v=c|cf[p-1];
(f[p][v]+=f[j][c]);
if(f[p][v]>=mo) f[p][v]-=mo;
}
}
}
else
{
fo(k,j+1,n)
{
if(a[i][k]==0)
{
f[j][c]=0;
break;
}
}
(f[n][c]+=f[j][c]);
if(f[n][c]>=mo) f[n][c]-=mo;
}
f[j][c]=0;
}
}
if(i==m)
{
ans=0;
fo(c,0,cf[n]-1) (ans+=f[n][c])%=mo;
}
else
fo(c,0,cf[n]-1)
{
f[0][c]=f[n][c];
f[n][c]=0;
}
}
printf("%lld\n",ans);
}