8.24 模拟赛 T3

题意:

给定一棵 \(n\) 个结点,以 \(1\) 为根的树,每个结点有点权 \(a_i\)。可以将若干个点染成红色。定义一个结点 \(u\) 是好的,当且仅当:

  • \(u\) 是红色。
  • \(u\) 子树内(包括自身)有 \(k\) 个权值和 \(u\) 权值相等红色结点,其中 \(k\) 为给定值。

对于所有的 \(1 \le i \le n\),求出树中有 \(i\) 个好的结点的染色方案。
认为两种方案不同,当且仅当存在一个结点在一个方案中是红色,另一个方案中没有染色。
\(1 \le k \le n \le 3000\),答案对 \(10^9+7\) 取模。

一眼的题,为什么场上写挂了。

首先可以观察到各种权值之间是独立的。也就是说,我们可以分开求出各种颜色的方案,然后背包背起来。
这里分开求方案,可以建虚树,在虚树上 DP 保证复杂度。

然后观察到一个性质:如果一个结点 \(u\) 是好的,那么它的一个红色祖先一定也是好的。

然后就可以 DP 方案数:

  • \(f_{u, i}\) 表示考虑以 \(u\) 为根的子树内,有 \(i\) 个好的结点。
  • \(g_{u, i}\) 表示考虑以 \(u\) 为根的子树内,有 \(i\) 个红色结点并且强制要求没有好的结点。注意,强制要求没有好的结点并不等价于 \(i \le k\)

建了虚树,转移略微困难。
虚树上的点分为关键点和辅助点。对于关键点,自然是可以控制染红或者不染红的。但是辅助点只能不染红,辅助点的作用是合并子结点的状态,并不是用来贡献新的方案数的。
所以分类讨论一个结点是否被染红。

  • 不染红:
    暴力背包,把子结点的 \(f\)\(g\) 都背进来就行了。
    这里不会影响 \(g\) 的【强制没有好的结点的性质】,因为 \(u\) 没有被染色,自然不是好的。
  • 染红:
    多分几步。
    首先背包的初始状态是,\(f_{u,0}=1,g_{u,1}=1\)
    接着,仍然是暴力背包合并。
    合并完之后注意,\(u\) 可能是一个好的结点。
    如果儿子里面有好的结点,自然 \(u\) 是好的,所以可以对 \(f_{u, i},i \ge 2\) 部分做右移操作,即 \(f_{u,i}\gets f_{u, i-1}\)
    对于儿子里面没有好的结点的情况,就用 \(g\) 判断。不难得到 \(f_{u, 1} = \sum_{i=k}g_{u,i}\),再消除掉这部分的 \(g\) 的贡献。

这里有个细节,染红那一块要单独开数组 DP 防止干扰。

最后注意 \(f_{u,0}\) 的赋值,依据状态,\(f_{u,0}=\sum_{i=0}g_{u,i}\)

答案就是所有颜色的 \(f_{1, i}\) 背起来。

总时间复杂度 \(O(n^2)\),赛时代码改。

#include <bits/stdc++.h>
using namespace std;

//#define filename "occur" 
#define FileOperations() freopen(filename".in", "r", stdin), freopen(filename".out", "w", stdout)
//#define multi_cases 1

#define inf 0x3f3f3f3f
#define Linf 0x3f3f3f3f3f3f3f3f
#define pii pair<int, int> 
#define ull unsigned long long
#define all(v) v.begin(), v.end()
#define upw(i, a, b) for(int i = (a); i <= (b); ++i)
#define dnw(i, a, b) for(int i = (a); i >= (b); --i)

template<class T> bool vmax(T &a, T b) { return b > a ? a = b, true : false; }
template<class T> bool vmin(T &a, T b) { return b < a ? a = b, true : false; }
template<class T> void clear(T &x) { T().swap(x); }

const int N = 3002;

const int P = 1000000007;
void vadd(int &a, int b) { a += b; if(a >= P) a -= P; }

int n, k, L, R;
int a[N];

vector<int> G[N];
struct LCA {
	int sz[N], dep[N], son[N], par[N];
	void DFS1(int u, int fa) {
		sz[u] = 1, dep[u] = dep[fa] + 1, par[u] = fa;
		for(auto v : G[u]) if(v != fa) {
			DFS1(v, u);
			sz[u] += sz[v];
			if(sz[v] > sz[son[u]]) son[u] = v;
		}
	}
	int top[N];
	void DFS2(int u, int t) {
		top[u] = t;
		if(son[u]) DFS2(son[u], t);
		for(auto v : G[u]) if(v != par[u] && v != son[u]) DFS2(v, v);
	}
	int getLCA(int u, int v) {
		while(top[u] != top[v]) {
			if(dep[top[u]] < dep[top[v]]) swap(u, v);
			u = par[top[u]];
		}
		return dep[u] < dep[v] ? u : v;
	}
} C;

int dfn[N], num;
void get_dfn(int u, int fa) {
	dfn[u] = ++num;
	for(auto v : G[u]) if(v != fa) get_dfn(v, u);
}

