Hello 2022

\(\inf\) 年没有打 CF 后被自己的手速感动。

Hello 2022,也算是对 2022 的期望叭。

HaHa, The best contest in Codeforces in 2022 so far

A - Stable Arrangement of Rooks

简单模拟。

B - Integers Shop

简单贪心。

C - Hidden Permutations

交互题,给定一个排列 \(p\),一开始 \(a_i=i\)

每次可以询问某个位置的 \(a_i\),每次询问后 \(a\) 序列会轮换一次,方式为 \(a_{p_i}\to a_i\)

利用不超过 \(2n\) 次询问确定 \(p\)

不论何时,假设此次询问 \(a_i=x\),紧接着再问一次 \(a_i=y\),则 \(p_x=y\)

对每个环统计答案,确定一个长度为 \(L\) 的环需要 \(L+1\) 次询问,故不超过 \(2n\)

void Work() {
	n = read();
	rep(i, 1, n) ans[i] = 0;
	int t;
	rep(i, 1, n) if(! ans[i]) {
		int s = Que(i), S = s;
		t = 0;
		a[++ t] = s;
		while(true) {s = Que(i); if(s == S) break; a[++ t] = s;}
		rep(j, 1, t - 1) ans[a[j]] = a[j + 1];
		ans[a[t]] = a[1];
	}
	printf("! ");
	rep(i, 1, n) printf("%d ", ans[i]); cout << endl;
}

D - The Winter Hike

\(2n\times 2n\) 的网格中,将左上角 \(n\times n\) 个人移动到右下角 \(n\times n\) 个格子,对应顺序随意。

经过一个格子就要付出它上面数字的代价,只统计一次。

移动方式为 一行 或 一列 的 左右 或 上下,出格就循环补充到另一端,求最小代价。

首先右下角的所有代价都一定加上。

  • 可以证明只要左上到右下有一条路,就能够移动所有人。

但是直接最短路是不正确的,因为无论如何,有 \(8\) 个格子必须经过至少其一,它们是

\((1,n+1),(1,2n),(n,n+1),(n,2n),(n+1,1),(2n,1),(n+1,n),(2n,n)\)

同时,若它们其一被经过了,那就已经形成了通路,故答案就是上面八个的 min

E - New School

给定 \(n\) 个老师,年龄分别为 \(a_i\),和 \(m\) 个学生群体,每个群体中有 \(k_i\) 个学生,年龄分别为 \(b_{i,j}\)

要求给老师学生配对,每个学生群体安排恰好一名老师,老师的年龄不小于该群体的平均年龄。

对于每个学生,询问若该学生退出所在群体,能否配对成功。

如果是单次询问,直接排序后贪心。

如果有学生退出,不难发现贪心操作匹配的老师至多 \(+1/-1\),故直接三个树状数组维护即可。

struct Tree {
	int c[N];
	void Init(int n) {rep(i, 0, n) c[i] = 0;}
	void Add(int x, int v) {for(; x <= n; x += x & -x) c[x] += v;}
	int Ask(int x) {int sum = 0; for(; x; x -= x & -x) sum += c[x]; return sum;}
	int Que(int L, int R) {return ((R - L + 1) == (Ask(R) - Ask(L - 1)));}
} Mt, Lt, Rt;
 
void Clear() {Mt.Init(n), Lt.Init(n), Rt.Init(n); t = 0;}
 
bool cmp1(int x, int y) {return x > y;}
bool cmp2(Node x, Node y) {return x.s * y.k > y.s * x.k;}
 
bool Get(int p, int v) {
	int l = 0, r = n;
	LL nk = stu[p].k - 1, ns = stu[p].s - v;
	while(l < r) {
		int mid = (l + r + 1) >> 1;
		if(stu[mid].s * nk >= ns * stu[mid].k) l = mid;
		else r = mid - 1;
	}
	int q = l;
 
	if(p <= q) {
		if(1 <= p - 1 && ! Mt.Que(1, p - 1)) return false;
		if(q + 1 <= n && ! Mt.Que(q + 1, n)) return false;
		if(p + 1 <= q && ! Lt.Que(p + 1, q)) return false;
		if(ns > 1LL * a[q] * nk) return false;
		return true; 
	}
	else {
		if(1 <= q && ! Mt.Que(1, q)) return false;
		if(p + 1 <= n && ! Mt.Que(p + 1, n)) return false;
		if(q + 1 <= p - 1 && ! Rt.Que(q + 1, p - 1)) return false;
		if(ns > 1LL * a[q + 1] * nk) return false;
		return true; 
	}
}
 
