Codeforces Round #400 (Div. 1 + Div. 2, combined)

比赛的时候做了前四道, 然而第四道FST了, 原因是m,n搞反.

以下是A-F的题解. 6/7

最后一道题 (貌似是数位DP) 还没想通......脑子有点乱, 先放着吧. # A. A Serial Killer 一个杀手这样杀人: 从两名候选人中挑一个杀掉, 并另选一个人成为新的死亡候选人. 已知初始的两名候选人和n天里每天的死者和新候选人, 求(n+1)天里每天的两名死亡候选人. 没有人重名. (1≤n≤1000)

一开始读错题, 以为初始候选人是未知的. 如果已知, 那么模拟即可.

#include <bits/stdc++.h>
#define Rep(i, a, b) for (int i = (a), i##_ = (b); i < i##_; ++i)
#define For(i, a, b) for (int i = (a), i##_ = (b); i <= i##_; ++i)
#define Down(i, a, b) for (int i = (a), i##_ = (b); i >= i##_; --i)

using namespace std;

int main()
{
	string s1, s2;
	cin >> s1 >> s2;
	cout << s1 << " " << s2 << endl;
	int n;
	cin >> n;
	Rep (i, 0, n) {
		string t1, t2;
		cin >> t1 >> t2;
		cout << t2 << " ";
		if (s1 == t1) {
			cout << s2 << endl;
			s1 = t2;
		} else {
			cout << s1 << endl;
			s2 = t2;
		}
	}
	return 0;
}

B. Sherlock and his girlfriend

给标号为2,3,4,...,n+1的n件珠宝染色, 一件珠宝不能和标号为它的质因子的珠宝同色, 求最小染色数和任意一种方案. (1≤n≤100000)

最小色数是NPC问题, 竞赛中出现, 一般来讲就得贪心了. 犯了一个方法论上的错误. 先试图证明 (看看它是不是无环, 然而并不) 而不是手算. 半天没搞出来, 翻了翻某群聊天记录, "质数输1, 合数输2". (算是开黑吧......)

??!

有道理啊......其实这就是贪心的策略. 但换种方式描述, 正确性就很显然了. 如果存在合数, 就至少得两种颜色. 这种构造说明2的下界是可以达到的.

特判n=1,2的情况.

#include <bits/stdc++.h>
#define Rep(i, a, b) for (int i = (a), i##_ = (b); i < i##_; ++i)
#define For(i, a, b) for (int i = (a), i##_ = (b); i <= i##_; ++i)
#define Down(i, a, b) for (int i = (a), i##_ = (b); i >= i##_; --i)

using namespace std;

const int MAX_N = 1e5;
bool f[MAX_N + 2];

int main()
{
	int n;
	cin >> n;
	if (n <= 2)
		cout << "1" << endl;
	else
		cout << "2" << endl;
	For (i, 2, n+1) {
		if (!f[i]) {
			cout << "1 ";
			for (int j = 2*i; j <= n+1; j += i)
				f[j] = true;
		} else
			cout << "2 ";
	}
	cout << endl;
	return 0;
}

C. Molly's Chemicals

给一个长度为n的序列, 每个数的绝对值不超过10^9. 问有多少个区间的和等于整数k的自然数次幂. (1≤n≤10^5, 1≤|k|≤10)

这道题是最后做的. 开始没什么思路. 但是那么多人都过掉了, 说明它有一个简单的想法.

某次比赛的惨痛教训告诉我要看看什么数范围比较小, 也许能以此作为突破口. 这里k的范围比较小. 但是这是避免乘法溢出的吧. 所有数之和不超过10^14次方, long long足矣.

既然它会溢出, 说明增长得比较快. 咦, 岂不是可以枚举k的幂? 先差分, 然后把序列从左到右扫一遍, 维护自己和自己左边出现了哪些数, 丢到map里再查询即可.

想出来了略高兴, 而且时间不多了, 忘记了一个细节: 并非所有指数函数增长都比较快......|k|=1需特判. 被hack了一发. 其实挺感激的, 不然这场就FST两题了.

