[CF888F]Connecting Vertices
题目
题解
这道题如果不仔细分析,还真看不出是道区间 \(DP\) 的题...
首先,题目要求的其中一个不能连边的限制有些奇怪:
两条线段不能在顶点之外的地方相交,如不能同时连(1,3) 和 (2,4)
如果我们直接放在一个 \(N\) 边形上,思考这个条件指哪些点无法连接?
以 \(N=5\) 举例,我们无法同时连接
- \((1,3)\) 与 \((2,4),(2,5),(2,6)\)
- \((1,4)\) 与 \((2,5),(3,5)\)
- \((1,5)\) 似乎可以不与其他的排斥
- ......
经过分析,发现:如果可以连接 \((a,b)\) 与 \((c,d)\),那么必然有 \([a,b]\) 与 \([c,d]\) 不相交(后面的 \([]\) 指区间)
再结合 \(N\le 500\),不难想到这题可以用区间 \(DP\) 做
将 \(1...N\) 放到一个区间上,所谓连边就是选择某一个区间,而我们的目的就是选择 \(N-1\) 个区间,让它们端点相连,保证最后所有点联通且选择的区间不能相交且选择的所有区间的左、右端点需有 \(a[l][r]=1\)
定义 \(f[l][r]\) 表示将 \([l,r]\) 这个区间的点全部联通的方案数,那么考虑两种转移:
- 如果 \(l,r\) 可以连接即有 \(a[l][r]=1\),那么考虑枚举一个断点 \(i\),让 \(i\) 和 \(i+1\) 断开,让 \([l,i]\) 和 \([i+1,r]\) 通过边 \((l,r)\) 联通,则 \(f[l][r]=\sum_{i=l}^{r-1}f[l][i]\times f[i+1][r]\);
- 考虑 \(l,r\) 是通过某一个中转点连接而非直接连接,枚举这个中转点 \(i\),有转移 \(f[l][r]=\sum_{i=l}^{r-1}f[l][i]\times f[i][r]\);
显然最后的答案即为 \(f[1][n]\),但是,对于第二个状态的转移显然是存在问题的:
首先观察状转 \(f[l][r]=\sum_{i=l}^{r-1}f[l][i]\times f[i][r]\),而对于 \(f[i][r]\),由于定义显然它包含了连接 \([i,r]\) 以 \(p(p\in [i,r)\) 为中转点的情况,而当 \(i=p\) 时,我们又重新算了一遍以这个点为中转点的情况,显然这两个方案重复。
那么,如何去掉这种重复的情况?随便加上一些不同的地方,让其变得不同即可
针对我们的状态定义,最方便的一种就是再加上一维 \([0/1]\),整体 \(f[l][r][0/1]\) 表示将 \([l,r]\) 全部联通,其中 \(l,r\) 两个点强制 连/不连 的情况,显然原状态 \(f'[l][r]=f[l][r][0]+f[l][r][1]\),对于转移有
- 由于我们之前的转移是假定连接了 \(l,r\),那么这个状态不变,只是将 \(f'[l][r]\) 展开即可,变为 \(f[l][r][0]=\sum_{i=l}^{r-1}(f[l][i][0]+f[l][i][1])\times (f[i][r][0]+f[i][r][1])\);
- 这个状态如果沿用之前的是有问题,这个我们说过,那么强制一些东西不一样,强制连接 \(l,i\),那么前后的状态就会有所不同,状转 \(f[l][r][1]=\sum_{i=l}^{r-1}f[l][i][0]\times (f[i][r][0]+f[i][r][1])\);
我们最后求的是 \(f'[l][r]=f[l][r][0]+f[l][r][1]\)
代码
#include<cstdio>
#include<cstring>
#define rep(i,__l,__r) for(signed i=(__l),i##_end_=(__r);i<=i##_end_;++i)
#define fep(i,__l,__r) for(signed i=(__l),i##_end_=(__r);i>=i##_end_;--i)
#define erep(i,u) for(signed i=tail[u],v=e[i].to;i;i=e[i].nxt,v=e[i].to)
#define writc(a,b) fwrit(a),putchar(b)
#define mp(a,b) make_pair(a,b)
#define ft first
#define sd second
typedef long long LL;
// typedef pair<int,int> pii;
typedef unsigned long long ull;
typedef unsigned uint;
#define Endl putchar('\n')
// #define int long long
// #define int unsigned
// #define int unsigned long long
#define cg (c=getchar())
template<class T>inline void read(T& x){
char c;bool f=0;
while(cg<'0'||'9'<c)f|=(c=='-');
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
if(f)x=-x;
}
template<class T>inline T read(const T sample){
T x=0;char c;bool f=0;
while(cg<'0'||'9'<c)f|=(c=='-');
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
return f?-x:x;
}
template<class T>void fwrit(const T x){//just short,int and long long
if(x<0)return (void)(putchar('-'),fwrit(-x));
if(x>9)fwrit(x/10);
putchar(x%10^48);
}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline int gcd(const int a,const int b){return b?gcd(b,a%b):a;}
inline void getInv(int inv[],const int lim,const int MOD){
inv[0]=inv[1]=1;for(int i=2;i<=lim;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
}
inline LL mulMod(const LL a,const LL b,const LL mod){//long long multiplie_mod
return ((a*b-(LL)((long double)a/mod*b+1e-8)*mod)%mod+mod)%mod;
}
const int MAXN=500;
const int MOD=1e9+7;
int a[MAXN+5][MAXN+5],n;
inline void Init(){
n=read(1);
rep(i,1,n)rep(j,1,n)a[i][j]=read(1);
}
int f[MAXN+5][MAXN+5][2];
int Dfs(const int l,const int r,const int q){
if(l==r){
if(q==0)return 1;
return 0;
}
if(f[l][r][q]!=-1)return f[l][r][q];
f[l][r][q]=0;
if(q==0){
if(a[l][r])rep(i,l,r-1){
f[l][r][q]+=1ll*(Dfs(l,i,0)+Dfs(l,i,1))*(Dfs(i+1,r,0)+Dfs(i+1,r,1))%MOD;
if(f[l][r][q]>=MOD)f[l][r][q]-=MOD;
}
}else{
rep(i,l,r-1)if(a[l][i]){//注意, 此处必须是 r-1, 不然就会算上强制连接 l,r 的情况
f[l][r][q]+=1ll*Dfs(l,i,0)*(Dfs(i,r,0)+Dfs(i,r,1))%MOD;
if(f[l][r][q]>=MOD)f[l][r][q]-=MOD;
}
}return f[l][r][q];
}
signed main(){
Init();
memset(f,-1,sizeof f);
printf("%d\n",(Dfs(1,n,0)+Dfs(1,n,1))%MOD);
return 0;
}