2019CCPC-江西省赛

Contest Info


[Practice Link](https://cn.vjudge.net/contest/313014)
Solved A B C D E F G H I J K
9/11 O - Ø O - O O O O O O
  • O 在比赛中通过
  • Ø 赛后通过
  • ! 尝试了但是失败了
  • - 没有尝试

Solutions


A. Cotree

题意:
有两棵树,一共有n个点,现在要求在两棵树中连一条边,使得他们变成一棵树,如何连边使下式最小:

i=1nj=i+1ndis(i,j)

思路:
我们可以单独考虑边的贡献,一条边的贡献是这条边两边的点数乘积。
那么新加的边的贡献是固定的。
我们考虑怎么减少已存在的边的贡献,其实我们可以感性的理解一下,我们最后选出的两个点相连,我们强制让它们成为他们各自树中的根,那么这么考虑:

  • f[i]表示以i为根的子树中的边的贡献
    那么转移有:

f[u]=vson[u]f[v]+sze[v](nsze[v])

  • g[i]表示以i为根的非子树中的边的贡献
    那么转移有:

g[u]=g[fa[u]]+f[fa[u]]f[u](sze[u])(nsze[u])+(sze[u]+S)(nsze[u]S)

其中S代表另外一棵树的大小,最后那两部分主要是ufa[u]那条边的贡献变了

代码:

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

#define ll long long
#define N 100010
int n, S, T;
vector <vector<int>> G;

int fa[N], sze[N];
ll f[N], g[N];
void DFS(int u) {
	sze[u] = 1;
	f[u] = 0; 
	for (auto v : G[u]) if (v != fa[u]) {
		fa[v] = u;
		DFS(v);
		sze[u] += sze[v];
		f[u] += f[v]; 
		f[u] += 1ll * sze[v] * (n - sze[v]);
	}
}

ll DFS2(int u, int rt, int S) {
	if (u != rt) {
		g[u] = g[fa[u]] + f[fa[u]] - f[u] + 1ll * (n - sze[u] - S) * (sze[u] + S) - 1ll * sze[u] * (n - sze[u]);
	} else {
		g[u] = 0;
	}
	ll res = f[u] + g[u]; 
	for (auto v : G[u]) if (v != fa[u]) {
		res = min(res, DFS2(v, rt, S)); 
	}
	return res;
}

int main() {
	while (scanf("%d", &n) != EOF) {
		G.clear(); G.resize(n + 1);
		for (int i = 1; i <= n; ++i) fa[i] = -1;
		for (int i = 1, u, v; i < n - 1; ++i) {
			scanf("%d%d", &u, &v);    
			G[u].push_back(v);
			G[v].push_back(u);
		}
		int rt[2]; rt[0] = 1;
		fa[1] = 1;
		DFS(1); S = sze[1]; T = n - S;
		for (int i = 1; i <= n; ++i) {
			if (fa[i] == -1) {
				rt[1] = i;
				fa[i] = i;
				DFS(i);
				break;
			}
		}
		ll res = DFS2(1, 1, T) + DFS2(rt[1], rt[1], S) + 1ll * S * T;
		printf("%lld\n", res);
	}
	return 0;
}

C.Trap

题意:
n根长度为ai的棍子,询问有多少种方案选出四条边,使得它们的gcd=1,并且能够组成一个等腰梯形。

思路:
考虑f(d)为长度为d的倍数的棍子组成的方案数,那么可以容斥原理有:

ans=i=1maxlenf(i)μ(i)

其中μ(i)为莫比乌斯函数,它的定义如下:

μ(n)={1n=1(1)kn=p1p2,pkn中无平方因数0n中有>1的平方因数

根据μ(n)的定义,我们发现当n中奇数个质因子的时候μ(n)=1,当n中有偶数个质因子的时候μ(n)=1,恰好是我们需要的容斥系数。
考虑怎么求解f(d),可以两个循环枚举上底和腰:

  • 下底的范围是(上底, 上底 + 腰 * 2)
  • 注意特判下底等于腰的情况和上底等于腰的情况,棍子是否足够

为什么下底的下界是上底 + 腰 * 2 ?
考虑等腰的那个等角越小的时候,下底越长,那么极限情况,就是角度为0的时候,这时候长度就是上底 + 腰 * 2, 但是这个长度不合法,所以是开区间。

代码:

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

#define ll long long
#define M 10010
int n, a[M], cnt[M];
vector <vector<int>> vec, fac;
int prime[M], check[M], mu[M], tot;

void init() {
	tot = 0;
	memset(check, 0, sizeof check);
	mu[1] = 1;
	fac.clear();
	fac.resize(M);
	for (int i = 2; i < M; ++i) {
		if (!check[i]) {
			prime[++tot] = i;
			mu[i] = -1;
		}
		for (int j = 1; j <= tot; ++j) {
			if (1ll * i * prime[j] >= M) break;
			check[i * prime[j]] = 1;
			if (i % prime[j] == 0) {
				mu[i * prime[j]] = 0;
				break;
			} else {
				mu[i * prime[j]] = -mu[i];
			}
		}
	}
	for (int i = 1; i < M; ++i) {
		for (int j = i; j < M; j += i) {
			fac[j].push_back(i);
		}
	}
}

int main() {
	init();
	while (scanf("%d", &n) != EOF) {
		vec.clear(); vec.resize(M);
		memset(cnt, 0, sizeof cnt);
		for (int i = 1; i <= n; ++i) {
			scanf("%d", a + i);
			++cnt[a[i]];
			for (auto it : fac[a[i]]) {
				vec[it].push_back(a[i]);
			}
		}
		ll res = 0;
		for (int i = 1; i < M; ++i) {
			if (vec[i].empty() || mu[i] == 0) continue;
			ll tmp = 0;
			sort(vec[i].begin(), vec[i].end());
			vec[i].erase(unique(vec[i].begin(), vec[i].end()), vec[i].end());
			int sze = vec[i].size(); 
			for (auto it : vec[i]) { //枚举上边
				int l = 0, r = 0;
				while (l < sze && vec[i][l] < it + 1) ++l;
				for (auto it2 : vec[i]) { //枚举腰 
					if (it != it2 && cnt[it2] >= 2) {
						while (r < sze - 1 && vec[i][r + 1] < it + 2 * it2) ++r;
						if (l < sze && r < sze && l <= r) {
							tmp += r - l + 1;
							if (vec[i][l] <= it2 && it2 <= vec[i][r]) {
								if (cnt[it2] < 3) --tmp;
							}
						}
					} else if (it == it2 && cnt[it] >= 3) {
						while (r < sze - 1 && vec[i][r + 1] < it + 2 * it2) ++r;
						if (l < sze && r < sze && l <= r) {
							tmp += r - l + 1;
						}	
					}
				}
			}
			res += tmp * mu[i];
		}
		printf("%lld\n", res);
	}
	return 0;
}

D.Wave

题意:
找一个最长的'wave',要求满足以下限制:

  • 序列长度大于等于2
  • 所有奇数位置上的数相同
  • 所有偶数位置上的数相同
  • 奇数位置和偶数位置的数不同

思路:
因为值域只有[1,100],那么暴力枚举奇数位置上的数和偶数位置上的数即可。
时间复杂度O(nc)

代码:

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

#define N 100010
int n, c, a[N];
int f[N][110], nx[110];

int main() {
	scanf("%d%d", &n, &c);	
	for (int i = 1; i <= n; ++i) scanf("%d", a + i);
	for (int i = 1; i <= c; ++i) nx[i] = n + 1, f[n + 1][i] = n + 1;
	for (int i = n; i >= 0; --i) {
		for (int j = 1; j <= c; ++j) {
			f[i][j] = nx[j];
		}
		if (i) nx[a[i]] = i;
	}
	int res = 0;
	for (int i = 1; i <= c; ++i) {
		for (int j = 1; j <= c; ++j) if (i != j) {
			int tmp = 0;
			int it = 0;
			while (it <= n) {
				it = f[it][i];
				if (it <= n) {
					tmp += 1;
				} else break;
				it = f[it][j];
				if (it <= n) {
					tmp += 1;
				} else break; 
			}
			if (tmp > 1) {
				res = max(res, tmp);	
			}
		}
	}
	printf("%d\n", res);
	return 0;
}

F.String

题意:
给出一个字符串,只包含'a', 'v', 'i', 'n', 要求从中等概率可重复的取出四个字符,问恰好是'avin'的概率是多少。

思路:
根据乘法原理计算即可。

代码:

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

#define ll long long
#define N 110
int n;
char s[N];
int cnt[220];

int main() {
	while (scanf("%d", &n) != EOF) {
		scanf("%s", s + 1);
		memset(cnt, 0, sizeof cnt);
		for (int i = 1; i <= n; ++i) ++cnt[s[i]];
		if (!cnt['a'] || !cnt['v'] || !cnt['i'] || !cnt['n']) {
			puts("0/1");
		} else {
			ll a = 1ll * cnt['a'] * cnt['v'] * cnt['i'] * cnt['n'];
			ll b = 1ll * n * n * n * n;
			ll G = __gcd(a, b);
			a /= G;
			b /= G;
			printf("%lld/%lld\n", a, b);
		}
	}
	return 0;
}

G. Traffic

题意:
在一个十字路口,有n辆东西走向的车,他们会在ai时刻到达,有m辆南北走向的车,他们会在bi时刻到达。问需要让m辆南北走向的车整体等待多少秒,使得他们的开始行动之后不会和东西走向的车相撞?

思路:
考虑aibi只有1000,那么等待时间不会超过1000,暴力枚举即可。

代码:

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

#define N 5100
int n, m, a[N], b[N];

bool ok(int x) {
	for (int i = 1; i <= m; ++i) {
		if (a[b[i] + x]) {
			return 0;
		}
	}
	return 1;
}

int main() {
	while (scanf("%d%d", &n, &m) != EOF) {
		memset(a, 0, sizeof a);
		for (int i = 1, x; i <= n; ++i) {
			scanf("%d", &x);
			a[x] = 1;
		}
		for (int i = 1; i <= m; ++i) scanf("%d", b + i);
		for (int i = 0; i <= 2000; ++i) {
			if (ok(i)) {
				printf("%d\n", i);
				break;
			}
		}
	}
	return 0;
}

H.Rng

题意:
考虑随机选择一个区间的过程:

  • 先从[1,n]等概率选择r
  • 再在[1,r]中等概率选择出l
    问随机选择两个区间,它们相交的概率?

思路:
考虑枚举r

  • 考虑R落在[r+1,n]的概率,即为:

1n2i=r+1nri

  • 考虑R落在[l,r]之间的概率,即为:

1n(1ri=1rri+1n)

代码:

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

#define ll long long
#define N 1000010
const ll p = 1e9 + 7;
ll qmod(ll base, ll n) {
	ll res = 1;
	while (n) {
		if (n & 1) {
			res = res * base % p;
		}
		base = base * base % p;
		n >>= 1;
	}
	return res;
}
int n;
ll f[N], g[N], inv[N]; 

ll Sf(int l, int r) {
	if (l > r) return 0;
	return (f[r] - f[l - 1] + p) % p;
}

ll Sg(int l, int r) {
	if (l > r) return 0;
	return (g[r] - g[l - 1] + p) % p; 
}

void add(ll &x, ll y) {
	x += y;
	if (x >= p) x -= p;
}

int main() {
	inv[1] = 1;
	for (int i = 2; i < N; ++i) inv[i] = inv[p % i] * (p - p / i) % p;
	for (int i = 1; i < N; ++i) {
		f[i] = (f[i - 1] + i) % p;
		g[i] = (g[i - 1] + inv[i]) % p;
	}
	while (scanf("%d", &n) != EOF) {
		ll res = 0;
		for (int i = 1; i <= n; ++i) {
			add(res, 1ll * i * inv[n] % p * inv[n] % p * Sg(i + 1, n) % p);
			add(res, 1ll * inv[i] * inv[n] % p * inv[n] % p * (1ll * i * (i + 1) % p - Sf(1, i) + p) % p);
		}
		printf("%lld\n", res);
	}
	return 0;
}

I. Budget

签到题。

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

#define ll long long
#define N 1100
int n;
char s[N];

ll get() {
	scanf("%s", s + 1);
	int len = strlen(s + 1); 
	int pos = -1;
	for (int i = 1; i <= len; ++i) {
		if (s[i] == '.') {
			pos = i;
			break;
		}
	}
	if (pos == -1 || len - pos < 3) return 0;
	int num = s[pos + 3] - '0';
	if (num <= 4) return -num;
	else return 10 - num; 
}

int main() {
	while (scanf("%d", &n) != EOF) {
		ll res = 0;
		for (int i = 1; i <= n; ++i) {
			res += get();
		}
		printf("%.3f\n", res * 0.001);
	}
	return 0;
}

J. Worker

题意:
n个销售点,有m个销售员,每个销售员在第i个销售点会产生ai的订单,问如何分配销售员使得每个销售点产生的订单相同。

思路:
首先单个销售点的订单量肯定是所有销售点ai的最小公倍数的倍数。
那么判断m能否整除其最小公倍数即可,然后按比例分配。

代码:

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

#define N 1100
#define ll long long
int n, a[N];
ll m, lcm;

void solve() {
	ll one = 0;
	for (int i = 1; i <= n; ++i) {
		one += lcm / a[i];
	}
	if (m % one) {
		puts("No");
		return;
	}
	ll cur = m / one;
	puts("Yes");
	for (int i = 1; i <= n; ++i) printf("%lld%c", cur * (lcm / a[i]), " \n"[i == n]);
}

int main() {
	while (scanf("%d%lld", &n, &m) != EOF) {
		for (int i = 1; i <= n; ++i) scanf("%d", a + i);
		lcm = a[1]; 
		for (int i = 2; i <= n; ++i) {
			lcm = lcm * a[i] / __gcd(lcm, 1ll * a[i]);
		}
		solve();
	}
	return 0;
}

K. Class

题意:
给出:

{x=a+by=ab

计算ab

思路:
签到题。

代码:

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

int main() {
	int x, y;
	while (cin >> x >> y) {
		int a = (x + y) / 2;
		int b = (x - y) / 2;
		cout << a * b << "\n";
	}
	return 0;
}
posted @   Dup4  阅读(1035)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示