linkkkkk
题意:
有
2
∗
n
2*n
2∗n个数,给出
m
m
m对关系
(
a
,
b
)
(a,b)
(a,b)表示a-b有联系。每次将这些数排成一排,如果相邻两个数有联系的话就可以消去,求方案数。
思路:
d
p
[
l
]
[
r
]
dp[l][r]
dp[l][r]表示将
[
l
,
r
]
[l,r]
[l,r]之间的数消去的方案数。
枚举中间点
k
k
k的时候,假设
k
−
r
k-r
k−r有联系,那么区间就可以分为
[
l
,
k
−
1
]
,
[
k
+
1
,
r
−
1
]
[l,k-1],[k+1,r-1]
[l,k−1],[k+1,r−1]两段,贡献是
d
p
[
l
]
[
k
−
1
]
∗
d
p
[
k
+
1
]
[
r
−
1
]
∗
C
r
−
l
+
1
2
k
−
l
2
dp[l][k-1]*dp[k+1][r-1]*C_{\frac{r-l+1}{2}}^{\frac{k-l}{2}}
dp[l][k−1]∗dp[k+1][r−1]∗C2r−l+12k−l
如果两个数之间的数的个数是奇数的话,这两个数之间有联系也是对答案没有影响的;同理枚举
k
k
k的时候也要保证分出来的两段区间是偶数个数。
代码:
ll n,m,dp[2100][2100];
bool vis[2100][2100];
vector<int>g[1100];
ll c[2100][2100];
void init(){
for(int i=0;i<2100;i++)
for(int j=0;j<=i;j++){
if(j==0) c[i][j]=1;
else c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
}
}
int main() {
init();
n=read,m=read;
rep(i,1,m){
int u=read,v=read;
if((v-u-1)%2) continue;
vis[u][v]=1;
if(abs(u-v)==1){
dp[u][v]=dp[v][u]=1;
}
}
for(int len=2;len<=2*n;len+=2){
for(int l=1;l+len-1<=2*n;l++){
int r=l+len-1;
if(vis[l][r]) dp[l][r]=dp[l+1][r-1];
for(int k=l+2;k<r;k+=2)
if(vis[k][r]){
dp[l][r]=(dp[l][r]+dp[l][k-1]*dp[k+1][r-1]%mod*c[(r-l+1)/2][(k-l)/2]%mod)%mod;
}
//cout<<dp[l][r]<<endl;
}
}
printf("%lld\n",dp[1][2*n]);
return 0;
}

这篇博客介绍了如何利用动态规划解决一个数学建模问题,即给定一定数量的数和它们之间的联系,计算在消除相邻有联系的数后的方案数。博主通过建立dp状态转移方程,考虑区间划分和组合计数的方法,实现了高效的算法,并给出了完整的C++代码实现。
504

被折叠的 条评论
为什么被折叠?



