正睿 25 年联赛联合训练 Day 14

正睿 25 年联赛联合训练 Day 14

前言

主播主播,你的正解确实很强,但还是太吃操作了。有没有更加简单又强势的算法推荐一下呢?

有的兄弟,有的,这么强的算法当然是不止一个了,一共有三位,都是当前版本 T0.5 的强势算法。掌握一个就能拿到 \(100+\) 的分数,三个全部掌握的话就可以阿克本场比赛了。

得分

T1 T2 T3 总分 排名
\(53\) \(60\) \(53\) \(166\) \(3/16\)

题解

改完题不笑的可以确诊玉玉症了。

T1 生成树

正解

我们考虑先把 \(b\) 边删掉,只保留 \(a\) 边。此时每一个连通块内只能走 \(a\) 边。然后我们只需要用一些 \(b\) 边把连通块连起来就行了。那么我们实际上可以通过跑最短路来解决,保证在同一连通块内不会走 \(b\) 边即可。

不过这样还不够,观察样例发现,如果我们重复到达同一个连通块两次是不符合条件的。我们没有什么好的方法去限制,那么就考虑状压一下。直接状压复杂度是 \(O(2^n\text{poly}(m))\) 的,过不去,不过进一步观察可以发现,对于大小 \(\le 3\) 的连通块,重复到达两次一定是不如直接走的,所以不需要管他们。那么只需要状压大小 \(\ge 4\) 的连通块即可,复杂度就降到了 \(O(m2^{\tfrac{n}{4}}\log n)\)

#include <bits/stdc++.h>

using namespace std;

const int Maxn = 2e3 + 5;
const int Inf = 2e9 + 5;

int n, m, a, b;
int head[Maxn], edgenum;
struct node {
	int nxt, to, w;
}edge[Maxn];
void add(int u, int v, int w) {
	edge[++edgenum] = {head[u], v, w}; head[u] = edgenum;
	edge[++edgenum] = {head[v], u, w}; head[v] = edgenum;
}

int id[Maxn], cnt;
int fa[Maxn], siz[Maxn];
void init() {for(int i = 1; i <= n; i++) fa[i] = i, siz[i] = 1;}
int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}
void merge(int x, int y) {
	x = find(x), y = find(y);
	if(x == y) return ;
	if(siz[x] > siz[y]) swap(x, y);
	fa[x] = y, siz[y] += siz[x];
}

int vis[80][1 << 17], dis[80][1 << 17]; 
struct Node {
	int w, x, s;
	bool operator < (const Node &b) const {return w > b.w;}
};
priority_queue <Node> q;

void dijkstra() {
	for(int i = 1; i <= n; i++) {
		for(int j = 0; j < (1 << cnt); j++) dis[i][j] = Inf;
	}
	q.push({0, 1, 0});
	dis[1][0] = 0;
	while(!q.empty()) {
		int x = q.top().x, s = q.top().s;
		q.pop();
		if(vis[x][s]) continue;
		vis[x][s] = 1;
		int P = find(x);
		for(int i = head[x]; i; i = edge[i].nxt) { 
			int to = edge[i].to;
			int Q = find(to), ns = s;
			if((edge[i].w == b && P == Q) || (id[Q] && ((s >> (id[Q] - 1)) & 1))) continue;
			if(edge[i].w == b && siz[P] >= 4) ns |= (1 << (id[P] - 1));
			if(dis[to][ns] > dis[x][s] + edge[i].w) {
				dis[to][ns] = dis[x][s] + edge[i].w;
				if(!vis[to][ns]) q.push({dis[to][ns], to, ns});
			}
		}
	}
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n >> m >> a >> b;
	init();
	for(int i = 1; i <= m; i++) {
		int u, v, w; cin >> u >> v >> w;
		add(u, v, w);
		if(w == a) merge(u, v);
	}
	for(int i = 1; i <= n; i++) {
		if(fa[i] == i) if(siz[i] >= 4) id[i] = ++cnt;
	}
	dijkstra();
	for(int i = 1; i <= n; i++) {
		int ans = Inf;
		for(int j = 0; j < (1 << cnt); j++) ans = min(ans, dis[i][j]);
		cout << ans << ' ';
	}
	return 0;
}

歪解

我们考虑直接枚举每一个点最后的最短路是由 \(pa+qb\) 组成的,那么我们只需要找到一条链符合这个条件,然后再跑一遍 Kruskal 看答案与最小生成树是否相同即可。这个可以直接用 BFS 在 \(O(n^3 m)\) 的复杂度内求得。不过我们可能有很多满足长度是 \(pa+qb\) 的链,只取一条是没有正确性的。考虑随机化,每次随机取一条链,这样随机几次后就有很大概率找到答案。复杂度 \(O(n^3 m)\),爆踩标算。