void Work() {
	m = read(), n = read();
	Clear();
 
	rep(i, 1, m) a[i] = read();
	sort(a + 1, a + m + 1, cmp1);
	rep(i, 1, n) {
		stu[i].k = read();
		stu[i].id = i;
		stu[i].s = 0;
		rep(j, 1, stu[i].k) {
			t ++;
			pos[t] = i;
			age[t] = read();
			stu[i].s += age[t];
		}
	}
	sort(stu + 1, stu + n + 1, cmp2);
	
	rep(i, 1, n) to[stu[i].id] = i;
 
	rep(i, 1, n) if(stu[i].s <= 1LL * a[i] * stu[i].k) Mt.Add(i, 1);
	rep(i, 2, n) if(stu[i].s <= 1LL * a[i - 1] * stu[i].k) Lt.Add(i, 1);
	rep(i, 2, n) if(stu[i - 1].s <= 1LL * a[i] * stu[i - 1].k) Rt.Add(i - 1, 1);
	stu[0].k = 0, stu[0].s = 1e10;
	
	rep(i, 1, t) ans[i] = Get(to[pos[i]], age[i]);
	
	rep(i, 1, t) putchar(ans[i] ? '1' : '0');
	puts("");
}

F - Strange Instructions

给定 01 串,有三种操作:

  1. 将一个 00 子串变为 0,得到 \(a\) 的价值。
  2. 将一个 11 子串变为 1,得到 \(b\) 的价值。
  3. 将一个 0 删去,扣除 \(c\) 的价值。

要求操作奇偶交替进行,最大化最终价值。

直觉上很可以贪心。

不难发现 1 的操作比较平凡,只需要统计一个数值 \(one\) 表示能进行操作 \(2\) 的次数即可完全代替它。

一般情况,显然选择 \(1,2\) 操作交替进行最优。但是当不能进行其中之一时,可能需要通过 \(3\) “续命”。

本着这些理念,稍加思考得到一个完整的贪心体系:

  • 需要进行操作 \(2\)
    • 若还能进行,任选一个子串变换。
    • 否则结束。
  • 需要进行操作 \(1/3\)
    • 若操作 \(2\) 无法再进行
      • 若能够进行操作 \(1\)尝试进行它(并非真的进行),这是这种情况的最后一次操作。
      • 若能够进行操作 \(3\) 消除形如 101 中的 0, 进行它。
      • 否则结束。
    • 若操作 \(2\) 还能进行
      • 若能在非边角块进行操作 \(1\),任选一个最短的块操作它。
      • 否则,若能在边角块进行操作 \(1\),操作它。(A)
      • 否则,若能在非边角块进行操作 \(3\),任选一个最短的块操作它。(B)
      • 否则,若能在边角块进行操作 \(3\),操作它。
      • 否则结束。

哈,这就真的结束了。

唯一值得注意的是 A B 的优先顺序,因为当前操作 \(2\) 还能进行,故不一定需要续命,先进行 \(1\) 不劣。

int n, a, b, c, arc[N], len[N];

LL Get(int oo) {
	int one = 0, cor1 = 0, cor2 = 0, mid = 0;
	int now = 1;
	bool flag = false;
	rep(i, 1, n) arc[i] = 0;
	rep(i, 2, n + 1)
		if(str[i] == str[i - 1]) now ++;
		else {
			int o = str[i - 1] - '0';
			if(o) one += now - 1;
			else {
				if(! flag && i != n + 1) cor1 = now;
				if(i == n + 1) cor2 = now;
				if(flag && i != n + 1) {
					now --;
					if(now) arc[now] ++;
					else mid ++;
				}
			}
			flag = true, now = 1;
		}
 
	int t = 0;
	per(i, n, 1) while(arc[i] --) len[++ t] = i;
	LL ans = 0, Ans = 0;
 
	while(true) {
		if(oo) {
			if(one)
				ans += b, one --, Ans = max(Ans, ans);
			else
				break;
		}
		else {
			if(! one) {
				if((t && len[t]) || cor1 > 1 || cor2 > 1) Ans = max(Ans, ans + a);
				if(mid) mid --, one ++, ans -= c;
				else break;
			}
			else {
				if(t && len[t]) {
					ans += a, Ans = max(Ans, ans);
					if(! -- len[t]) mid ++, t --;
				}
				else if(cor1 > 1 || cor2 > 1) {
					if(cor1 > 1) cor1 --; else cor2 --;
					ans += a, Ans = max(Ans, ans);
				}
				else if(mid) {
					ans -= c, mid --, one ++;
				}
				else if(cor1 || cor2) {
					if(cor1) cor1 --; else cor2 --;
					ans -= c;
				}
				else break;
			}
		}
		
		oo ^= 1;
	}
 
	return Ans;
}
 
void Work() {
	n = read(), a = read(), b = read(), c = read();
	scanf("%s", str + 1);
	str[n + 1] = '#';
	printf("%lld\n", max(Get(0), Get(1)));
}

G - Weighted Increasing Subsequences

统计所有严格上升子序列的价值和。

对于子序列 \(a_{k_1}<a_{k_2}<\cdots<a_{k_m},k_1<k_2<\cdots<k_m\),它的价值为:

\[\sum\limits_{i=1}^m [a_{k_i}<\max(a_{k_m+1}\sim a_n)]$$。 \]