#include <bits/stdc++.h>
#define Rep(i, a, b) for (int i = (a), i##_ = (b); i < i##_; ++i)
#define For(i, a, b) for (int i = (a), i##_ = (b); i <= i##_; ++i)
#define Down(i, a, b) for (int i = (a), i##_ = (b); i >= i##_; --i)

using namespace std;
typedef long long ll;

map<ll, int> cnt;
ll L[50];

int main()
{
	int n, m = 0;
	ll k, S = 0, ans = 0;
	cin >> n >> k;

	if (k != 1 && k != -1) {
		for (ll x = 1; x <= 1e14; x *= k)
			L[m++] = x;
	} else {
		L[m++] = 1;
		if (k == -1)
			L[m++] = -1;
	}
	
	++cnt[0];
	For (i, 1, n) {
		ll a;
		cin >> a;
		S += a;
		Rep (j, 0, m)
			if (cnt.count(S - L[j]))
				ans += cnt[S - L[j]];
		++cnt[S];
	}

	cout << ans << endl;

	return 0;
}

D. The Door Problem

n间房, m个开关, 每个开关可以控制多扇房门, 每间房恰好被两个开关控制. 按一次开关的效果是切换门的状态 (开/关). 已知每扇门的初始状态, 问能否使所有门都开. (2≤n≤10^5, 2≤m≤10^5)

先将问题数学化, 能列出一个模2的方程组. 可以转化为异或方程组 - 是否意味着可以高斯消元呢? 这个数据范围不允许.

换一种角度, 限制只能取0或1, 把这些变量之间的关系转化为相等和不等. 关押罪犯? 用虚点或带权并查集解决, 或者建图 - 2-SAT.

但是我一时忘记并查集究竟怎么搞了......虽然最终敲出了一个, 但是在m,n上翻车了.

结点和变量对应, 而不是和关系对应. 虽然通常用n表示点, m表示边.

带权并查集 不管相等还是不等, 一律合并, 表示两者关系已知. 每个点一个附加域, 表示与父亲的关系, 路径压缩的时候维护一下. 合并的时候将x, y的关系转化为x, y根结点的关系.
虚点并查集 开2m个结点, 然后有两种理解: 与x相等的集合/与x不等的集合, x=0/x=1. 每一个关系合并两对集合. 必须合并两对, 不然有问题. 这一点用第二种解释更加明确. 之所以用并查集而非遇到不确定的填0, 就是因为这样可以同时枚举x=0,1的情况. 注意到合并的集合是对称的, 检查的时候看一种即可.

注意到虚点并查集合并的集合跟2-SAT连的边是一致的. 相较2-SAT, 并查集的优势在于可以实时回答.

#include <bits/stdc++.h>
#define Rep(i, a, b) for (int i = (a), i##_ = (b); i < i##_; ++i)
#define For(i, a, b) for (int i = (a), i##_ = (b); i <= i##_; ++i)
#define Down(i, a, b) for (int i = (a), i##_ = (b); i >= i##_; --i)

using namespace std;
const int MAX_N = 1e5, MAX_M = 1e5;
vector<int> adj[MAX_N + 1];
int f[MAX_M*2 + 1], r[MAX_M*2 + 1], s[MAX_N + 1];
int F(int x)
{
	return f[x] ? f[x] = F(f[x]) : x;
}
inline void merge(int x, int y)
{
	if (x == y) return;
	if (r[y] > r[x]) swap(x, y);
	f[y] = x;
	r[x] += x == y;
}

int main()
{
	int n, m;
	cin >> n >> m;

	For (i, 1, n)
		cin >> s[i];
	For (i, 1, m) {
		int k;
		cin >> k;
		For (j, 1, k) {
			int u;
			cin >> u;
			adj[u].push_back(i);
		}
	}
	bool ok = true;
	For (i, 1, n) {
		int x = F(adj[i][0]), y = F(adj[i][1]), ux = F(adj[i][0] + m), uy = F(adj[i][1] + m);
		if (s[i]) { // eq
			if (x == uy) {
				ok = false;
				break;
			}
			merge(x, y);
			merge(ux, uy);
		} else { // ueq
			if (x == y) {
				ok = false;
				break;
			}
			merge(x, uy);
			merge(y, ux);
		}	
	}

	puts(ok ? "YES" : "NO");
	return 0;
}