T2 树

Fun Fact:本题交互库并没有判断给出的 \(x\) 是否在 \([1,n]\) 范围内,所以只要有解就输出 TAK 0 就可以通过。

正解

首先容易发现每个限制对应的合法 \(x\) 的范围是一个连通块,那么我们就是要找出这些连通块交集中的一个点。这里有一个结论:我们只需要判断所有连通块深度最大的根是否满足条件即可。

证明如下:设该点为 \(p\),如果它不满足条件,那么说明有连通块完全在 \(p\) 子树内或完全在 \(p\) 子树外。如果它完全在子树内,说明 \(p\) 不是最深的根,矛盾;否则说明有两个连通块没有交集,显然交集一定为 \(0\),即无解。

用倍增求一下 LCA 和 \(k\) 级祖先即可,复杂度 \(O(n\log n)\)

#include <bits/stdc++.h>

using namespace std;

const int Maxn = 3e5 + 5;
const int Inf = 2e9;

int T;
int n, m;
int head[Maxn], edgenum;
struct node {
	int nxt, to;
}edge[Maxn << 1];

void add(int u, int v) {
	edge[++edgenum] = {head[u], v}; head[u] = edgenum;
	edge[++edgenum] = {head[v], u}; head[v] = edgenum;
}

int fa[Maxn][20], dep[Maxn];
void dfs(int x, int fth) {
	fa[x][0] = fth; dep[x] = dep[fth] + 1;
	for(int i = 1; i <= 19; i++) fa[x][i] = fa[fa[x][i - 1]][i - 1];
	for(int i = head[x]; i; i = edge[i].nxt) {
		int to = edge[i].to;
		if(to == fth) continue;
		dfs(to, x);
	}
}

int lca(int u, int v) {
	if(dep[u] < dep[v]) swap(u, v);
	for(int i = 19; i >= 0; i--) {
		if(dep[fa[u][i]] >= dep[v]) u = fa[u][i];
	}
	if(u == v) return u;
	for(int i = 19; i >= 0; i--) {
		if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
	}
	return fa[u][0];
}

int dis(int u, int v) {return dep[u] + dep[v] - 2 * dep[lca(u, v)];}

int kthfa(int x, int k) {
	for(int i = 19; i >= 0; i--) {
		if((k >> i) & 1) x = fa[x][i];
	}
	return !x ? 1 : x;
}

struct Node {
	int a, b, d;
}lim[Maxn];

void solve() {
	edgenum = 0;
	for(int i = 1; i <= n; i++) head[i] = 0;
	cin >> n >> m;
	for(int i = 1; i < n; i++) {
		int u, v; cin >> u >> v;
		add(u, v);
	}
	dfs(1, 0);
	int ans = 0;
	for(int i = 1; i <= m; i++) {
		int a, b, d; cin >> a >> b >> d;
		if(ans == Inf) continue;
		lim[i] = {a, b, d};
		int p = lca(a, b), dis = (d - (dep[a] + dep[b] - 2 * dep[p])) / 2;
		if(dis < 0) {ans = Inf;  continue;}
		p = kthfa(p, dis); ans = dep[p] > dep[ans] ? p : ans;
	}
	bool flg = 1;
	if(ans == Inf) {
		cout << "NIE\n"; return ;
	}
	for(int i = 1; i <= m; i++) {
		if(dis(ans, lim[i].a) + dis(ans, lim[i].b) > lim[i].d) flg = 0;
	}
	if(flg) cout << "TAK " << ans << '\n';
	else cout << "NIE\n";
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> T;
	while(T--) solve();
	return 0;
}

歪解

如果直接暴力判断的话复杂度显然是 \(O(nm)\) 的,考虑打乱枚举点和限制的顺序,这样可以直接拿到 \(90\) 分。

更牛的歪解

我们采取一个必要探路和贪心的措施。对于每一个限制,显然 \(d_i-dis(a_i,b_i)\) 越小限制越严格。那么对于每个点我们只取前几十个最严格的限制进行判断,先把不可能成为答案的点判掉。然后对于剩下的备选点进行随机取点,判断所有的条件,有限次数内如果有就直接输出,否则就认为无解。这样可以直接通过。

T3 种树

正解

考虑用 dp 来求解。我们以队伍为对象进行 dp,设 \(dp(i,j)\) 表示当前组建好所有大小 \(\ge i\) 的队伍,已经放好 \(j\) 个人的方案数。转移的时候枚举大小为 \(i\) 的队伍有几个,转移方程为:

