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

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

得分

T1 T2 T3 T4 总分 排名
\(80\) \(100\) \(70\) \(0\) \(250\) \(4/35\)
  • T1 忘了不能操作 \(1,n\)\(100\to 80\)

题解

T1 最大最小

简单题,显然考虑二分答案,有超出的部分就按下去,看能不能按完即可。复杂度 \(O(n\log V)\)

#include <bits/stdc++.h>

using namespace std;

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

int n, a[Maxn];
int b[Maxn];

bool check(int x) {
	for(int i = 1; i <= n; i++) b[i] = a[i];
	for(int i = 2; i < n; i++) {
		if(b[i] > x) {
			if(b[i] > b[i + 1] && b[i] > b[i - 1]) {
				int mx = max(b[i - 1], b[i + 1]);
				int num = min((b[i] - mx) >> 1, b[i] - x);
				b[i] -= num, b[i - 1] += num, b[i + 1] += num;
			}
		}
	}
	for(int i = 1; i <= n; i++) if(b[i] > x) return 0;
	return 1;
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n;
	for(int i = 1; i <= n; i++) {
		cin >> a[i];
	}
	int l = 0, r = 1e9, ans = 0;
	while(l <= r) {
		int mid = (l + r) >> 1;
		if(check(mid)) ans = mid, r = mid - 1;
		else l = mid + 1;
	}
	cout << ans << '\n';
	return 0;
}

T2 魔法树

考虑一个关键性质:每个点出现的间隔点数一定是一样的,并且等于它到根上每个点的儿子个数的乘积。于是我们就可以考虑递归的思路:先判断间隔点数不一样的情况,然后对所有出现两次以上的点求出它的间隔点数的 \(\gcd\),那么当前节点的儿子个数就是这个 \(\gcd\)

如果这个值是 \(1\) 那么显然不可行,返回不合法,否则继续向下递归;我们已经知道了当前根的儿子个数,那么就可以知道每个子树内数字的出现顺序,递归求解子问题即可。每次递归节点处至少减半,复杂度 \(O(m\log m)\)

#include <bits/stdc++.h>

using namespace std;

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

int T;
int n, a[Maxn];
int vis[Maxn], pos[Maxn], len[Maxn];

bool dfs(vector <int> &v) {
	bool flg = 1, sb = 0;
	int g = 0, cnt = 0;
	for(int i = 0; i < v.size(); i++) vis[v[i]] = pos[v[i]] = len[v[i]] = 0;
	for(int i = 0; i < v.size(); i++) {
		if(!vis[v[i]]) {
			vis[v[i]] = 1, pos[v[i]] = i;
			cnt++;
		}
		else {
			flg = 0; int num = i - pos[v[i]];
			pos[v[i]] = i;
			if(len[v[i]] == 0) len[v[i]] = num, g = __gcd(g, num);
			else {
				if(len[v[i]] != num) sb = 1;
			}
		}
	}
	if(flg || cnt == 1) return 1;
	if(sb || g == 1) return 0;
	vector <int> nxt[g];
	for(int i = 0; i < v.size(); i++) nxt[i % g].push_back(v[i]);
	vector<int>().swap(v);
	bool res = 1;
	for(int i = 0; i < g; i++) res &= dfs(nxt[i]);
	return res;
}

void solve() {
	cin >> n;
	vector <int> v(n);
	for(int i = 1; i <= n; i++) cin >> v[i - 1];
	bool ans = dfs(v);
	if(ans) cout << "Yes\n";
	else cout << "No\n";
}

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

T3 合并石子

这个第 \(k\) 大的期望非常的熟悉啊,我们直接上一个 Min-Max 容斥搞一搞:

\[E(k\max(S))=\sum_{T\in S,|T|\ge k} (-1)^{|T|-k}\binom{|T|-1}{k-1} E(\min(T)) \]

那么重点就在于求出所有子集大小为 \(|T|\) 的子集的最小值期望数。这个我们继续转化成所有大小为 \(|T|\) 的最小值和除以 \(\binom{n}{|T|}\)。现在的问题就是求 \(|T|\) 个数的最小值之和了。

我们先转化成最小值 \(x\) 乘上最小值为 \(x\) 的子集数量,考虑怎样求后者。我们再用一个经典容斥套路,转化为求出最小值 \(\ge x\) 的子集数量。此时我们发现,每个 \(A_i\) 匹配的 \(B_i\) 要满足 \(A_i+B_i\ge x\),排序后可求出每个 \(A_i\) 对应的可选的 \(B_i\) 是一段前缀,然后直接 dp,设 \(dp(i,j)\) 表示前 \(i\) 个数选出了 \(j\) 个,满足最小值 \(\ge x\) 的方案数。转移方程为:

\[dp(i,j)=dp(i-1,j)+dp(i-1,j-1)\times(pos_i -j+1) \]

实践中可以滚动数组,然后我们就可以求出答案了。

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int Maxn = 2e5 + 5;
const int Inf = 2e9;
const int Mod = 1e9 + 7;

int n, k, a[Maxn], b[Maxn];

int qpow(int a, int b) {
	int res = 1;
	while(b) {
		if(b & 1) res = res * a % Mod;
		a = a * a % Mod, b >>= 1;
	}
	return res;
}

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