vector<int> vt[N];	//虚树
vector<int> vec, p;
int mark[N];
void build() {
	auto cmp = [&] (int x, int y) { return dfn[x] < dfn[y]; };
	sort(all(vec), cmp);
	clear(p);
	upw(i, 1, (int)vec.size() - 1) p.push_back(C.getLCA(vec[i], vec[i-1]));
	for(auto u : p) mark[u] = 0;
	p.push_back(1), mark[1] = 0;
	for(auto u : vec) p.push_back(u), mark[u] = 1;
	
	sort(all(p), cmp), p.erase(unique(all(p)), p.end());
	upw(i, 1, (int)p.size() - 1) {
		int u = p[i], v = C.getLCA(p[i], p[i-1]);
		vt[u].push_back(v), vt[v].push_back(u);
	}
}

int f[N][N], g[N][N];
int sz[N];
void DFS(int u, int fa) {
	f[u][0] = g[u][0] = 1;
	sz[u] = 1;
	for(auto v : vt[u]) if(v != fa) {
		DFS(v, u);
		vector<int> tg(sz[u] + sz[v] + 1);
		upw(i, 0, sz[u]) upw(j, 0, sz[v]) vadd(tg[i + j], 1ll * g[u][i] * g[v][j] % P);
		upw(i, 0, sz[u] + sz[v]) g[u][i] = tg[i];
		
		vector<int> tf(sz[u] + sz[v] + 1);
		upw(i, 0, sz[u]) upw(j, 0, sz[v]) vadd(tf[i + j], 1ll * f[u][i] * f[v][j] % P);
		upw(i, 0, sz[u] + sz[v]) f[u][i] = tf[i];
		
		sz[u] += sz[v];
	}
	if(mark[u]) {	//u点染红的方案数,加法原理加起来
		vector<int> tmpf(n + 1), tmpg(n + 1);
		tmpf[0] = tmpg[1] = 1;
		sz[u] = 1;
		for(auto v : vt[u]) if(v != fa) {
			vector<int> tg(n + 1);
			upw(i, 0, sz[u]) upw(j, 0, sz[v]) vadd(tg[i + j], 1ll * tmpg[i] * g[v][j] % P);
			upw(i, 0, sz[u] + sz[v]) tmpg[i] = tg[i];
			
			vector<int> tf(n + 1);
			upw(i, 0, sz[u]) upw(j, 0, sz[v]) vadd(tf[i + j], 1ll * tmpf[i] * f[v][j] % P);
			upw(i, 0, sz[u] + sz[v]) tmpf[i] = tf[i];
			
			sz[u] += sz[v];
		}
		dnw(i, sz[u], 2) tmpf[i] = tmpf[i-1];
		tmpf[1] = 0;
		upw(i, k, sz[u]) vadd(tmpf[1], tmpg[i]);
		
		upw(i, 1, sz[u]) vadd(f[u][i], tmpf[i]);
		
		upw(i, 1, min(sz[u], k-1)) vadd(g[u][i], tmpg[i]);
	}
	
	f[u][0] = 0;
	upw(i, 0, sz[u]) vadd(f[u][0], g[u][i]);
}

int h[N << 1];	//背包背的时候,可能会2倍

void WaterM() {
	cin >> n >> L >> R >> k;
	upw(i, 1, n) cin >> a[i];
	
	vector<int> va{-1};
	upw(i, 1, n) va.push_back(a[i]);
	sort(all(va)), va.erase(unique(all(va)), va.end());
	upw(i, 1, n) a[i] = lower_bound(all(va), a[i]) - va.begin();
	int ctot = (int)va.size() - 1;
	
	upw(i, 2, n) {
		int fa;
		cin >> fa;
		G[fa].push_back(i), G[i].push_back(fa);
	}
	C.DFS1(1, 0), C.DFS2(1, 1);
	
	get_dfn(1, 0);
	
	int tot = 0;
	h[0] = 1;
	upw(val, 1, ctot) {
		
		upw(i, 1, n) clear(vt[i]);
		clear(vec);
		upw(i, 1, n) if(a[i] == val) vec.push_back(i);
		build();
		DFS(1, 0);
		
		
		vector<int> t(tot + sz[1] + 1);
		upw(i, 0, tot) upw(j, 0, sz[1]) vadd(t[i + j], 1ll * h[i] * f[1][j] % P);
		upw(i, 0, tot + sz[1]) h[i] = t[i];
		tot += sz[1];
		
		for(auto i : p) upw(j, 0, sz[i]) f[i][j] = g[i][j] = 0;
	}
	
	int ans = 0;
	upw(i, L, R) vadd(ans, h[i]);
	cout << ans << '\n';
}

signed main() {
#ifdef filename
	FileOperations();
#endif
	
	signed _ = 1;
#ifdef multi_cases
	scanf("%d", &_);
#endif
	while(_--) WaterM();
	return 0;
}


posted @ 2025-09-04 22:50  Water_M  阅读(11)  评论(0)    收藏  举报