E. The Holmes Children

定义:

给定n, k, 求 (1≤n≤10^12, 1≤k≤10^12)

注意到. 因为. 于是, 可以用莫比乌斯反演证明, 也可以yy一番: 以n作为分母的最简真分数 (包括0) 有n个, 分子一定和n的一个因子互质, 并且不能同时跟两个因子互质 (有理数的最简分数形式是唯一的).

所以, .

多重欧拉函数 去年APIO讲课为数不多的get到的知识点: . 偶数不与偶数互质, 所以, 对大于1的奇数应用欧拉函数得到偶数, 证明1: 当k<1, [1, k), [k, 2k-1)中与k互质的数一一对应; 证明2: 用公式, 均是偶数.

所以暴力一番即可, 时间复杂度.

单个欧拉函数值的求法 使用公式. 问题转化为快速求出的所有质因子. 真要快速可以使用Pollard-Rho算法. 这里只要求做到.
一个数的因子的数量是的, 并且很容易地找出. 但是怎样确保找出来的是质数呢? 从小到大枚举, 把这种因子除干净. 然而时间复杂度就不对了. 事实上, 还是可以做到的. 假设n存在两个相同或不同的质因子, 则它的最小质因子不会超过, 如果找到了, 将它除干净; 没找到说明n是质数, 退出即可. 循环结束后, 如果n>1, 则n是原数的质因子.
模(10^9+7)可能是用来迷惑大众的吧......
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;

inline ll phi(ll n)
{
	ll ans = n;
	for (ll i = 2; i*i <= n; ++i) {
		if (n % i == 0) {
			ans = ans / i * (i-1);
			do {
				n /= i;
			} while (n % i == 0);
		}
	}
	return n > 1 ? ans / n * (n-1) : ans;
}

int main()
{
	ll n, k;
	cin >> n >> k;
	ll ans = n;
	k = (k+1)/2;
	while (k && ans > 1) {
		--k;
		ans = phi(ans);
	}
	cout << ans % MOD << endl;
	return 0;
}

F. Sherlock's bet to Moriarty

一个n个点依次标号1,2,...,n的凸n边形和m条不在n边形内部相交的对角线, 形成的区域按照排序, 用不超过20种颜色 (用整数1,2,...20表示) 对区域染色, 使得两个区域颜色同为c仅当之间的简单路径上存在一个区域, 其颜色值小于c. 任意输出一种方案. (3≤n≤100000, 0≤m≤n-3)

为什么是20种不是k种? 可能跟2^20=1048576<100000有关. 建出平面图的对偶图, 发现是一棵树 (连通, 点数=边数+1). 尝试一条链的情形, 或许可以将链的中点填上1, 两边递归. 如点数N=7: 3 2 3 1 3 2 3. 显然链只需种颜色足矣. 联想到树的点分治递归的层数是的, 可以在重心填上颜色c, 每棵子树递归填{c+1,c+2,...,20}.

树的重心 定义: 去掉该点形成的最大子树最小的点. 性质: 所有子树大小≤N/2. 证明: 反证. 如图, 设u满足重心的定义, v是最大子树, 然而size(v)>N/2. 现在来说明u不能是重心: size(x)<size(v), size(y)≤N/2<size(v). tree's centroid 推论: 一棵树只能有一个或两个重心, 并且它们只能相邻. 重心的求法: DFS.

现在的问题是怎么建出这张图. 参考了一下官方教程. 将对角线(a,b)按照min(|a-b|, n-|a-b|)排序, 一层一层地拨开. 维护nxt指针, 表示轮廓线上某点的下一个点, 并记录一下这条边外面区域的编号. 特殊处理一下最里面的区域. 然后按照题意重新编号. 由于两个区域至多有两个公共点, 所以取区域的前三大顶点编号排序即可. 在有序的数组里二分寻找符合题意的编号.