首先离散化到一个排列,方法是按照 \((a_i,-i)\) 排序,这是一个应对子序列问题的常见 trick。

对于每个位置单独统计,即统计经过这个位置且它对价值有贡献的子序列数。

预处理 \(pre_i,suf_i\) 表示从 \(i\) 开始或结束的上升子序列数,这个是经典的 DP 问题,可以用树状数组优化。

考虑统计从 \(i\) 开始,但是最终后面没有数 \(>a_i\) 的方案数 \(bac_i\),那么 \(ans=\sum pre\times(suf-bac)\)

观察发现,最后一个比 \(a_i\) 大的数一定是一个后缀 Max,所以可以直接处理出后缀 Max 序列出来。

设为 \(p_1>p_2>...(a_{p_1}<a_{p_2}<...)\),对每个位置找到最小的 \(q\) 满足 \(a_{p_q}>a_i\)

那么只需要统计从 \(a_i\) 出发,到 \(a_{p_q}\) 结束的子序列数即可(一定是恰好于 \(p_q\) 结束,往后的话不满足 \(q\) 的最小性)

这个问题可以用相似的树状数组优化 DP 解决。

保证复杂度的还有一个重要性质,对于 \(q\),只需要针对值域为 \((a_{p_{q-1}},a_{p_q}]\) 的位置 DP,同样是 \(q\) 的最小性。

最终复杂度 \(O(n\log n)\)

const int N = 2e5 + 10;
const LL P = 1e9 + 7;
 
int n, t, a[N], pos[N], Mx[N], val[N];
int cnt, head[N], nxt[N], ver[N];
LL pre[N], suf[N], bac[N], num[N << 1];
pair<int, int> arc[N], p[N << 1];

struct BIT {
	LL c[N];
	void Init(int n) {rep(i, 1, n) c[i] = 0;}
	void Add(int x, LL v) {for(; x <= n; x += x & -x) c[x] = (c[x] + v) % P;}
	LL Ask(int x) {LL sum = 0; for(; x; x -= x & -x) sum = (sum + c[x]) % P; return sum;}
	LL Que(int L, int R) {LL now = Ask(R) - Ask(L - 1); return (now % P + P) % P;}
} At;
 
void Clear() {
	cnt = t = 0;
	rep(i, 1, n)
		pre[i] = suf[i] = bac[i] = num[i] = 0, 
		head[i] = nxt[i] = ver[i] = pos[i] = Mx[i] = val[i] = 0;
}
 
void Ins(int u, int v) {nxt[++ cnt] = head[u], ver[cnt] = v, head[u] = cnt;}
 
void Work() {
	n = read(); Clear();
	rep(i, 1, n) a[i] = read(), arc[i] = make_pair(a[i], -i);
	sort(arc + 1, arc + n + 1);
	rep(i, 1, n) a[pos[i] = - arc[i].second] = i;
	
	At.Init(n);
	rep(i, 1, n) pre[i] = (At.Que(1, a[i]) + 1) % P, At.Add(a[i], pre[i]);
	At.Init(n);
	per(i, n, 1) suf[i] = (At.Que(a[i], n) + 1) % P, At.Add(a[i], suf[i]);
	
	Mx[0] = -1;
	per(i, n, 1)
		if(a[i] > val[t]) Mx[++ t] = i, val[t] = a[i], bac[i] = 1;
		else {
			int o = lower_bound(val + 1, val + t + 1, a[i]) - val;
			Ins(o, i);
		}
 
	At.Init(n);
	a[Mx[0] = 0] = 0;
	rep(i, 1, t) if(head[i]) {
		int L = a[Mx[i - 1]] + 1, R = a[Mx[i]], tot = R - L + 1;
 
		rep(j, L, R) p[j - L + 1] = make_pair(pos[j], 0);
		for(int j = head[i]; j; j = nxt[j]) {
			int now = ver[j];
			p[++ tot] = make_pair(now, 1);
		}
		sort(p + 1, p + tot + 1);
		while(p[tot].first != Mx[i]) tot --;
 
		At.Add(a[p[tot].first], 1);
		per(j, tot - 1, 1) {
			int o = p[j].first;
			if(p[j].second)
				bac[o] = At.Que(a[o], n);
			else {
				num[j] = At.Que(a[o], n);
				At.Add(a[o], num[j]);
			}
		}
		
		At.Add(a[p[tot].first], -1);
		per(j, tot - 1, 1)
			if(! p[j].second) At.Add(a[p[j].first], - num[j]), num[j] = 0;
	}
	
	LL ans = 0;
	rep(i, 1, n) {
		LL A = pre[i];
		LL B = ((suf[i] - bac[i]) % P + P) % P;
		ans = (ans + A * B % P) % P;
	}
	printf("%lld\n", ans);
}
posted @ 2022-01-06 23:05  LPF'sBlog  阅读(20)  评论(0编辑  收藏  举报