AtCoder Regular Contest 137


ARC137

心都凉了半截(

A 你暴力枚举复杂度就是对的,\([1,10^{18}]\) 内最远质数距离为 \(1000\) 级别。

B - Count 1's

给定一个 \(0/1\) 串,可以选择翻转一个区间,只能翻转一次,求可能的最终 \(1\) 的个数。

复杂的离谱的赛时做法:

\(s\) 为前缀和,翻转区间 \([l,r]\) 带来的影响为 \((r-l+1)-2(s_r-s_{l-1})\)

将贡献拆开:\((r-2s_r)-(l-1+2s_{l-1})\),发现 \(l-1+2s_{l-1}\) 的变化量 \(\leq 1\),相当于前缀的它是一个连续区间。

枚举右端点,同时维护区间,每次在树状数组上区间加,最终非 \(0\) 位就是能表示出来的。

const int N = 3e5 + 10;
int n, a[N], s[N], c[N];
 
void Add(int x, int v) {x ++; for(; x <= n + 1; x += x & - x) c[x] += v;}
int Ask(int x) {int s = 0; x ++; for(; x; x -= x & - x) s += c[x]; return s;}
 
int main() {
	n = read();
	rep(i, 1, n) a[i] = read(), s[i] = s[i - 1] + a[i];
	Add(s[n], 1), Add(s[n] + 1, - 1);
	int L = 0, R = 0;
	rep(r, 1, n) {
		Add(s[n] + L + r - 2 * s[r], 1);
		Add(s[n] + R + r - 2 * s[r] + 1, - 1);
		L = min(L, 2 * s[r] - r);
		R = max(R, 2 * s[r] - r);
	}
	int ans = 0;
	rep(i, 0, n) if(Ask(i) > 0) ans ++;
	printf("%d\n", ans);
	return 0;
}

实际上,更进一步,发现 \(r-2s_r\) 的变化量也 \(\leq 1\),所以最终答案也是一段区间。

那只要求最大/最小即可,把 \(0\) 赋值为 \(1\)\(1\) 赋值为 \(-1\),相当于求最大/最小子段和,经典贪心。

C - Distinct Numbers

给定一个严格递增的非负整数序列,每轮要求将最大值减少并重新插回序列。

要求序列元素依然非负且严格递增(不能有重复元素),不能操作者判负,Alice 先手,求谁必胜。

比较人类智慧的博弈题(

\(a_n>a_{n-1}+1\),那么先手是必胜的,因为如果存在将最大值减小的必胜策略,那么先手会执行。

否则先手会将 \(a_n\) 移动到 \(a_{n-1}+1\),然后将这个必败态给后手,先手依然必胜。

否则,一开始 \(a_n=a_{n-1}+1\),显然大家都不能让对方取到 \(a_n>a_{n-1}+1\) 的局面,那么每次最大值一定是 \(-1\),而且慢慢递减的。

最终的胜负就由 \(a_n-(n-1)\) 的奇偶性决定。

const int N = 3e5 + 10;
int n, a[N];

int main() {
	n = read();
	rep(i, 1, n) a[i] = read();
	if(a[n] > a[n - 1] + 1) puts("Alice");
	else puts(((a[n] - (n - 1)) & 1) ? "Alice" : "Bob");
	return 0;
}

D - Prefix XORs

给定长度为 \(n\) 的序列,每次操作进行一次异或前缀和,即 \(i=1\to n,a_i\gets a_i\oplus a_{i-1}\)

\(m\) 轮操作中,每一轮的 \(a_n\)

降智(

显然,第 \(k\) 轮有贡献的就是 \(\displaystyle \binom{(n-i)+(k-1)}{k-1}\) 为奇数的那些 \(i\)

根据 Lucas 定理,相当于二进制下,\(k-1\)\((n-i)+(k-1)\) 的子集。

赛时到这就想不动了,实际上,这等价于 \((n-i)\)\((k-1)\) 的二进制无交集,否则一定有进位,无论怎么进位一定会不满足条件。

然后就是高维前缀和的事了。

const int N = 1e6 + 10;
const int M = 1 << 20;
int n, m, o, f[M];

int main() {
	n = read(), m = read();
	rep(i, 1, n) f[n - i] = read();
	int x = max(n, m);
	while(x) x >>= 1, o ++;
	rep(j, 0, o - 1)
		rep(i, 0, (1 << o) - 1)
			if(i >> j & 1) f[i] ^= f[i ^ (1 << j)];
	int s = (1 << o) - 1;
	rep(i, 1, m)
		printf("%d ", f[s - (i - 1)]); puts("");
	return 0;
}

E - Bakery

开面包店,有 \(n\) 天,每次最多能卖出 \(a_i\) 块面包,面包不能过夜,每块卖出的面包得到 \(D\) 元。

\(m\) 个人可以雇佣,第 \(i\) 个人会工作从 \(l_i\)\(r_i\) 天,每天生产 \(1\) 块面包,需要 \(c_i\) 元雇佣。

求最大利润。

和人员雇佣很像,不难得到经典的费用流建模:

  • 对于 \(i\in[1,n]\),连边 \((i-1,i,m-a_i,0),(i-1,i,a_i,D)\)
  • 连边 \((s,0,m,0)\).
  • 对于 \(i\in[1,m]\),连边 \((l_i-1,r_i,1,c_i)\)
  • 汇点 \(t=n\)

\(s,t\) 的最小费用最大流,\((D\times \sum a)-\text{cost}\) 就是答案。(AtCoder Library 超好用的)

#include 
using namespace atcoder;

int main() {
	int n = read(), m = read(), D = read();
	int s = n + 1, t = n;
	mcf_graph G(s + 1);
	LL ans = 0;
	rep(i, 1, n) {
		int a = read();
		ans += 1LL * a * D;
		G.add_edge(i - 1, i, a, D);
		G.add_edge(i - 1, i, m - a, 0);
	}
	G.add_edge(s, 0, m, 0);
	rep(i, 1, m) {
		int l = read(), r = read(), c = read();
		G.add_edge(l - 1, r, 1, c);
	}
	printf("%lld\n", ans - G.flow(s, t).second);
	return 0;
}