那永恒没骗我 离散也没有
这份爱只是活在 另外时空
如果 每一声再见 会开启
一个新的空间
每做一个梦 是时空 偶尔的重叠
你好吗 离开的人啊
请帮我 相爱到剧终
\(~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\) ——周深 《花开忘忧》
题意
\(~~~~\) 圆上有 \(2n\) 个点,保证任意选出六个点连边不会交于一点,求有多少连边方案使得连完过后恰有 \(n-1\) 个交点且这些交点形成一棵树.
\(~~~~\) \(1\leq n\leq 20\).
题解
\(~~~~\) 我宣布ubuntu的输入法就算是fcitx也很难用,想办法安个搜狗。
\(~~~~\) AT 的题不出意外会有一个思路然后这个思路出了意外。
\(~~~~\) 这题一开始想成共有 \(n\) 个点,自然而然考虑到状压。然后发现可以不关心已经选了的点怎么连的边,因为只要新连边的两个端点跨过了其中一个端点即可。于是有了美好的 \(\mathcal{O(2^n n^3)}\) 的算法,然后发现是 \(2n\) ,然后自闭。
\(~~~~\) 然后顺着刚刚的思路,我们只需要关心一个区间的端点中间只包含了一个连出去的边,所以我们定义 \(dp[l,r,x]\) 表示 \([l,r]\) 区间内 \(x\) 向区间外连边,其他内部连的方案数。
\(~~~~\) 然后考虑怎么转移这样一个东西过来,枚举状态 \([l,r,i]\) 用 \(n^3\) ,然后我们枚举两个端点 \(j,k\) 表示这两个端点连边,那么这个端点就会与已经枚举的 \(x\) 有交,这也是唯一的交。然后我们来看更其他的点。可以发现要么这些边的端点在 \(j\) 两侧,要么在 \(i\) 两侧,要么在 \(k\) 两侧。更进一步地,存在两个分割点 \(p,q\) ,使得 \([l,p]\) 内的互相连边,\([p+1,q-1]\) 内的互相连边,\([q,r]\) 内的互相连边,并且甚至我们已经确定了连出去的点,所以我们就得到了三个子问题。
\(~~~~\) 所以 \(dp_{l,i,r}=dp_{p+1,i,q-1}\times dp_{q,k,r} \times dp_{l,j,p}\times [a_{j,k}=1]\) ,这就有了 \(n^7\) 的转移。
\(~~~~\) 不过这东西常数挺小然后可以过。
\(~~~~\) 令 \(g_{l,k,p}=\sum f_{l,j,p}\times [a_{j,k}=1]\) ,这可以在每个状态过后用 \(n^2\) 完成。
\(~~~~\) 最后只需要每次枚举完状态过后 \(n^2\) 就可以了.
\(~~~~\) 最后再吐槽一次输入法
代码
#include <bits/stdc++.h>
#define ll long long
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
T f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
x*=f;
}
char Map[45][45];
ll f[45][45][45],g[45][45][45];
int main() {
#ifndef ONLINE_JUDGE
freopen("input","r",stdin);
freopen("output","w",stdout);
#endif
int n;read(n);n*=2;
for(int i=1;i<=n;i++) scanf("%s",Map[i]+1);
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) Map[i][j]-='0';
for(int i=2;i<=n;i++)
{
f[i][i][i]=1;
for(int j=i+1;j<=n;j++) g[i][j][i]=Map[i][j];
}
for(int len=3;len<n;len+=2)
{
for(int l=2;l+len-1<=n;l++)
{
int r=l+len-1;
for(int p=l;p<=r;p+=2)
{
for(int q=r;q>=l;q-=2)
{
ll Tmp=0;
for(int k=q;k<=r;k++) Tmp+=g[l][k][p]*f[q][k][r];
for(int i=p+1;i<q;i++) f[l][i][r]+=f[p+1][i][q-1]*Tmp;
}
}
for(int i=l;i<=r;i++)
for(int j=i+1;j<=n;j++) g[l][j][r]+=Map[i][j]*f[l][i][r];
}
}
ll Ans=0;
for(int i=2;i<=n;i++) Ans+=Map[1][i]*f[2][i][n];
printf("%lld",Ans);
return 0;
}
标签:连边,没骗,然后,times,离散,枚举,端点,活在,dp
From: https://www.cnblogs.com/Azazel/p/16724588.html