按照*Miracle*的话来说,网上又多了一篇n^3暴力的题解
可能是因为很多猫题虽然很好,但是写正解性价比比较低?
直接做不可做,转化为统计贡献:$O(n)$枚举每个权值,直接统计第k大大于等于这个权值的联通块个数的和— —这样每个权值x恰会贡献x次。
将所有大于等于当前权值的点点权赋为1,其余点点权赋为零,然后就是$O(n^2)$树形背包:设$dp[i][j]$表示以i为根的子树里选出(新)点权和为j的联通块,且联通块必须包含i自身的方案数。
一些小小的卡常:unsigned int,减法取模,不够k个结束(这真的算卡常吗=。=)
正解需要生成函数知识,用整体DP的思想来做,线段树合并+拉格朗日插值
我们优化上面这个树形背包,考虑$f[i][j]$表示在以i为根的子树里选出点权和大于等于j的联通块数的生成函数,$g[i][j]$表示以i为根的子树所有点nde的$f[nde][j]$的和
f的转移是需要卷积的,g转移只需要加法,需要优化$f$的转移。先把f转成点值表达,这样就可以直接乘法了,最后再拉格朗日插值把多项式插出来。
转移是f的第二维对应位置相乘,然后用整体DP解决
上面四行都是我口胡的
1 // luogu-judger-enable-o2 2 #include<cstdio> 3 #include<cctype> 4 #include<cstring> 5 #include<algorithm> 6 #define uint unsigned int 7 using namespace std; 8 const int N=2048; 9 const uint mod=64123; 10 int n,k,w,t1,t2,cnt,tot; 11 int p[N],noww[2*N],goal[2*N]; 12 int val[N],pro[N],siz[N],sze[N]; 13 uint ans,dp[N][N]; 14 void Read(int &x) 15 { 16 x=0; char ch=getchar(); 17 while(!isdigit(ch)) 18 ch=getchar(); 19 while(isdigit(ch)) 20 x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); 21 } 22 void Add(uint &x,uint y) 23 { 24 x+=y; 25 if(x>=mod) x-=mod; 26 } 27 void Link(int f,int t) 28 { 29 noww[++cnt]=p[f]; 30 goal[cnt]=t,p[f]=cnt; 31 noww[++cnt]=p[t]; 32 goal[cnt]=f,p[t]=cnt; 33 } 34 void DFS(int nde,int fth) 35 { 36 register int i,j,h,g; 37 for(i=0;i<=sze[nde];i++) dp[nde][i]=0; 38 dp[nde][pro[nde]]=1,siz[nde]=pro[nde]; 39 for(i=p[nde];i;i=noww[i]) 40 if(goal[i]!=fth) 41 { 42 g=goal[i],DFS(g,nde); 43 for(j=siz[nde];~j;j--) 44 for(h=siz[g];~h;h--) 45 Add(dp[nde][j+h],dp[nde][j]*dp[g][h]%mod); 46 siz[nde]=siz[nde]+siz[g]; 47 } 48 for(i=k;i<=siz[nde];i++) Add(ans,dp[nde][i]); 49 } 50 int main() 51 { 52 register int i,j; 53 Read(n),Read(k),Read(w); 54 for(i=1;i<=n;i++) Read(val[i]); 55 for(i=1;i<n;i++) Read(t1),Read(t2),Link(t1,t2); 56 for(i=1;i<=w;tot=0,i++) 57 { 58 for(j=1;j<=n;j++) pro[j]=val[j]>=i,tot+=pro[j]; 59 if(tot<k) printf("%u",ans),exit(0); DFS(1,0),swap(siz,sze); 60 } 61 printf("%u",ans); 62 return 0; 63 }