AGC001 题解

A - BBQ Easy

先将 2N 个数排序,从大到小考虑,最大的数一定不会产生贡献,次大的数可以和最大的数捆绑在一起,并产生贡献,以此类推,这样的贪心正确性还是较为显然的。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll Read() {
	int sig = 1;
	ll num = 0;
	char c = getchar();
	while(!isdigit(c)) {
		if(c == '-') {
			sig = -1;
		}
		c = getchar();
	}
	while(isdigit(c)) {
		num = (num << 3) + (num << 1) + (c ^ 48);
		c = getchar();
	}
	return num * sig;
}
void Write(ll x) {
	if(x < 0) {
		putchar('-');
		x = -x;
	}
	if(x >= 10) {
		Write(x / 10);
	}
	putchar((x % 10) ^ 48);
}

const int N = 205;
int n, a[N];

int main() {
	int i;
	n = Read(), n <<= 1;
	for(i = 1; i <= n; i++) {
		a[i] = Read();
	}
	sort(a + 1, a + n + 1);
	int ans = 0;
	for(i = 1; i <= n; i += 2) {
		ans += a[i];
	}
	Write(ans), putchar('\n');
	return 0;
}

B - Mysterious Light

可以看到,从光反射第二次后开始后,以两轮为周期,光可能经过的范围总是为一个平行四边形,且光总是沿着平行四边形某个 120 的内角的角平分线射出。
比如样例的图:
image
设两条边长为 x,y。则对于一个平行四边形,只算它内部的光线总长,答案为:

