【AtCoder】Tokio Marine & Nichido Fire Insurance Programming Contest 2020

比赛链接

点击打开链接

官方题解

点击打开链接

Problem A. Nickname

输出 s 1 s 2 s 3 s_1s_2s_3 s1s2s3 即可。
时间复杂度 O ( ∣ S ∣ ) O(|S|) O(S)

#include
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
char s[MAXN];
int main() {
	scanf("%s", s + 1), s[4] = 0;
	printf("%s\n", s + 1);
	return 0;
}

Problem B. Tag

判断 T × ( V − W ) T\times (V-W) T×(VW) ∣ A − B ∣ |A-B| AB 的大小关系即可。
时间复杂度 O ( 1 ) O(1) O(1)

#include
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int main() {
	int a, v, b, w, t;
	read(a), read(v), read(b), read(w), read(t);
	int dist = abs(a - b);
	if (v > w && 1ll * t * (v - w) >= dist) puts("YES");
	else puts("NO");
	return 0;
}

Problem C. Lamps

考虑初始时 A i = 0 A_i=0 Ai=0 的情况,则经过 O ( L o g N ) O(LogN) O(LogN) 次操作后,应当有 A i = N A_i=N Ai=N
因此,有效操作的轮数不会超过 O ( L o g N ) O(LogN) O(LogN) 。模拟,直到 A i = N A_i=N Ai=N 或是操作次数用尽即可。

时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN)

#include
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int n, k, a[MAXN];
bool work() {
	static int s[MAXN];
	memset(s, 0, sizeof(s));
	for (int i = 1; i <= n; i++) {
		int l = max(1, i - a[i]), r = min(n, i + a[i]);
		s[l]++, s[r + 1]--;
	}
	bool res = false;
	for (int i = 1; i <= n; i++) {
		s[i] += s[i - 1];
		if (s[i] != a[i]) {
			res = true;
			a[i] = s[i];
		}
	}
	return res;
}
int main() {
	read(n), read(k);
	for (int i = 1; i <= n; i++)
		read(a[i]);
	for (int i = 1; i <= k; i++)
		if (!work()) break;
	for (int i = 1; i <= n; i++)
		printf("%d ", a[i]);
	printf("\n");
	return 0;
}

Problem D. Knapsack Queries on a tree

一种可行的暴力是记 d p i , j dp_{i,j} dpi,j 表示考虑节点 i i i 及其祖先,剩余 j j j 容量的背包的最优解。
d p i dp_{i} dpi 显然可以通过 O ( L ) O(L) O(L) 的扫描由 d p ⌊ i 2 ⌋ dp_{\lfloor\frac{i}{2}\rfloor} dp2i 转移得到。
该做法的预处理复杂度为 O ( N × L ) O(N\times L) O(N×L) ,单组询问的复杂度为 O ( 1 ) O(1) O(1)

另一种可行的暴力是直接枚举选取物品的集合。
由于树高不超过 O ( L o g N ) O(LogN) O(LogN) ,该做法的预处理复杂度为 O ( 1 ) O(1) O(1) ,单组询问的复杂度为 O ( N ) O(N) O(N)

α = O ( N ) \alpha=O(\sqrt{N}) α=O(N ) ,按照第一种方法计算 d p i    ( i ≤ α ) dp_{i}\;(i\leq \alpha) dpi(iα) ,则可以 O ( 1 ) O(1) O(1) 回答出现位置 ≤ α \leq \alpha α 的询问。
对于剩余询问,枚举 ≥ α \geq \alpha α 的物品是否选取,并用 d p dp dp 值计算 ≤ α \leq \alpha α 的物品的最优选取方案即可。

时间复杂度 O ( ( L + Q ) N ) O((L+Q)\sqrt{N}) O((L+Q)N )

#include
using namespace std;
const int MAXN = 3e5 + 5;
const int MAXV = 1e5 + 5;
const int MAXM = 1 << 9;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int a[MAXM][MAXV];
int n, m, r, q, v[MAXN], w[MAXN];
int query(int x, int y) {
	if (x <= m) return a[x][y];
	else {
		int ans = query(x / 2, y);
		if (y >= w[x]) chkmax(ans, v[x] + query(x / 2, y - w[x]));
		return ans;
	}
}
int main() {
	read(n), m = (1 << 9) - 1, r = 1e5;
	for (int i = 1; i <= n; i++)
		read(v[i]), read(w[i]);
	for (int i = 1; i <= m; i++) {
		int f = i / 2;
		for (int j = 1; j <= r; j++) {
			a[i][j] = a[f][j];
			if (j >= w[i]) chkmax(a[i][j], v[i] + a[f][j - w[i]]);
		}
	}
	read(q);
	for (int i = 1; i <= q; i++) {
		int x, y; read(x), read(y);
		printf("%d\n", query(x, y));
	}
	return 0;
}

Problem E. O(rand)

不妨认为 S = 0 , T = 2 k − 1 S=0,T=2^k-1 S=0,T=2k1 ,且 S ≤ A i ≤ T S\leq A_i\leq T SAiT
则题目中的要求等价于:对于每一个二进制位,选出的集合既有 0 0 0 ,也有 1 1 1

考虑枚举选择的集合中第一个元素,则对于每个二进制位,已经存在了 0 , 1 0,1 0,1 中的一者。
剩余问题可转换为:选择一个集合,使得集合的二进制与的结果为 0 0 0 ,求方案数。
则用 FWT 配合容斥原理解决转化后的问题即可。

时间复杂度 O ( N × V L o g V + N 2 ) O(N\times VLogV+N^2) O(N×VLogV+N2)
官方题解中,给出了一种复杂度更为优秀的解法。