\[dp(i,j+k\times i)\leftarrow dp(i+1,j)\times \binom{b_i-j}{k\times i}\times \frac{(k\times i)!}{(i!)^k} \]

其中 \(b_i\) 表示限制 \(\ge i\) 的人的数量。显然直接枚举的复杂度就是 \(O(n^2\log n)\) 的,可以通过。

歪解

上面说了以队伍为对象进行 dp,那么现在以人为对象也是可以的。首先对 \(a_i\) 从大到小排序,对于每一个队伍,在 \(a_i\) 最小的地方统计这个队伍的贡献。设 \(dp(i,j)\) 表示当前遍历到第 \(i\) 个人,已经放好 \(j\) 个人的方案数。转移方程为:

\[dp(i,j)= dp(i-1,j-1) +\sum dp(i-1,j-k) \binom{n-i-j+k}{k-1} \]

显然直接转移是 \(O(n^3)\) 的,无法通过。考虑后面的和式,发现它可以写成下面形式:

\[\frac{1}{(n-i-j+1)!}\sum dp(i-1,j-k)\times (n-i-(j-k))!\times \frac{1}{(k-1)!} \]

发现它可以分成两个部分,分别只与 \(j-k\)\(k\) 有关。发现这是下标和固定的形式,恰好模数是 \(998244353\),考虑利用 NTT 优化转移过程,这样就可以做到 \(O(n^2\log n)\) 了。

#include <bits/stdc++.h>

using namespace std;

const int Maxn = 4096 + 5;
const int Inf = 2e9;
const int Mod = 998244353;
const int Gen = 3;
const int InvG = 332748118;
int Add(int x, int y) {return x + y >= Mod ? x + y - Mod : x + y;}
void pls(int &x, int y) {x = Add(x, y);}
int Del(int x, int y) {return x - y < 0 ? x - y + Mod : x - y;}
void sub(int &x, int y) {x = Del(x, y);}
int qpow(int a, int b) {
	int res = 1;
	while(b) {
		if(b & 1) res = 1ll * res * a % Mod;
		a = 1ll * a * a % Mod; b >>= 1;
	}
	return res;
}
int inv(int a) {return qpow(a, Mod - 2);}

int n, a[Maxn], r[Maxn], len = 1;
int f[Maxn], g[Maxn], dp[Maxn][Maxn];

void init() {
	f[0] = 1; for(int i = 1; i <= n; i++) f[i] = 1ll * f[i - 1] * i % Mod;
	g[n] = inv(f[n]); for(int i = n; i >= 1; i--) g[i - 1] = 1ll * g[i] * i % Mod;
}

void NTT(int *a, int len, int typ) {
	for(int i = 0; i < len; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) * (len >> 1));
	for(int i = 0; i < len; i++) if(i < r[i]) swap(a[i], a[r[i]]);
	for(int h = 1; h < len; h <<= 1) {
		int cur = qpow(typ == 1 ? Gen : InvG, (Mod - 1) / (h << 1));
		for(int i = 0; i < len; i += (h << 1)) {
			int w = 1;
			for(int j = 0; j < h; j++, w = 1ll * w * cur % Mod) {
				int x = a[i + j], y = 1ll * w * a[i + j + h] % Mod;
				a[i + j] = Add(x, y);
				a[i + j + h] = Del(x, y);
			}
		} 
	}
	if(typ == -1) {
		int iv = inv(len);
		for(int i = 0; i < len; i++) a[i] = 1ll * a[i] * iv % Mod;
	}
}

int F[Maxn], G[Maxn];

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n;
	init();
	for(int i = 1; i <= n; i++) cin >> a[i];
	sort(a + 1, a + n + 1);
	dp[n + 1][0] = 1;
	for(int i = n; i >= 1; i--) {
		for(int j = 0; j < len; j++) F[j] = G[j] = 0;
		for(int j = 0; j <= n - i; j++) F[j] = 1ll * dp[i + 1][j] * f[n - i - j] % Mod;
		for(int j = 1; j <= min(a[i], n - i + 1); j++) G[j] = g[j - 1];
		int m = n - i + 1; len = 1;
		while(len <= (m << 1)) len <<= 1;
		NTT(F, len, 1), NTT(G, len, 1);
		for(int j = 0; j < len; j++) F[j] = 1ll * F[j] * G[j] % Mod;
		NTT(F, len, -1);
		for(int j = 0; j <= n - i + 1; j++) dp[i][j] = Add(1ll * F[j] * g[n - i - j + 1] % Mod, dp[i + 1][j]);
	}
	cout << dp[1][n] << '\n';
	return 0;
}
posted @ 2025-03-28 16:45  UKE_Automation  阅读(46)  评论(0)    收藏  举报