CF1060F Shrinking Tree
Posted miracevin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CF1060F Shrinking Tree相关的知识,希望对你有一定的参考价值。
DP好题
肯定考虑在压缩中找到不变的关系。
n很小,不妨枚举每个点成为最后编号的情况,并且把这个点rt作为根
其实概率是:p/(n-1)!*(1/2)^k这里的p是所有缩边的排列中,和rt合并有k次的方案数。
我们只计算p*(1/2)^k部分,最后统一除以(n-1)!
基础的想法:
f[x][k]x为根的子树,和x合并k次的方案数,但是由于不知道和父亲的边什么时候缩,无法转移
直接记录到父亲?g[x][k],和x的fa合并k次方案数?其实还不能转移。
问题就是在于和父亲的边e不知道什么时候缩
再考虑到,可能当父亲是rt的时候特殊考虑,
如下定义状态:
dp[x][k]x为根的子树的边序列,当x变成rt的时候,还有k条边没有合并,最终合并成一个rt的大点的概率和
g[x][k]x为根的子树+x到fa的e的边的序列g,当fa变成rt的时候,g总共还有k条边没有合并,最终合并成一个rt的大点的概率和
这种奇怪的设法,有点对未来承诺的意思,而且是直接加入了状态的定义里,并没有用数组某一维记录。
并且有一个明显的划分点:x或者fa变成rt,这样便于统计1/2概率的额外限制
至于e的合并,我们可以在g中考虑到。
我们把之前的合并k次,直接在dp值中进行计算即可。
e在合并的时候,x有没有变成rt,再利用之前的dp[son],两种情况讨论,可以求出g[son]
再用g[son]们,背包进行合并。
只要保证分配的新序列中,x变成rt之前的操作都在x变成rt之前。分别组合数分配即可。
ans[rt]=dp[rt][n-1]
O(n^4)
#include<bits/stdc++.h> #define reg register int #define il inline #define fi first #define se second #define mk(a,b) make_pair(a,b) #define numb (ch^‘0‘) #define pb push_back #define solid const auto & #define enter cout<<endl #define pii pair<int,int> using namespace std; typedef long long ll; template<class T>il void rd(T &x){ char ch;x=0;bool fl=false;while(!isdigit(ch=getchar()))(ch==‘-‘)&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb);(fl==true)&&(x=-x);} template<class T>il void output(T x){if(x/10)output(x/10);putchar(x%10+‘0‘);} template<class T>il void ot(T x){if(x<0) putchar(‘-‘),x=-x;output(x);putchar(‘ ‘);} template<class T>il void prt(T a[],int st,int nd){for(reg i=st;i<=nd;++i) ot(a[i]);putchar(‘\n‘);} namespace Modulo{ const int mod=998244353; int ad(int x,int y){return (x+y)>=mod?x+y-mod:x+y;} void inc(int &x,int y){x=ad(x,y);} int mul(int x,int y){return (ll)x*y%mod;} void inc2(int &x,int y){x=mul(x,y);} int qm(int x,int y=mod-2){int ret=1;while(y){if(y&1) ret=mul(x,ret);x=mul(x,x);y>>=1;}return ret;} } //using namespace Modulo; namespace Miracle{ const int N=55; double dp[N][N],g[N][N],tmp[N]; double C[N][N]; int n; double ans[N]; struct node{ int nxt,to; }e[2*N]; int hd[N],cnt; void add(int x,int y){ e[++cnt].nxt=hd[x]; e[cnt].to=y; hd[x]=cnt; } int sz[N]; void dfs(int x,int fa){ sz[x]=1; dp[x][0]=1.00; for(reg i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(y==fa) continue; dfs(y,x); for(reg j=0;j<=sz[y];++j){ g[y][j]=(double)(sz[y]-j)*dp[y][j]; for(reg k=0;k<j;++k){ g[y][j]+=(double)0.5*dp[y][k]; } } memset(tmp,0,sizeof tmp); for(reg k=0;k<sz[x];++k){ for(reg j=0;j<=sz[y];++j){ tmp[k+j]+=C[sz[x]+sz[y]-k-j-1][sz[x]-1-k]*C[k+j][k]*dp[x][k]*g[y][j]; } } sz[x]+=sz[y]; for(reg k=0;k<sz[x];++k) dp[x][k]=tmp[k]; } } int main(){ rd(n);int x,y; for(reg i=1;i<n;++i){ rd(x);rd(y);add(x,y);add(y,x); } C[0][0]=1; for(reg i=1;i<=n;++i){ C[i][0]=1; for(reg j=1;j<=i;++j){ C[i][j]=C[i-1][j-1]+C[i-1][j]; } } for(reg i=1;i<=n;++i){ memset(dp,0,sizeof dp); memset(g,0,sizeof g); dfs(i,0); ans[i]=dp[i][n-1]; } double jie=1; for(reg i=2;i<n;++i) jie*=i; for(reg i=1;i<=n;++i){ printf("%.10lf\n",ans[i]/jie); } return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* */
考虑x是不是rt,要进行讨论的。考虑e什么时候合并,也是要讨论的。
x是不是rt就成了分界点。e什么时候合并也是关键点,这样才对son的子树内序列做出了限制。也才能用上dp[son]转移。
所以状态就直接记录还剩下多少个边没有合并,这些边都要注意是否有1/2的概率限制。而且还可知道之前放入了多少边,有助于组合数分配转移。
以上是关于CF1060F Shrinking Tree的主要内容,如果未能解决你的问题,请参考以下文章