【LOJ6172】Samjia 和大樹(樹形DP+猜結論)
阿新 • • 發佈:2020-07-21
大致題意: 給定一棵樹,你可以給每個點賦一個\(1\sim m\)之間的整數權值,要求所有相鄰點權值之差的絕對值\(\ge k\)。求方案數。
暴力\(DP\)
首先我們考慮暴力\(DP\),顯然就是設\(f_{i,j}\)表示第\(i\)個點權值為\(j\)時,填完\(i\)子樹的方案數。
字首和/字尾和優化一下轉移,就可以做到\(O(Tnm)\)。
但由於\(m\)過大,這個方法無法接受。
猜結論
根據資料範圍,盲猜可以把值域通過一些奇奇怪怪的方式壓到\(O(nk)\)級別。
然後思索了半個小時,最後猜出一個結論:\(DP\)陣列除了左右兩側的\(nk\)個元素之外,中間那一大串的值都是相等的。
會猜出這個結論自然是有一定依據的,不過更多是感性的臆想。。。
但結論的驗證是非常簡單的,只要用暴力\(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\n",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\n",(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\n",QP(m,n));else if(m<=2*S) BF::Solve();else TreeDP::Solve();//分情況採用不同的解法 }return 0; }