#include
using namespace std;
const int MAXN = 55;
const int MAXV = 1 << 18;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
ll coef[MAXN], binom[MAXN][MAXN];
int cnt[MAXV], bits[MAXV];
int m, goal, n, k, s, t, bit[MAXN], a[MAXN];
bool inc(int x, int y) {
	return (x | y) == x;
}
void FWTOR(int *a, int N) {
	for (int len = 2; len <= N; len <<= 1)
	for (int s = 0; s < N; s += len)
	for (int i = s, j = s + len / 2; j < s + len; i++, j++)
		a[i] = a[i] + a[j];
}
int main() {
	read(m), read(k), read(s), read(t);
	if (!inc(t, s)) {
		puts("0");
		return 0;
	}
	for (int i = 1; i <= m; i++) {
		int x; read(x);
		if (inc(x, s) && inc(t, x)) a[++n] = x;
	}
	m = 0, goal = 0;
	for (int i = 1; i <= 18; i++) {
		bit[i] = 1 << (i - 1);
		if ((t & bit[i]) && (s & bit[i]) == 0) m++, goal = goal * 2 + 1;
	}
	for (int i = 1; i <= n; i++) {
		int res = 0;
		for (int j = 1; j <= 18; j++)
			if ((t & bit[j]) && (s & bit[j]) == 0) {
				if (a[i] & bit[j]) res = res * 2 + 1;
				else res = res * 2;
			}
		a[i] = res;
	}
	for (int i = 0; i <= n; i++) {
		binom[i][0] = 1;
		for (int j = 1; j <= i; j++)
			binom[i][j] = binom[i - 1][j - 1] + binom[i - 1][j];
		for (int j = 0; j <= k - 1; j++)
			coef[i] += binom[i][j];
	}
	for (int i = 1; i <= goal; i++)
		bits[i] = bits[i / 2] + i % 2;
	ll ans = 0;
	for (int i = 1; i <= n; i++) {
		memset(cnt, 0, sizeof(cnt));
		for (int j = i + 1; j <= n; j++)
			cnt[a[i] ^ a[j] ^ goal]++;
		FWTOR(cnt, goal + 1);
		for (int j = 0; j <= goal; j++)
			if (bits[j] & 1) ans -= coef[cnt[j]];
			else ans += coef[cnt[j]];
	}
	cout << ans << endl;
	return 0;
}

Problem F. Triangles

不失一般性地,考虑顶点在 ( 0 , i ) , ( j , 0 ) , ( W , k ) (0,i),(j,0),(W,k) (0,i),(j,0),(W,k) 处的三角形。
由对称性,考虑仅计算 i ≤ k i\leq k ik 的情况,令 k = i + d k=i+d k=i+d

由毕克定理,三角形内部的点数 A A A ,边界上的点数 B B B ,面积 S S S 应当满足
2 S = 2 A + B − 2 2S=2A+B-2 2S=2A+B2

也即
( 2 i + d ) W − i j − ( W − j ) ( i + d ) = 2 A + g c d ( i , j ) + g c d ( W , d ) + g c d ( W − j , i + d ) − 2 (2i+d)W-ij-(W-j)(i+d)=2A+gcd(i,j)+gcd(W,d)+gcd(W-j,i+d)-2 (2i+d)Wij(Wj)(i+d)=2A+gcd(i,j)+gcd(W,d)+gcd(Wj,i+d)2

整理得
i W − g c d ( i , j ) − g c d ( W − j , i + d ) = 2 A + g c d ( W , d ) − 2 − d j iW-gcd(i,j)-gcd(W-j,i+d)=2A+gcd(W,d)-2-dj iWgcd(i,j)gcd(Wj,i+d)=2A+gcd(W,d)2dj

因此,我们需要计算满足如下不等式的 ( i , j , d ) (i,j,d) (i,j,d) 的个数:
i W − g c d ( i , j ) − g c d ( W − j , i + d ) ≤ 2 K + g c d ( W , d ) − 2 − d j iW-gcd(i,j)-gcd(W-j,i+d)\leq 2K+gcd(W,d)-2-dj iWgcd(i,j)gcd(Wj,i+d)2K+gcd(W,d)2dj

考虑枚举 ( j , d ) (j,d) (j,d) 则不等式的右侧应当为一个定值。
由于 g c d ( i , j ) + g c d ( W − j , i + d ) ≤ j + ( W − j ) = W gcd(i,j)+gcd(W-j,i+d)\leq j+(W-j)=W gcd(i,j)+gcd(Wj,i+d)j+(Wj)=W ,即不等式左侧关于 i i i 单调。
因此,我们可以 O ( 1 ) O(1) O(1) 算出满足不等式的 i i i 的个数。

又因为使得不等式右侧 ≥ 0 \geq 0 0 ( j , d ) (j,d) (j,d) 的组数不超过 O ( V L o g V ) O(VLogV) O(VLogV) ,枚举即可。

时间复杂度 O ( V L o g 2 V ) O(VLog^2V) O(VLog2V)

#include
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int n, m, k;
ll work(int n, int m) {
	ll ans = 0;
	for (int i = 0; i <= n - 2; i++)
	for (int j = 1; j <= m - 1 && i * j <= 2 * k + m; j++) {
		int tmp = 2 * k + __gcd(i, m) - i * j - 2;
		if (tmp >= 0) {
			int limit = min(n - 1 - i, tmp / m + 1);
			ans += (limit - (m * limit - __gcd(limit, j) - __gcd(limit + i, m - j) > tmp)) * (1 + (i != 0));
		}
	}
	return ans;
}
int main() {
	read(n), read(m), read(k);
	cout << work(n, m) * 2 + work(m, n) * 2 << endl;
	return 0;
}

你可能感兴趣的:(【OJ】AtCoder)