zoukankan      html  css  js  c++  java
  • 【LOJ6172】Samjia 和大树(树形DP+猜结论)

    点此看题面

    大致题意: 给定一棵树,你可以给每个点赋一个(1sim m)之间的整数权值,要求所有相邻点权值之差的绝对值(ge k)。求方案数。

    暴力(DP)

    首先我们考虑暴力(DP),显然就是设(f_{i,j})表示第(i)个点权值为(j)时,填完(i)子树的方案数。

    前缀和/后缀和优化一下转移,就可以做到(O(Tnm))

    但由于(m)过大,这个方法无法接受。

    猜结论

    根据数据范围,盲猜可以把值域通过一些奇奇怪怪的方式压到(O(nk))级别。

    然后思索了半个小时,最后猜出一个结论:(DP)数组除了左右两侧的(nk)个元素之外,中间那一大串的值都是相等的。

    会猜出这个结论自然是有一定依据的,不过更多是感性的臆想。。。

    但结论的验证是非常简单的,只要用暴力(DP)随便造个数据打个表即可。

    有了这个结论,剩下的应该就非常简单了吧。

    (Upt)

    • 这个猜想实际上可以通过归纳法来证明。
    • 其实还容易发现,(DP)数组还是对称的,因此完全可以写得更简单,是我写复杂了。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100
    #define S 10000
    #define X 1000000007
    #define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
    #define Inc(x,y) ((x+=(y))>=X&&(x-=X))
    using namespace std;
    int n,m,k,ee,lnk[N+5];struct edge {int to,nxt;}e[2*N+5];
    namespace BF//小数据跑暴力
    {
    	int f[N+5][2*S+5],g1[2*S+5],g2[2*S+5];
    	I void DP(CI x,CI lst=0)
    	{
    		RI i,j;for(i=1;i<=m;++i) f[x][i]=1;for(i=lnk[x];i;i=e[i].nxt) if(e[i].to^lst)
    		{
    			for(DP(e[i].to,x),j=1;j<=m;++j) g1[j]=(g1[j-1]+f[e[i].to][j])%X;
    			for(j=m;j;--j) g2[j]=((j==m?0:g2[j+1])+f[e[i].to][j])%X;
    			for(j=1;j<=m;++j) f[x][j]=1LL*f[x][j]*
    				(0LL+(j>k?g1[j-k]:0)+(j+k<=m?g2[j+k]:0)+(k?0:X-f[e[i].to][j]))%X;
    		}
    	}
    	I void Solve()
    	{
    		RI i,t=0;for(DP(1),i=1;i<=m;++i) Inc(t,f[1][i]);printf("%d
    ",t);
    	}
    }
    namespace TreeDP//大数据利用猜得的结论
    {
    	int w,s,f[N+5][2*S+5],g1[2*S+5],g2[2*S+5];
    	I int Get1(CI x)//求前缀和,分三类讨论(其实我写复杂了)
    	{
    		if(x<=S) return g1[x];if(x<=m-S) return (1LL*(x-S)*s+g1[S])%X;
    		return (1LL*s*(m-w)+g1[x-(m-S)+S+1])%X;
    	}
    	I int Get2(CI x)//求后缀和,同样分三类讨论
    	{
    		if(x>=m-S) return g2[x-(m-S)+S+1];if(x>S) return (1LL*(m-S-x)*s+g2[S+1])%X;
    		return (1LL*s*(m-w)+g2[x])%X;
    	}
    	I void DP(CI x,CI lst=0)//动态规划
    	{
    		#define T(x) (((x)>k?Get1((x)-k):0)+((x)+k<=m?Get2((x)+k):0))
    		RI i,j;for(i=1;i<=w;++i) f[x][i]=1;for(i=lnk[x];i;i=e[i].nxt) if(e[i].to^lst)//枚举子节点
    		{
    			DP(e[i].to,x),s=f[e[i].to][S+1];//先DP子节点
    			for(j=1;j<=w;++j) g1[j]=(g1[j-1]+f[e[i].to][j])%X;//前缀和
    			for(j=w;j;--j) g2[j]=((j==w?0:g2[j+1])+f[e[i].to][j])%X;//后缀和
    			for(j=1;j<=S+1;++j) f[x][j]=1LL*f[x][j]*T(j)%X;//DP
    			for(j=1;j<=S;++j) f[x][S+1+j]=1LL*f[x][S+1+j]*T(m-S+j)%X;//DP
    		}
    	}
    	I void Solve()
    	{
    		RI i,t=0;for(w=2*S+1,DP(1),i=1;i<=w;++i) Inc(t,f[1][i]);
    		printf("%d
    ",(1LL*f[1][S+1]*(m-w)+t)%X);//注意答案的输出
    	}
    }
    I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}//快速幂
    int main()
    {
    	RI Tt,i,x,y;scanf("%d",&Tt);W(Tt--)
    	{
    		for(scanf("%d%d%d",&n,&m,&k),ee=0,i=1;i<=n;++i) lnk[i]=0;//清空
    		for(i=1;i^n;++i) scanf("%d%d",&x,&y),add(x,y),add(y,x);//建树
    		if(!k) printf("%d
    ",QP(m,n));else if(m<=2*S) BF::Solve();else TreeDP::Solve();//分情况采用不同的解法
    	}return 0;
    }
    
  • 相关阅读:
    杭电 1521 排列组合

    杭电 1799 循环多少次?
    杭电1028 Ignatius and the Princess III(整数拆分)
    毕业论文A.1 Matlab环境配置
    Open Graphics Library初步_搭建环境_GLUT
    Open Graphics Library初步_摄影机_GLUT
    C#使用office相关com组件
    插入排序
    二叉树的四种遍历方法(递归、迭代)
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/LOJ6172.html
Copyright © 2011-2022 走看看