f(x,y)={xif x=yf(y,x)if x<yf(xy,y)+2yotherwise.

显然初始两条光线总长为 N,则答案为 N+f(X,NX)O(N) 暴力计算可拿 300 分。
观察这个式子,首先可以令边界为 f(x,0)=x,可以证明两个边界是等价的,很像辗转相减法,可以优化为辗转相除法,即:

f(x,y)={xif y=0f(y,x)if x<yf(xmody,y)+2xyyotherwise.

观察到当 x<y 时,下面的式子等价于上面的式子,可得:

f(x,y)={xif y=0f(xmody,y)+2xyyotherwise.

#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll Read() {
	int sig = 1;
	ll num = 0;
	char c = getchar();
	while(!isdigit(c)) {
		if(c == '-') {
			sig = -1;
		}
		c = getchar();
	}
	while(isdigit(c)) {
		num = (num << 3) + (num << 1) + (c ^ 48);
		c = getchar();
	}
	return num * sig;
}
void Write(ll x) {
	if(x < 0) {
		putchar('-');
		x = -x;
	}
	if(x >= 10) {
		Write(x / 10);
	}
	putchar((x % 10) ^ 48);
}

ll Solve(ll x, ll y) {
	return y ? Solve(y, x % y) + 2ll * (x / y) * y : -x;
}
int main() {
	ll n = Read(), x = Read();
	Write(n + Solve(n - x, x));
	return 0;
}

C - Shorten Diameter

没看数据范围,结果一看 N 只有 2000
考虑暴力枚举直径的中心,对 K 的奇偶性进行分类讨论。

  • K 为奇数时,枚举一条边,将与边的两个端点的距离最小值小于等于 K12 的点删去。
  • K 为偶数时,枚举一个点,将与点的距离小于等于 K2 的点删去。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll Read() {
	int sig = 1;
	ll num = 0;
	char c = getchar();
	while(!isdigit(c)) {
		if(c == '-') {
			sig = -1;
		}
		c = getchar();
	}
	while(isdigit(c)) {
		num = (num << 3) + (num << 1) + (c ^ 48);
		c = getchar();
	}
	return num * sig;
}
void Write(ll x) {
	if(x < 0) {
		putchar('-');
		x = -x;
	}
	if(x >= 10) {
		Write(x / 10);
	}
	putchar((x % 10) ^ 48);
}
const int N = 2005;
int n, cnt = 0, k, o;
vector<int> e[N];
bool vis[N];
void Dfs(int u, int dis) {
	vis[u] = true;
	if(dis > k / 2) {
		cnt++;
	}
	for(auto v : e[u]) {
		if(vis[v] || v == o) {
			continue;
		}
		Dfs(v, dis + 1);
	}
}
int main() {
	int i;
	n = Read(), k = Read();
	vector<pair<int, int> > vec;
	for(i = 1; i < n; i++) {
		int u = Read(), v = Read();
		vec.emplace_back(u, v);
		e[u].push_back(v), e[v].push_back(u);
	}
	int ans = INT_MAX;
	if(k & 1) {
		for(auto p : vec) {
			int u = p.first, v = p.second;
			memset(vis, 0, sizeof(vis)), cnt = 0;
			o = v, Dfs(u, 0), o = u, Dfs(v, 0);
			ans = min(ans, cnt);
		}
	}
	else {
		for(i = 1; i <= n; i++) {
			memset(vis, 0, sizeof(vis)), cnt = 0;
			Dfs(i, 0);
			ans = min(ans, cnt);
		}
	}
	Write(ans);
	return 0;
}

D - Arrays and Palindrome

注意到:

  • 对于重叠的两个回文限制,长度分别为 x,x+1,且两个回文限制的初始位置(或结束位置,同理)相同,则可以推出这个长度为 x+1 的序列相同。
  • 对于重叠的两个回文限制,长度均为 xx 是偶数,且两个回文限制的初始位置(或结束位置,同理)相差 1,则可以推出这个长度为 x+1 的序列相同。
  • 对于一个长度为 x 的回文限制,最多会产生 x2 个两个数相等的限制条件,而判断 n 个数相等至少需要 n1 个限制条件,所以若 A 中有三个及以上的奇数一定无解。
  • 否则,把奇数尽量排在边缘是最优的。

可以构造:

bi={ai1if i=1aM+1if i=Maiotherwise.

特判 a1=1 的情况。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll Read() {
	int sig = 1;
	ll num = 0;
	char c = getchar();
	while(!isdigit(c)) {
		if(c == '-') {
			sig = -1;
		}
		c = getchar();
	}
	while(isdigit(c)) {
		num = (num << 3) + (num << 1) + (c ^ 48);
		c = getchar();
	}
	return num * sig;
}
void Write(ll x) {
	if(x < 0) {
		putchar('-');
		x = -x;
	}
	if(x >= 10) {
		Write(x / 10);
	}
	putchar((x % 10) ^ 48);
}

const int N = 100005;
int n, m, a[N];

int main() {
	int i, cnto = 0;
	n = Read(), m = Read();
	for(i = 1; i <= m; i++) {
		a[i] = Read();
		if(a[i] & 1) {
			cnto++;
		}
	}
	if(m == 1) {
		if(a[1] == 1) {
			printf("1\n1\n1");
		}
		else {
			printf("%d\n2\n%d %d", a[1], a[1] - 1, 1);
		}
		return 0;
	}
	if(cnto > 2) {
		printf("Impossible");
		return 0;
	}
	for(i = 1; i <= m; i++) {
		if(a[i] & 1) {
			swap(a[1], a[i]);
			break;
		}
	}
	for(i = m; i; i--) {
		if(a[i] & 1) {
			swap(a[m], a[i]);
			break;
		}
	}
	vector<int> b;
	if(a[1] > 1) {
		b.push_back(a[1] - 1);
	}
	for(i = 2; i < m; i++) {
		b.push_back(a[i]);
	}
	b.push_back(a[m] + 1);
	for(i = 1; i <= m; i++) {
		Write(a[i]), putchar(' ');
	}
	int k = b.size();
	putchar('\n'), Write(k), putchar('\n');
	for(i = 0; i < k; i++) {
		Write(b[i]), putchar(' ');
	} 
	return 0;
}

E - BBQ Hard

直接做是不好做的。
注意到组合数,因此考虑这个东西的组合意义。
首先有:

i=1Nj=i+1N(ai+bi+aj+bjai+aj)=(i=1Nj=1N(ai+bi+aj+bjai+aj))i=1N(2ai+2bi2ai)2

对于 1i,jN(ai+bi+aj+bjai+aj) 即为从 (0,0) 出发,每次只能向右、向上走一个单位长度,走到 (ai+aj,bi+bj) 的方案数。
将点向左平移 ai 个单位长度,向下平移 bi 个单位长度,(ai+bi+aj+bjai+aj) 即为从 (ai,bi) 出发,每次只能向右、向上走一个单位长度,走到 (aj,bj) 的方案数。
因此考虑 DP,初始给每个 (ai,bi)1 的系数,再在 (ai,bi) 处统计贡献。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll Read() {
	int sig = 1;
	ll num = 0;
	char c = getchar();
	while(!isdigit(c)) {
		if(c == '-') {
			sig = -1;
		}
		c = getchar();
	}
	while(isdigit(c)) {
		num = (num << 3) + (num << 1) + (c ^ 48);
		c = getchar();
	}
	return num * sig;
}
void Write(ll x) {
	if(x < 0) {
		putchar('-');
		x = -x;
	}
	if(x >= 10) {
		Write(x / 10);
	}
	putchar((x % 10) ^ 48);
}

const int N = 200005, M = 4005;
const ll Mod = 1e9 + 7;
ll f[M][M], fact[N], invfact[N], inv[N];
void Init(int n) {
	int i;
	inv[1] = 1;
	for(i = 2; i <= n; i++) {
		inv[i] = Mod - Mod / i * inv[Mod % i] % Mod;
	}
	fact[0] = invfact[0] = 1;
	for(i = 1; i <= n; i++) {
		fact[i] = fact[i - 1] * i % Mod;
		invfact[i] = invfact[i - 1] * inv[i] % Mod;
	}
}
ll C(ll n, ll m) {
	if(n < m || n < 0) {
		return 0;
	}
	return fact[n] * invfact[m] % Mod * invfact[n - m] % Mod;
}
int n, a[N], b[N];
ll QuickPow(ll x, ll y) {
	if(y == 0) {
		return 1;
	}
	if(y == 1) {
		return x;
	}
	ll half = QuickPow(x, y >> 1);
	if(y & 1) {
		return half * half % Mod * x % Mod;
	}
	return half * half % Mod;
}

int main() {
	Init(N - 5);
	int i, j;
	n = Read();
	for(i = 1; i <= n; i++) {
		a[i] = Read(), b[i] = Read();
		f[2001 - a[i]][2001 - b[i]]++;
	}
	for(i = 1; i <= 4002; i++) {
		for(j = 1; j <= 4002; j++) {
			f[i][j] = (f[i][j] + f[i - 1][j] + f[i][j - 1]) % Mod;
		}
	}
	ll ans = 0;
	for(i = 1; i <= n; i++) {
		ans = (ans + f[2001 + a[i]][2001 + b[i]]) % Mod;
		ans = (ans - C(2 * a[i] + 2 * b[i], a[i] * 2) + Mod) % Mod;
	}
	Write(ans * QuickPow(2, Mod - 2) % Mod);
	return 0;
}

F - Wide Swap

参考 @linghuchong_ 的神仙题解
对于排列 P,我们定义 QPi=i
显然在排列 P 中,若有 |PiPj|=1,则在排列 Q 中,ij 一定相邻。
问题转化为,每次可以交换 Q 中相邻的两个数 Qi,Qi+1,需满足 |QiQi+1|K
对于字典序最小的排列 P,一定有越小的数坐标越靠前,因此我们的目标还是让 Q 的字典序最小。
考虑如何对 Q 进行“排序”,显然可以有一个 O(N2) 的类冒泡排序。
做到 O(NlogN) 则是归并的思想。
对于两个已经按上述规则“排序”的序列,假设为序列 a,b,如下图,要将它们归并成序列 c
image
现在如果当前 a 的第一个数需要插到 c 的末尾,显然是可行的,所以当 ai<bj 时,也就是将 ai 插到 c 的末尾更优时一定这么做。
否则,假设当前 b 的第一个数要插到 c 的末尾,考虑有哪些数会阻挡它,显然是 a 剩下的数:
image
a 剩下的数会阻挡它,当且仅当下面的条件成立:

  • k[i,|a|]|akbj|<K

反之,a 剩下的数不会阻挡它,当且仅当下面的条件成立:

  • k[i,|a|]|akbj|K

这一条件可以拆成两个条件(两个条件成立一个即可):

  • (mink=i|a|ak)bjK
  • bj(maxk=i|a|ak)K

其中,第二种情况若满足,则 ai<bj 必然成立,不满足 ai>bj 的前提,所以只要满足第一种情况就可以了。
等一下,这个条件貌似漏了一些情况,若满足上述条件,a 剩下的数一定不会阻挡它,但是反过来就不对了。
即,a 可以不满足上述情况,但是 bj 在转移的也可以不受阻挡,并且 ai 还是可以大于 bj(存在一个 kkiak<bjK)。
但实际上我们可以证明,不存在不满足上述情况的且已被“排序”的 a,证明如下:

假设存在满足上述情况且已被“排序”的 a,那么 a 可以被一定分成两个集合 S1,S2,使得 (maxxS1x)+Kbj,且 (minxS2x)Kbj
这样的话,xS1,yS2yx2K>K
因此 S1 的数不能被 S2 中的数阻挡,而 S1 的数一定小于 S2 的数,所以 S1 的数应该被交换到最前面,所以 a 并未被完全排序,矛盾。

因此,只要 (mink=i|a|ak)bjK 满足,bj 就一定可以被插入到 c 的末尾,且 bj 插入到 c 的末尾一定是最优的(此时 ai>bj 显然成立)。
否则,我们只能把 ai 插入到 c 的末尾。
代码实现时,可以预处理 a 的后缀最小值,来判断 bj 是否应该被插入到 c 的末尾。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll Read() {
	int sig = 1;
	ll num = 0;
	char c = getchar();
	while(!isdigit(c)) {
		if(c == '-') {
			sig = -1;
		}
		c = getchar();
	}
	while(isdigit(c)) {
		num = (num << 3) + (num << 1) + (c ^ 48);
		c = getchar();
	}
	return num * sig;
}
void Write(ll x) {
	if(x < 0) {
		putchar('-');
		x = -x;
	}
	if(x >= 10) {
		Write(x / 10);
	}
	putchar((x % 10) ^ 48);
}

const int N = 500005;
int n, K, p[N], q[N], t[N], minn[N];
void Merge(int l, int mid, int r) {
	minn[mid + 1] = N;
	int i, j, k;
	for(i = mid; i >= l; i--) {
		minn[i] = min(minn[i + 1], q[i]);
	}
	i = l, j = mid + 1, k = l;
	while(i <= mid && j <= r) {
		if(minn[i] - K >= q[j]) {
			t[k++] = q[j++];
		}
		else {
			t[k++] = q[i++];
		}
	}
	while(i <= mid) {
		t[k++] = q[i++];
	}
	while(j <= r) {
		t[k++] = q[j++];
	}
	for(i = l; i <= r; i++) {
		q[i] = t[i];
	}
}
void Solve(int l, int r) {
	if(l >= r) {
		return ;
	}
	int mid = (l + r) >> 1;
	Solve(l, mid), Solve(mid + 1, r), Merge(l, mid, r);
}

int main() {
	int i;
	n = Read(), K = Read();
	for(i = 1; i <= n; i++) {
		p[i] = Read(), q[p[i]] = i;
	}
	Solve(1, n);
	for(i = 1; i <= n; i++) {
		p[q[i]] = i;
	}
	for(i = 1; i <= n; i++) {
		Write(p[i]), putchar('\n');
	}
	return 0;
}
posted @   Include_Z_F_R_qwq  阅读(16)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示