简要题意:求树上所有联通块中第k大的数的和。
正解是肯定不会的……只会暴力……
考虑一个简单的暴力,枚举当前第k大的数w,之后在树上跑树形背包,然后计算当前有多少个联通块满足至少k个元素大于w。
这个算法会超时。然后这里有一些玄学的优化……
首先如果一共也攒不够k个联通块就可以不DP了。然后这里我们考虑用(f[i][j])表示以i为根,满足联通块中有至少j个大于当前枚举的值的情况,这样转移的方程是:(f[x][min(j+p,k)] = f[x][min(j+p,k)] + f[e[i].to][p] * f[x][j]) 务必注意的是,(f[x][j])在DP过程中值会变化。所以要开临时变量存。
听LK说,第一个优化在k大的时候有用第二个在k小的时候有用……然后结合上7秒的实现再开O2就过了……
#include<bits/stdc++.h>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('
')
#define pr pair<int,int>
#define mp make_pair
#define fi first
#define sc second
using namespace std;
typedef long long ll;
const int mod = 64123;
const int M = 200005;
const int N = 10000005;
int read()
{
int ans = 0,op = 1;char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') op = -1;ch = getchar();}
while(ch >='0' && ch <= '9') ans = ans * 10 + ch - '0',ch = getchar();
return ans * op;
}
struct edge
{
int next,to,from;
}e[M];
int inc(int a,int b){return (a+b) % mod;}
int mul(int a,int b){return 1ll * a * b % mod;}
int n,k,w,d[M],head[M],ecnt,x,y,val[M],f[3000][5000],ans;
bool can[M];
void add(int x,int y)
{
e[++ecnt] = {head[x],y,x};
head[x] = ecnt;
}
void dfs(int x,int fa)
{
val[x] = can[x];
rep(i,0,k) f[x][i] = 0;
f[x][can[x]] = 1;
for(int i = head[x];i;i = e[i].next)
{
if(e[i].to == fa) continue;
dfs(e[i].to,x);
per(j,val[x],0)
{
int cur = f[x][j];
per(p,val[e[i].to],0)
f[x][min(j+p,k)] = inc(f[x][min(j+p,k)],mul(f[e[i].to][p],cur));
}
val[x] = min(k,val[x] + val[e[i].to]);
}
}
int main()
{
n = read(),k = read(),w = read();
rep(i,1,n) d[i] = read();
rep(i,1,n-1) x = read(),y = read(),add(x,y),add(y,x);
rep(q,1,w)
{
int cnt = 0;
rep(i,1,n) cnt += (can[i] = (d[i] >= q));
if(cnt < k) break;
dfs(1,0);
//rep(i,1,n) printf("#%d ",f[i][k]);
rep(i,1,n) ans = inc(ans,f[i][k]);
}
printf("%d
",ans);
return 0;
}