int C(int n, int m) {
	if(n < m) return 0;
	return f[n] * g[m] % Mod * g[n - m] % Mod;
}

int A(int n, int m) {
	if(n < m) return 0;
	return f[n] * g[n - m] % Mod;
}

int pos[Maxn], dp[Maxn], sum[405][805];

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n >> k;
	init();
	int mx1 = 0, mx2 = 0;
	for(int i = 1; i <= n; i++) {
		cin >> a[i]; mx1 = max(mx1, a[i]);
	}
	for(int i = 1; i <= n; i++) {
		cin >> b[i]; mx2 = max(mx2, b[i]);
	}
	int ans = 0;
	sort(a + 1, a + n + 1, [](int x, int y){return x < y;});
	sort(b + 1, b + n + 1, [](int x, int y){return x > y;});
	for(int mn = 1; mn <= mx1 + mx2; mn++) {
		for(int i = 1; i <= n; i++) {
			pos[i] = upper_bound(b + 1, b + n + 1, mn - a[i], greater<int>()) - b - 1;
			dp[i] = 0;
		}
		dp[0] = 1;
		for(int i = 1; i <= n; i++) {
			for(int j = min(pos[i], i - 1); j >= 0; j--) {
				int num = pos[i] - j;
				dp[j + 1] = (dp[j + 1] + dp[j] * num % Mod) % Mod;
			}
		}
		for(int i = 1; i <= n; i++) {
			sum[i][mn] = (sum[i][mn] + dp[i] % Mod) % Mod;
		}
	}
	for(int i = k; i <= n; i++) {
		int num = 0;
		for(int j = 1; j <= mx1 + mx2; j++) {
			num = (num + (sum[i][j] - sum[i][j + 1] + Mod) % Mod * j) % Mod;
		}
		int res = C(i - 1, k - 1) * num % Mod * qpow(A(n, i), Mod - 2) % Mod;
		if((i - k) & 1) ans = (ans - res + Mod) % Mod;
		else ans = (ans + res) % Mod;
	}
	cout << ans << '\n';
	return 0;
}

T4 联通格子

把所有点离散化,然后这些点形成了一个网格图。此时点数为 \(196\),关键点数为 \(14\)。我们直接跑斯坦纳树加上一些常数优化就可以直接跑过了。由于斯坦纳树求出来的是边数,而要求的是点数,所以加一即可。

#include <bits/stdc++.h>

using namespace std;

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

int n, m, x[Maxn], y[Maxn];
int tx[Maxn], tot1, ty[Maxn], tot2;
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 mat(int i, int j) {return (i - 1) * tot2 + j;}
int dp[1 << 14][200];

queue <int> q;
bool vis[Maxn];
void SPFA(int S) {
	for(int i = 1; i <= m; i++) if(dp[S][i] != Inf) q.push(i), vis[i] = 1;
	while(!q.empty()) {
		int x = q.front(); q.pop();
		vis[x] = 0;
		for(int i = head[x]; i; i = edge[i].nxt) {
			int to = edge[i].to;
			if(dp[S][to] > dp[S][x] + edge[i].w) {
				dp[S][to] = dp[S][x] + edge[i].w;
				if(!vis[to]) q.push(to), vis[to] = 1;
			}
		}
	}
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n;
	for(int i = 1; i <= n; i++) {
		cin >> x[i] >> y[i];
		tx[++tot1] = x[i], ty[++tot2] = y[i];
	}
	sort(tx + 1, tx + tot1 + 1), sort(ty + 1, ty + tot2 + 1);
	tot1 = unique(tx + 1, tx + tot1 + 1) - tx - 1;
	tot2 = unique(ty + 1, ty + tot2 + 1) - ty - 1;
	for(int i = 1; i <= tot1; i++) {
		for(int j = 1; j <= tot2; j++) {
			if(j != tot2) add(mat(i, j), mat(i, j + 1), ty[j + 1] - ty[j]);
			if(i != tot1) add(mat(i, j), mat(i + 1, j), tx[i + 1] - tx[i]);
		}
	}
	m = tot1 * tot2;
	for(int i = 0; i < (1 << n); i++) for(int j = 1; j <= m; j++) dp[i][j] = Inf;
	for(int i = 1; i <= n; i++) {
		int px = lower_bound(tx + 1, tx + tot1 + 1, x[i]) - tx;
		int py = lower_bound(ty + 1, ty + tot2 + 1, y[i]) - ty;
		dp[1 << (i - 1)][mat(px, py)] = 0;
	}
	for(int S = 1; S < (1 << n); S++) {
		for(int T = S & (S - 1); T; T = (T - 1) & S) {
			if(T < (S ^ T)) break;
			for(int i = 1; i <= m; i++) dp[S][i] = min(dp[S][i], dp[T][i] + dp[T ^ S][i]);
		}
		SPFA(S);
	}
	int ans = Inf;
	for(int i = 1; i <= m; i++) ans = min(ans, dp[(1 << n) - 1][i]);
	cout << ans + 1 << '\n'; 
	return 0;
}
posted @ 2025-02-20 16:30  UKE_Automation  阅读(45)  评论(0)    收藏  举报