#include <bits/stdc++.h>
#define Rep(i, a, b) for (int i = (a), i##_ = (b); i < i##_; ++i)
#define For(i, a, b) for (int i = (a), i##_ = (b); i <= i##_; ++i)
#define Down(i, a, b) for (int i = (a), i##_ = (b); i >= i##_; --i)
#define fst first
#define sec second

using namespace std;
typedef pair<int, int> ii;

const int MAX_N = 1e5, MAX_M = MAX_N - 3;

struct Diagonal {
	int a, b, x;
	bool operator<(const Diagonal& d)
	{
		return x < d.x;
	}
} D[MAX_M];

struct Area {
	int n, a, b, c;
} A[MAX_M + 1];

int nxt[MAX_N], mark[MAX_N], area[MAX_M];
vector<int> adj[MAX_M + 1];

void walk(int s, int t, int i)
{
	static int v[MAX_N + MAX_M];
	Area& ar = A[i];
	int u = s;

	while (u != t) {
		if (mark[u] != -1)
			area[mark[u]] = i;
		v[ar.n++] = u;
		u = nxt[u];
	}
	v[ar.n++] = t;
	nxt[s] = t;
	mark[s] = i;

	sort(v, v+ar.n, greater<int>());
	ar.a = v[0], ar.b = v[1], ar.c = v[2];
}

inline bool cmp(int i, int j)
{
	return A[i].a < A[j].a || (A[i].a == A[j].a && ii(A[i].b, A[i].c) < ii(A[j].b, A[j].c));
}

void build(int n, int m)
{
	Rep (i, 0, n-1) nxt[i] = i+1;
	nxt[n-1] = 0;

	fill_n(mark, n, -1);

	int s = -1, t = -1;
	Rep (i, 0, m) {
		s = D[i].a, t = D[i].b;
		walk(s, t, i);
	}
	walk(t, s, m);
	area[m-1] = m;

	static int o[MAX_M + 1];

	Rep (i, 0, m+1) o[i] = i;
	sort(o, o+m+1, cmp);

	Rep (i, 0, m) {
		int u = lower_bound(o, o+m+1, i, cmp) - o,
			v = lower_bound(o, o+m+1, area[i], cmp) - o;
		adj[u].push_back(v);
		adj[v].push_back(u);
	}
}

bool G[MAX_M + 1];
int parent[MAX_M + 1], color[MAX_M + 1], sz[MAX_M + 1];

ii get(int u, int p, int n)
{
	ii ans(n, n);
	sz[u] = 1;
	int mx = 0;
	Rep (i, 0, adj[u].size()) {
		int v = adj[u][i];
		if (v != p && !G[v]) {
			ans = min(ans, get(v, u, n));
			mx = max(mx, sz[v]);
			sz[u] += sz[v];
		}
	}

	mx = max(mx, n-sz[u]);

	if (mx < ans.fst) {
		parent[u] = p;
		return ii(mx, u);
	} else
		return ans;
}

void dfs(int u, int p, int s, int c)
{
	int g = get(u, p, s).sec;
	G[g] = true;
	color[g] = c;
	Rep (i, 0, adj[g].size()) {
		int v = adj[g][i];
		if (!G[v])
			dfs(v, g, v == parent[g] ? s - sz[g] : sz[v], c+1);
	}
}

int main()
{
	ios::sync_with_stdio(false);

	int n, m;
	cin >> n >> m;

	if (!m) {
		cout << "1" << endl;
		return 0;
	}

	Rep (i, 0, m) {
		Diagonal& d = D[i];
		int a, b;
		cin >> a >> b;
		--a, --b;
		int mx = max(a, b), mn = min(a, b);
		if (mx - mn < n - mx + mn)
			d = (Diagonal){mn, mx, mx - mn};
		else
			d = (Diagonal){mx, mn, n - mx + mn};

	}
	sort(D, D+m);

	build(n, m);

	dfs(0, -1, m+1, 1);

	Rep (i, 0, m+1)
		cout << color[i] << " ";
	cout << endl;

	return 0;
}

G. Sherlock and the Encrypted Data

待填.