2018 Multi-University Training Contest 5__全部题解+标程

  • 非常简单:B, E
  • 简单:C, G, H
  • 中等:A, F, I, K
  • 困难:D, J, L

而正确通过每道题目的队伍数量如下:

题目ABCDEFGHIJKL
通过数量74572109063193442310
提交数量2945133216334443617202347881025

可以看到预测结果与实际结果的差距还是很大的,不得不承认这场比赛有点难了。不论如何,我们祝愿大家能在未来的比赛中获得更好的经验和更好的奖项。

可在右方查看目录

A. Always Online

Shortest judge solution: 2670 bytes.

借助反证法,我们可以证明,对于一个连通无向图,每条边在至多一个环中当且仅当任意两点间的不相交路径至多两条。另外,最大流和最小割是等价问题,我们只需要考虑割掉最少容量的边使得源点 s s s 与汇点 t t t 不连通即可。

考虑 s s s- t t t 割问题,如果一个环中割掉了至少两条边,则通过调整法可以只割掉至多两条边,因为 s s s t t t 至多有两条不相交路径经过这个环。此外,如果多个环都被割掉了一些边,则通过反证法和调整法可以知道只割掉至多一个环的边。

一个观察是,如果在最小割中一个环被割掉了至少一条边,那么一定是割掉了两条边,且其中一条是容量最小的边。因此,我们可以将每个环中容量最小的边移除,给这个环中其他边加上它的容量,使得任意两点间的最大流量在数值上不发生变化。

经过改动的图形成了一棵树。我们只需要向空图中按容量降序加入每条边,便可以知道对于加入容量 w w w 的边后才连通的两个点集 S S S T T T f l o w ( s , t ) = w \mathrm{flow}(s, t) = w flow(s,t)=w ( s ∈ S , t ∈ T ) (s \in S, t \in T) (sS,tT)。利用并查集维护这些点集,同时统计每个点集里有多少个点的某个二进制位是 1 1 1 即可按位计算答案。时间复杂度 O ( m log ⁡ n ) \mathcal{O}(m \log n) O(mlogn)

注意 f l o w ( s , t ) \mathrm{flow}(s, t) flow(s,t) 可以达到 2 ⋅ 1 0 9 2 \cdot 10^9 2109,这意味着答案可能超过 2 63 2^{63} 263

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = (int)1e5 + 1, maxm = (int)2e5 + 1, maxd = 17;
int t, n, m, dsu[maxn], up[maxn], dep[maxn], tot, que[maxn];
int mx, sz[maxn], cnt[maxn][maxd];
vector<int> e[maxn], ord, adt;
struct Edge {
	int u, v, w;
	bool operator < (Edge const &t) const {
		return w > t.w;
	}
} seq[maxm];
int dsu_find(int u) {
	return dsu[u] < 0 ? u : (dsu[u] = dsu_find(dsu[u]));
}
bool dsu_merge(int u, int v) {
	u = dsu_find(u);
	v = dsu_find(v);
	if(u == v)
		return 0;
	if(dsu[u] < dsu[v])
		swap(u, v);
	dsu[v] -= dsu[u] == dsu[v];
	dsu[u] = v;
	return 1;
}
LL dsu_merge(int u, int v, int w) {
	u = dsu_find(u);
	v = dsu_find(v);
	assert(u != v);
	if(dsu[u] < dsu[v])
		swap(u, v);
	dsu[v] -= dsu[u] == dsu[v];
	dsu[u] = v;
	LL ret = 0;
	int &su = sz[u], &sv = sz[v];
	for(int i = 0; i < mx; ++i) {
		int &cu = cnt[u][i], &cv = cnt[v][i];
		if((w >> i) & 1)
			ret += ((LL)cu * cv + (LL)(su - cu) * (sv - cv)) << i;
		else
			ret += ((LL)cu * (sv - cv) + (LL)(su - cu) * cv) << i;
		cv += cu;
	}
	ret += ((LL)(w >> mx) * su * sv) << mx;
	sv += su;
	return ret;
}
void bfs(int rt) {
	tot = 0;
	que[tot++] = rt;
	for(int i = 0; i < tot; ++i) {
		int u = que[i];
		for(int it : e[u]) {
			if(it == up[u])
				continue;
			int v = u == seq[it].u ? seq[it].v : seq[it].u;
			up[v] = it;
			dep[v] = dep[u] + 1;
			que[tot++] = v;
		}
	}
}
int main() {
	scanf("%d", &t);
	while(t--) {
		scanf("%d%d", &n, &m);
		for(int i = 0; i < m; ++i)
			scanf("%d%d%d", &seq[i].u, &seq[i].v, &seq[i].w);
		sort(seq, seq + m);
		memset(dsu + 1, -1, n * sizeof(int));
		for(int i = 0; i < m; ++i)
			if(dsu_merge(seq[i].u, seq[i].v)) {
				e[seq[i].u].push_back(i);
				e[seq[i].v].push_back(i);
				ord.push_back(i);
			} else {
				adt.push_back(i);
			}
		up[1] = -1;
		dep[1] = 0;
		bfs(1);
		for(int i : adt) {
			int u = seq[i].u, v = seq[i].v, w = seq[i].w;
			while(u != v) {
				if(dep[u] < dep[v])
					swap(u, v);
				int j = up[u];
				seq[j].w += w;
				u = u == seq[j].u ? seq[j].v : seq[j].u;
			}
		}
		sort(ord.begin(), ord.end(), [&](int const &i, int const &j) {
			return seq[i].w > seq[j].w;
		});
		for( ; 1 << mx <= n; ++mx);
		for(int i = 1; i <= n; ++i) {
			dsu[i] = -1;
			sz[i] = 1;
			for(int j = 0; j < mx; ++j)
				cnt[i][j] = (i >> j) & 1;
		}
		LL ans = 0;
		for(int i : ord)
			ans += dsu_merge(seq[i].u, seq[i].v, seq[i].w);
		printf("%llu\n", ans);
		for(int i = 1; i <= n; ++i)
			vector<int>().swap(e[i]);
		vector<int>().swap(ord);
		vector<int>().swap(adt);
	}
	return 0;
}

B. Beautiful Now

Shortest judge solution: 1584 bytes.

考虑下标的置换,可以发现最少的交换次数等于 n n n 减去置换中循环的数量,所以可以枚举所有的 n n n 排列解决此题。对于每个置换,只需要检查它是否可以在 k k k 次交换内实现(因为可以交换相同的位置)并更新答案。期望时间复杂度是 O ( n ! ) \mathcal{O}(n!) O(n!) O ( n ! n ) \mathcal{O}(n! n) O(n!n) 的算法有可能会超时(也可能不超时)。为了实现期望复杂度可能需要在搜索和回溯过程中高效维护链和环的信息。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 9;
int t, n, m, pL[maxn + 2], pR[maxn + 2], qL[maxn], qR[maxn], low, upp;
char buf[maxn + 2];
void dfs(int dep, int cnt, int val) {
	if(dep == n) {
		low = min(low, val);
		upp = max(upp, val);
		return;
	}
	for(int i = pR[n + 1]; i != n; i = pR[i]) {
		pL[pR[i]] = pL[i];
		pR[pL[i]] = pR[i];
		int nxt = cnt + (qR[i] != dep);
		if(nxt <= m) {
			qR[qL[dep]] = qR[i];
			qL[qR[i]] = qL[dep];
			dfs(dep + 1, nxt, (val << 3) + (val << 1) + buf[i]);
			qR[qL[dep]] = dep;
			qL[qR[i]] = i;
		}
		pL[pR[i]] = pR[pL[i]] = i;
	}
}
int main() {
	scanf("%d", &t);
	while(t--) {
		scanf("%d%d", &low, &m);
		upp = low;
		n = sprintf(buf, "%d", low);
		if(n > maxn) {
			printf("%d %d\n", low, upp);
			continue;
		}
		if(n <= m) {
			sort(buf, buf + n, greater<char>());
			sscanf(buf, "%d", &upp);
			for(int i = 1; i < n; ++i)
				if(buf[0] > buf[i] && buf[i] > '0')
					swap(buf[0], buf[i]);
			sort(buf + 1, buf + n);
			sscanf(buf, "%d", &low);
			printf("%d %d\n", low, upp);
			continue;
		}
		for(int i = 0; i < n; ++i) {
			buf[i] -= '0';
			pL[i] = i - 1;
			pR[i] = i + 1;
			qL[i] = qR[i] = i;
		}
		pL[0] = n + 1;
		pR[n + 1] = 0;
		for(int i = pR[n + 1]; i != n; i = pR[i]) {
			if(!buf[i])
				continue;
			pL[pR[i]] = pL[i];
			pR[pL[i]] = pR[i];
			int cnt = qR[i] != 0;
			if(cnt <= m) {
				qR[qL[0]] = qR[i];
				qL[qR[i]] = qL[0];
				dfs(1, cnt, buf[i]);
				qR[qL[0]] = 0;
				qL[qR[i]] = i;
			}
			pL[pR[i]] = pR[pL[i]] = i;
		}
		printf("%d %d\n", low, upp);
	}
	return 0;
}

C. Call It What You Want

Shortest judge solution: 2019 bytes.

显然,除了 n = 1 n = 1 n=1 之外,其他情况下 Φ n ( x ) \Phi_n(x) Φn(x) 的常数项总是 1 1 1。此外,泰勒展开表明,如果 f ( x ) f(x) f(x) 是一个常数项为零的整系数多项式,那么 ln ⁡ ( 1 + f ( x ) ) \ln\left(1 + f(x)\right) ln(1+f(x)) 是一个整系数形式幂级数。因此,简单运用 M?bius 反演可知

ln ⁡ ( 1 − x n ) = ln ⁡ ( ∏ d ∣ n ( ( − 1 ) [ n = 1 ] Φ d ( x ) ) ) ⇔ ln ⁡ ( ( − 1 ) [ n = 1 ] Φ n ( x ) ) = ln ⁡ ( ∏ d ∣ n ( 1 − x d ) μ ( n / d ) ) \ln\left(1 - x^n\right) = \ln\left(\prod_{d | n}{\left((-1)^{[n = 1]} \Phi_d(x)\right)}\right) \Leftrightarrow \ln\left((-1)^{[n = 1]} \Phi_n(x)\right) = \ln\left(\prod_{d | n}{\left(1 - x^d\right)^{\mu(n / d)}}\right) ln(1xn)=lndn((1)[n=1]Φd(x))ln((1)[n=1]Φn(x))=lndn(1xd)μ(n/d)

不难发现, Φ d ( x ) \Phi_d(x) Φd(x) 的最高项次数是 φ ( d ) \varphi(d) φ(d)。但更重要的是注意到每个多项式 ( 1 − x d ) (1 - x^d) (1xd) 只有两项非零。因此直接在模 x φ ( d ) + 1 x^{\varphi(d) + 1} xφ(d)+1 意义下计算类似背包动态规划形式的卷积即可做到时间复杂度 O ( ∑ d ∣ n 2 ω ( d ) φ ( d ) ) = O ( 2 6 n ) \mathcal{O}(\sum_{d | n}{2^{\omega(d)}\varphi(d)}) = \mathcal{O}(2^6 n) O(dn2ω(d)φ(d))=O(26n),因为 2 × 3 × 5 × 7 × 11 × 13 = 30030 2 \times 3 \times 5 \times 7 \times 11 \times 13 = 30030 2×3×5×7×11×13=30030 30030 × 17 = 510510 ≥ n 30030 \times 17 = 510510 \geq n 30030×17=510510n

这个题是思考卷积优化时想出来的,不过,我们尽可能卡掉了大部分用了加速卷积方法的做法,放过了一些类似暴力的做法。

P.S. 可能要小心负系数的处理。

#include <bits/stdc++.h>
using namespace std;
const int maxn = (int)1e5 + 1, maxe = 7, maxs = (int)5e6 + maxn;
int pool[maxs], *tail = pool;
int pr[maxn][maxe], phi[maxn], *poly[maxn];
int t, n, m, ord[maxn];
int main() {
	phi[1] = 1;
	for(int i = 2; i < maxn; ++i) {
		if(!pr[i][0])
			for(int j = i; j < maxn; j += i)
				pr[j][++pr[j][0]] = i;
		phi[i] = i;
		for(int j = 1; j <= pr[i][0]; ++j)
			phi[i] -= phi[i] / pr[i][j];
	}
	scanf("%d", &t);
	while(t--) {
		scanf("%d", &n);
		m = 0;
		for(int i = 1; i <= n; ++i) {
			if(n % i > 0)
				continue;
			ord[m++] = i;
			if(poly[i] != NULL)
				continue;
			int *seq = poly[i] = tail;
			tail += phi[i] + 1;
			memset(seq, 0, (phi[i] + 1) * sizeof(int));
			seq[0] = i > 1 ? 1 : -1;
			int pcnt = pr[i][0], *pp = pr[i] + 1;
			for(int j = 0; j < 1 << pcnt; ++j) {
				int val = i, sgn = 1;
				for(int k = 0; k < pcnt; ++k)
					if((j >> k) & 1) {
						val /= pp[k];
						sgn = -sgn;
					}
				if(sgn > 0)
					for(int k = phi[i]; k >= val; --k)
						seq[k] -= seq[k - val];
				else
					for(int k = val; k <= phi[i]; ++k)
						seq[k] += seq[k - val];
			}
		}
		sort(ord, ord + m, [&](int const &u, int const &v) {
			if(phi[u] != phi[v])
				return phi[u] < phi[v];
			for(int i = phi[u]; i >= 0; --i)
				if(poly[u][i] != poly[v][i])
					return poly[u][i] < poly[v][i];
			return false;
		});
		static char buf[maxn << 3 | 1];
		char *ptr = buf;
		for(int i = 0; i < m; ++i) {
			int o = ord[i];
			*(ptr++) = '(';
			for(int j = phi[o], fir = 1; j >= 0; --j, fir = 0) {
				if(!poly[o][j])
					continue;
				if(poly[o][j] < 0)
					*(ptr++) = '-';
				else if(!fir)
					*(ptr++) = '+';
				if(!j || abs(poly[o][j]) != 1)
					ptr += sprintf(ptr, "%d", abs(poly[o][j]));
				if(j) {
					*(ptr++) = 'x';
					if(j > 1)
						ptr += sprintf(ptr, "^%d", j);
				}
			}
			*(ptr++) = ')';
		}
		*ptr = '\0';
		assert(ptr - buf <= (maxn << 3));
		puts(buf);
	}
	return 0;
}

D. Daylight

Shortest judge solution: 4518 bytes.

考虑分别到 u u u v v v 距离不超过 w w w 的点集 S u S_u Su S v S_v Sv,令 u u u v v v 的距离是 k k k,则 S u S_u Su S v S_v Sv 的交集是到 u u u v v v 这条路径中点距离超过 w − k 2 w - \frac{k}{2} w2k 的点集。不妨把树上的每条边的中点视为一个新点,则问题转化为在线询问到一个点距离不超过定值的点数。

如果一个点作为树根时它最大的子树节点数达到最少,那么定义这个点是树的重心。尝试将树进行分治,每次选择树的重心作为树根,处理所有必经重心的路径,再递归处理删掉当前重心后的每个连通块,这样每一层至多需要 O ( n ) \mathcal{O}(n) O(n) 的空间记录每个连通块中距离相应重心不超过定值的有效点数,而这样的层数不超过 ⌈ log ⁡ 2 ( n + 1 ) ⌉ \left \lceil \log_2(n + 1) \right \rceil log2(n+1)

实际上,增加 ( n − 1 ) (n - 1) (n1) 个新点是不必要的,只需要在处理询问时讨论从边的中点走到连通块的重心时先经过这条边的哪个端点即可。时间复杂度 O ( n log ⁡ n ) \mathcal{O}(n \log n) O(nlogn)

#include <bits/stdc++.h>
using namespace std;
const int maxn = (int)1e5 + 1, maxd = 17;
int t, n, m, lnk[maxn], rdep[maxn], mx, rfa[maxd][maxn];
struct Edge {
	int nxt, v;
} e[maxn << 1 | 1];
bool ban[maxn];
int tot, ord[maxn], fa[maxn], dep[maxn];
void bfs(int rt) { // fa[rt], dep[rt] have been set 
	tot = 0;
	ord[tot++] = rt;
	for(int i = 0; i < tot; ++i) {
		int u = ord[i];
		for(int it = lnk[u]; it != -1; it = e[it].nxt) {
			int v = e[it].v;
			if(v == fa[u] || ban[v])
				continue;
			fa[v] = u;
			dep[v] = dep[u] + 1;
			ord[tot++] = v;
		}
	}
}
int ptot, idx[maxd][maxn], idx2[maxd][maxn], dis[maxd][maxn];
int len[maxn << 1 | 1], *cnt[maxn << 1 | 1];
int pool[(maxn * maxd) << 1 | 1], *tail;
int sz[maxn];
void build(int depth, int rt) {
	assert(depth < mx);
	fa[rt] = dep[rt] = 0;
	bfs(rt);
	int val = maxn;
	rt = -1;
	for(int i = tot - 1; i >= 0; --i) {
		int u = ord[i], mx = 0;
		sz[u] = 1;
		for(int it = lnk[u]; it != -1; it = e[it].nxt) {
			int v = e[it].v;
			if(v == fa[u] || ban[v])
				continue;
			mx = max(mx, sz[v]);
			sz[u] += sz[v];
		}
		mx = max(mx, tot - sz[u]);
		if(mx < val) {
			val = mx;
			rt = u;
		}
	}
	int pid = ptot++;
	idx[depth][rt] = idx2[depth][rt] = pid;
	dis[depth][rt] = fa[rt] = dep[rt] = 0;
	len[pid] = 1;
	for(int it = lnk[rt]; it != -1; it = e[it].nxt) {
		int tr = e[it].v;
		if(ban[tr])
			continue;
		fa[tr] = rt;
		dep[tr] = dep[rt] + 1;
		bfs(tr);
		int cid = ptot++;
		len[cid] = dep[ord[tot - 1]] + 1;
		cnt[cid] = tail;
		tail += len[cid];
		assert(tail - pool <= (maxn * maxd << 1));
		memset(cnt[cid], 0, len[cid] * sizeof(int));
		len[pid] = max(len[pid], len[cid]);
		for(int i = 0; i < tot; ++i) {
			int u = ord[i];
			idx[depth][u] = pid;
			idx2[depth][u] = cid;
			dis[depth][u] = dep[u];
			++cnt[cid][dep[u]];
		}
	}
	cnt[pid] = tail;
	tail += len[pid];
	assert(tail - pool <= (maxn * maxd << 1));
	memset(cnt[pid], 0, len[pid] * sizeof(int));
	for(int it = lnk[rt]; it != -1; it = e[it].nxt) {
		int tr = e[it].v;
		if(ban[tr])
			continue;
		int cid = idx2[depth][tr];
		for(int i = 1; i < len[cid]; ++i) {
			cnt[pid][i] += cnt[cid][i];
			cnt[cid][i] += cnt[cid][i - 1];
		}
	}
	cnt[pid][0] = 1; // because dis[depth][rt] = 0
	for(int i = 1; i < len[pid]; ++i)
		cnt[pid][i] += cnt[pid][i - 1];
	ban[rt] = 1;
	for(int it = lnk[rt]; it != -1; it = e[it].nxt) {
		int tr = e[it].v;
		if(!ban[tr])
			build(depth + 1, tr);
	}
}
int query(int u, int w) {
	int ret = 0;
	for(int i = 0; ; ++i) {
		int pid = idx[i][u], cid = idx2[i][u], dt = w - dis[i][u];
		if(dt >= 0 && len[pid])
			ret += cnt[pid][min(dt, len[pid] - 1)];
		if(pid == cid)
			break;
		if(dt >= 0 && len[cid])
			ret -= cnt[cid][min(dt, len[cid] - 1)];
	}
	return ret;
}
int query(int u, int v, int w) {
	if(w < 0)
		return 0;
	if(u == v)
		return query(u, w);
	int ret = 0;
	bool chku = 1, chkv = 1;
	for(int i = 0; chku || chkv; ++i) {
		int pidu = idx[i][u], cidu = idx2[i][u], dtu = w - dis[i][u];
		int pidv = idx[i][v], cidv = idx2[i][v], dtv = w - dis[i][v];
		if(chku && chkv && pidu == pidv) {
			dtu = dtv = max(dtu, dtv);
			if(dtu >= 0 && len[pidu])
				ret += cnt[pidu][min(dtu, len[pidu] - 1)];
		} else {
			if(chku && dtu >= 0 && len[pidu])
				ret += cnt[pidu][min(dtu, len[pidu] - 1)];
			if(chkv && dtv >= 0 && len[pidv])
				ret += cnt[pidv][min(dtv, len[pidv] - 1)];
		}
		chku &= pidu != cidu;
		chkv &= pidv != cidv;
		if(chku && chkv && cidu == cidv) {
			if(dtu >= 0 && len[cidu])
				ret -= cnt[cidu][min(dtu, len[cidu] - 1)];
		} else {
			if(chku && dtu >= 0 && len[cidu])
				ret -= cnt[cidu][min(dtu, len[cidu] - 1)];
			if(chkv && dtv >= 0 && len[cidv])
				ret -= cnt[cidv][min(dtv, len[cidv] - 1)];
		}
	}
	return ret;
}
int lca(int u, int v) {
	for(int i = 0, j = rdep[u] - rdep[v]; j > 0; ++i, j >>= 1)
		(j & 1) && (u = rfa[i][u]);
	for(int i = 0, j = rdep[v] - rdep[u]; j > 0; ++i, j >>= 1)
		(j & 1) && (v = rfa[i][v]);
	if(u == v)
		return u;
	for(int i = mx - 1; i >= 0; --i)
		if(rfa[i][u] != rfa[i][v]) {
			u = rfa[i][u];
			v = rfa[i][v];
		}
	return rfa[0][u];
}
int main() {
	scanf("%d", &t);
	while(t--) {
		scanf("%d%d", &n, &m);
		tot = 0;
		memset(lnk + 1, -1, n * sizeof(int));
		for(int i = 1; i < n; ++i) {
			int u, v;
			scanf("%d%d", &u, &v);
			e[tot] = (Edge){lnk[u], v};
			lnk[u] = tot++;
			e[tot] = (Edge){lnk[v], u};
			lnk[v] = tot++;
		}
		memset(ban + 1, 0, n * sizeof(bool));
		fa[1] = dep[1] = 0;
		bfs(1);
		memcpy(rdep + 1, dep + 1, n * sizeof(int));
		memcpy(rfa[0] + 1, fa + 1, n * sizeof(int));
		for(mx = 1; 1 << mx <= n; ++mx)
			for(int i = 1; i <= n; ++i)
				rfa[mx][i] = rfa[mx - 1][rfa[mx - 1][i]];
		ptot = 0;
		tail = pool;
		build(0, 1);
		int ans = 0;
		while(m--) {
			int u, v, w;
			scanf("%d%d%d", &u, &v, &w);
			u = (u + ans) % n + 1;
			v = (v + ans) % n + 1;
			w = (w + ans) % n;
			int pp = lca(u, v), dt = rdep[u] + rdep[v] - (rdep[pp] << 1);
			int x = rdep[u] < rdep[v] ? v : u;
			for(int i = 0, j = dt >> 1; j > 0; ++i, j >>= 1)
				(j & 1) && (x = rfa[i][x]);
			int y = dt & 1 ? rfa[0][x] : x;
			printf("%d\n", ans = query(u, w) + query(v, w) - query(x, y, w - ((dt + 1) >> 1)));
		}
	}
	return 0;
}

E. Everything Has Changed

Shortest judge solution: 709 bytes. Shortest judge Java solution: 771 bytes.

注意切割区域两两不相交,则不同切割区域不会相互影响。枚举每个与圆盘相交的切割区域,计算互相被包含的圆周长度即可。

#include <bits/stdc++.h>
using namespace std;
typedef double DB;
const DB pi = acos(-1.0);
int main() {
	int t;
	scanf("%d", &t);
	while(t--) {
		int n, R;
		scanf("%d%d", &n, &R);
		DB ans = 0, rem = pi;
		for(int i = 1; i <= n; ++i) {
			int x, y, r;
			scanf("%d%d%d", &x, &y, &r);
			int dis2 = x * x + y * y;
			if(dis2 < (R - r) * (R - r) || dis2 > (R + r) * (R + r))
				continue;
			DB dis = sqrtl(dis2);
			DB ang1 = acos(min(max((dis2 + R * R - r * r) / (2 * R * dis), -1.0), 1.0));
			DB ang2 = acos(min(max((dis2 - R * R + r * r) / (2 * r * dis), -1.0), 1.0));
			rem -= ang1;
			ans += 2 * r * ang2;
		}
		ans += 2 * R * rem;
		printf("%.20f\n", ans);
	}
	return 0;
}

F. Fireflies

Shortest judge solution: 2222 bytes.

Dilworth 定理表明,最小链覆盖数等于最大反链数,也即选出互不可达位置的最多数目。Sperner 定理显示,若要选择 S S S 的幂集的一个子集使得其中没有一个集合包含在另一个集合中,则这种子集的大小最大为 ( ∣ S ∣ ⌊ ∣ S ∣ / 2 ⌋ ) {|S| \choose \left \lfloor |S| / 2 \right \rfloor} (S/2S)。Sperner 定理的一种推广可以给出本问题的答案:选择满足所有坐标之和等于 M = ⌊ 1 2 ∑ i = 1 n ( p i + 1 ) ⌋ M = \left \lfloor \frac{1}{2} \sum_{i = 1}^{n}{(p_i + 1)} \right \rfloor M=21i=1n(pi+1) 的位置即可达到最大的数量。

因此,我们需要计算 ∑ i = 1 n x i = M \sum_{i = 1}^{n}{x_i} = M i=1nxi=M 的解数,其中 x i ∈ Z x_i \in \mathbb{Z} xiZ, 1 ≤ x i ≤ p i 1 \leq x_i \leq p_i 1xipi ( i = 1 , 2 , ⋯   , n ) (i = 1, 2, \cdots, n) (i=1,2,,n)。考虑容斥原理,可知答案为 ∑ I ⊆ J ( − 1 ) ∣ I ∣ ( M − 1 − ∑ i ∈ I p i n − 1 ) \sum_{I \subseteq J}{(-1)^{|I|}{M - 1 - \sum_{i \in I}{p_i} \choose n - 1}} IJ(1)I(n1M1iIpi),这里 J = { 1 , 2 , ⋯   , n } J = \{1, 2, \cdots, n\} J={1,2,,n}。注意到这个组合数是关于 ∑ i ∈ I p i \sum_{i \in I}{p_i} iIpi 的低次多项式,所以我们可以分别计算 ( ∑ i ∈ I p i ) e \left(\sum_{i \in I}{p_i}\right)^e (iIpi)e ( e = 0 , 1 , ⋯   , n − 1 ) (e = 0, 1, \cdots, n - 1) (e=0,1,,n1) 的贡献。

考虑中途相遇法,我们可以将 J J J X = { 1 , 2 , ⋯   , ⌊ n / 2 ⌋ } X = \{1, 2, \cdots, \left \lfloor n / 2 \right \rfloor\} X={1,2,,n/2} Y = { ⌊ n / 2 ⌋ + 1 , ⌊ n / 2 ⌋ + 2 , ⋯   , n } Y = \{\left \lfloor n / 2 \right \rfloor + 1, \left \lfloor n / 2 \right \rfloor + 2, \cdots, n\} Y={n/2+1,n/2+2,,n},然后分别预处理关于 ∑ i ∈ X p i \sum_{i \in X}{p_i} iXpi ∑ i ∈ Y p i \sum_{i \in Y}{p_i} iYpi 的信息,最后利用二项式定理和双指针的方法计算它们共同产生的贡献。这样做的时间复杂度是 O ( 2 n / 2 n 2 ) \mathcal{O}(2^{n / 2} n^2) O(2n/2n2)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = (int)33, maxd = 1 << 16 | 1, mod = (int)1e9 + 7;
int inv[maxn], c[maxn][maxn];
int t, n, p[maxn], a[maxn], b[maxn][maxn], f[maxn];
LL m;
struct Subset {
	int cnt;
	pair<LL, int> val[maxd];
	void parse(int n, int a[]) {
		cnt = 1 << n;
		for(int i = 0; i < cnt; ++i) {
			LL sum = 0;
			int cnt = 0;
			for(int j = 0; j < n; ++j)
				if((i >> j) & 1) {
					sum += a[j];
					++cnt;
				}
			val[i] = make_pair(sum, cnt);
		}
		sort(val, val + cnt);
	}
} lft, rht;
int main() {
	inv[1] = 1;
	for(int i = 2; i < maxn; ++i)
		inv[i] = mod - (int)(mod / i * (LL)inv[mod % i] % mod);
	for(int i = 0; i < maxn; ++i) {
		c[i][0] = c[i][i] = 1;
		for(int j = 1; j < i; ++j)
			(c[i][j] = c[i - 1][j - 1] + c[i - 1][j]) >= mod && (c[i][j] -= mod);
	}
	scanf("%d", &t);
	while(t--) {
		scanf("%d", &n);
		m = 0;
		for(int i = 0; i < n; ++i) {
			scanf("%d", p + i);
			m += p[i] - 1;
		}
		m >>= 1;
		a[0] = 1;
		for(int i = 1; i < n; ++i) {
			int coeff0 = (m + i) % mod * inv[i] % mod;
			int coeff1 = mod - inv[i];
			a[i] = (LL)coeff1 * a[i - 1] % mod;
			for(int j = i - 1; j > 0; --j)
				a[j] = ((LL)coeff0 * a[j] + (LL)coeff1 * a[j - 1]) % mod;
			a[0] = (LL)coeff0 * a[0] % mod;
		}
		for(int i = 0; i < n; ++i)
			for(int j = i; j < n; ++j)
				b[i][j] = (LL)a[j] * c[j][i] % mod;
		int half = n >> 1;
		lft.parse(half, p);
		rht.parse(n - half, p + half);
		memset(f, 0, n * sizeof(int));
		int ans = 0;
		for(int i = lft.cnt - 1, j = 0; i >= 0; --i) {
			for( ; j < rht.cnt && lft.val[i].first + rht.val[j].first <= m; ++j) {
				int cur = rht.val[j].second & 1 ? mod - 1 : 1, prd = rht.val[j].first % mod;
				for(int x = 0; x < n; ++x) {
					(f[x] += cur) >= mod && (f[x] -= mod);
					cur = (LL)cur * prd % mod;
				}
			}
			int cur = lft.val[i].second & 1 ? mod - 1 : 1, prd = lft.val[i].first % mod;
			for(int x = 0; x < n; ++x) {
				int sum = 0;
				for(int y = x; y < n; ++y)
					sum = (sum + (LL)b[x][y] * f[y - x]) % mod;
				ans = (ans + (LL)cur * sum) % mod;
				cur = (LL)cur * prd % mod;
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}

G. Glad You Came

Shortest judge solution: 1139 bytes. Shortest judge Java solution: 1614 bytes.

如果有两个操作覆盖相同的区间,我们可以保留最大的那个。对于每个操作 ( l , r , v ) (l, r, v) (l,r,v),令 d d d 等于 ⌊ log ⁡ 2 ( r − l + 1 ) ⌋ \left \lfloor \log_2(r - l + 1) \right \rfloor log2(rl+1),我们可以用两个操作 ( l , l + 2 d − 1 , v ) (l, l + 2^d - 1, v) (l,l+2d1,v) ( r − 2 d + 1 , r , v ) (r - 2^d + 1, r, v) (r2d+1,r,v) 替换此操作。这样做之后,每个操作所覆盖的区间长度均为 2 2 2 的幂,这意味着长度仅有 O ( log ⁡ n ) \mathcal{O}(\log n) O(logn) 种。剩下的只不过是,按长度递减的顺序枚举操作,将每个操作分成两个相等长度的操作,直到区间长度为一。这样做的时间复杂度为 O ( m + n log ⁡ n ) \mathcal{O}(m + n \log n) O(m+nlogn),空间复杂度为 O ( n log ⁡ n ) \mathcal{O}(n \log n) O(nlogn)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = (int)1e5 + 1, maxd = 17;
int t, n, m, mx, Log[maxn], a[maxd][maxn];
unsigned int X, Y, Z;
unsigned int rng61() {
	X ^= X << 11;
	X ^= X >> 4;
	X ^= X << 5;
	X ^= X >> 14;
	unsigned int tmp = X ^ Y ^ Z;
	X = Y;
	Y = Z;
	Z = tmp;
	return Z;
}
inline void upd(int &x, int y) {
	x < y && (x = y);
}
int main() {
	for(int i = 2; i < maxn; ++i)
		Log[i] = Log[i >> 1] + 1;
	scanf("%d", &t);
	while(t--) {
		scanf("%d%d%u%u%u", &n, &m, &X, &Y, &Z);
		for(mx = 0; 1 << mx <= n; ++mx);
		while(m--) {
			int L = rng61() % n + 1, R = rng61() % n + 1, v = rng61() & ((1 << 30) - 1);
			if(L > R)
				swap(L, R);
			int d = Log[R - L + 1];
			upd(a[d][L], v);
			upd(a[d][R - (1 << d) + 1], v);
		}
		for(int i = mx - 1; i > 0; --i)
			for(int j = 1; j + (1 << i) - 1 <= n; ++j) {
				upd(a[i - 1][j], a[i][j]);
				upd(a[i - 1][j + (1 << (i - 1))], a[i][j]);
				a[i][j] = 0;
			}
		LL ans = 0;
		for(int i = 1; i <= n; ++i) {
			ans ^= (LL)i * a[0][i];
			a[0][i] = 0;
		}
		printf("%lld\n", ans);
	}
	return 0;
}

H. Hills And Valleys

Shortest judge solution: 1664 bytes.

枚举 [ x , y ] [x, y] [x,y] 表示翻转区间 [ l , r ] [l, r] [l,r] 中对答案产生贡献的数字所处值域,然后找出 A A A 最长的类似 0 ∗ 1 ∗ ⋯ x ∗ y ∗ ( y − 1 ) ∗ ⋯ x ∗ y ∗ ( y + 1 ) ∗ ⋯ 9 ∗ 0^* 1^* \cdots x^* y^* (y - 1)^* \cdots x^* y^* (y + 1)^* \cdots 9^* 01xy(y1)xy(y+1)9 的子序列,其中 k ∗ k^* k 表示任意非负整数个 k k k

#include <bits/stdc++.h>
const int maxn = (int)1e5 + 1, maxv = 13;
int t, n, tot, g[maxn][maxv], *f = g[0];
char buf[maxn], pat[maxv];
int main() {
	scanf("%d", &t);
	while(t--) {
		scanf("%d%s", &n, buf);
		char vL = '9', vR = '0';
		for(int i = 0; i < n; ++i) {
			vL = std::min(vL, buf[i]);
			vR = std::max(vR, buf[i]);
		}
		if(vL == vR) {
			printf("%d %d %d\n", n, 1, 1);
			continue;
		}
		int m = 0;
		char pL, pR;
		for(char low = vL; low < vR; ++low)
			for(char upp = low + 1; upp <= vR; ++upp) {
				tot = 0;
				for(char ch = vL; ch <= low; ++ch)
					pat[tot++] = ch;
				for(char ch = upp; ch >= low; --ch)
					pat[tot++] = ch;
				for(char ch = upp; ch <= vR; ++ch)
					pat[tot++] = ch;
				memset(f, 0, tot * sizeof(int));
				for(int i = 0; i < n; ++i)
					for(int j = 0; j < tot; ++j) {
						f[j] += buf[i] == pat[j];
						j && f[j] < f[j - 1] && (f[j] = f[j - 1]);
					}
				if(m < f[tot - 1]) {
					m = f[tot - 1];
					pL = low;
					pR = upp;
				}
			}
		tot = 0;
		for(char ch = vL; ch <= pL; ++ch)
			pat[tot++] = ch;
		for(char ch = pR; ch >= pL; --ch)
			pat[tot++] = ch;
		for(char ch = pR; ch <= vR; ++ch)
			pat[tot++] = ch;
		memset(g[0], 0, tot * sizeof(int));
		for(int i = 0; i < n; ++i) {
			int *pre = g[i], *cur = g[i + 1];
			for(int j = 0; j < tot; ++j) {
				cur[j] = pre[j] + (buf[i] == pat[j]);
				j && cur[j] < cur[j - 1] && (cur[j] = cur[j - 1]);
			}
		}
		int L = 0, R = 0;
		for(int i = n - 1, j = tot - 1; i >= 0; --i) {
			int *pre = g[i], *cur = g[i + 1];
			int lft = pre[j] + (buf[i] == pat[j]);
			int rht = j ? cur[j - 1] : -1;
			while(lft < rht) {
				if(pat[j - 1] == pL && pat[j] == pR) {
					if(!R)
						R = i;
					else
						L = i + 1;
				}
				--j;
				lft = pre[j] + (buf[i] == pat[j]);
				rht = j ? cur[j - 1] : -1;
			}
		}
		printf("%d %d %d\n", m, L + 1, R + 1);
	}
	return 0;
}

I. Innocence

Shortest judge solution: 2851 bytes.

考虑这个问题的简化版:给定 N , { R i } , K N, \{R_i\}, K N,{Ri},K,统计有多少种选择 N N N 个整数 x 1 , x 2 , ⋯   , x N x_1, x_2, \cdots, x_N x1,x2,,xN 的方案满足 x i ∈ [ 0 , R i ] x_i \in [0, R_i] xi[0,Ri] ( i = 1 , 2 , ⋯   , N ) (i = 1, 2, \cdots, N) (i=1,2,,N) 且它们的按位异或值等于 K K K。考虑数位上的动态规划,我们可以根据最高的出现严格小于的二进制位对所有情况进行分类,这里严格小于是指存在至少一个整数 x i x_i xi 在这一位上严格小于 R i R_i Ri。比较麻烦的可能是统计方案数,不过我们已经知道某个整数 x j x_j xj 的更低位部分可以取任何数值,因此我们可以先不管它的低位,在确定其他数的低位后再通过方程确定它的低位。

回到原来的问题,我们可以利用容斥原理将其转化为 2 N 2^N 2N 个简化版的问题,即用 x i ∈ [ 0 , R ] x_i \in [0, R] xi[0,R] 的情况排除掉 x i ∈ [ 0 , L − 1 ] x_i \in [0, L - 1] xi[0,L1] 的情况从而得到答案。对于每个子问题,我们可以用矩阵快速幂加速动态规划的过程,还可以发现 K K K 对这些问题的影响可以根据满足 x i ∈ [ 0 , R ] x_i \in [0, R] xi[0,R] 的情况数量的奇偶性来分类,这意味着我们可以将 2 N 2^N 2N 种可能的状态变成 2 2 2 类问题,从而优化做法。如果先预处理关于 N , L , R N, L, R N,L,R 的动态规划信息再去处理关于 K K K 的部分,那么每组测试数据的时间复杂度可以做到 O ( ( M 3 log ⁡ N + Q ) log ⁡ R ) \mathcal{O}((M^3 \log N + Q) \log R) O((M3logN+Q)logR),这里 M M M 是指矩阵的边长。顺带一提,标程的 M M M 8 8 8

#include <bits/stdc++.h>
const int mod = (int)1e9 + 7, maxd = 30, maxm = 8, half = maxm >> 1, quat = half >> 1;
typedef int Matrix[maxm | 1][maxm | 1];
void matMul(Matrix a, Matrix b, Matrix &c) {
	typedef long long LL;
	static LL tmp[maxm | 1][maxm | 1];
	for(int i = 0; i < maxm; ++i)
		for(int j = 0; j < maxm; ++j)
			tmp[i][j] = 0;
	for(int i = 0; i < maxm; ++i)
		for(int j = 0; j < maxm; ++j) {
			if(!a[i][j])
				continue;
			for(int k = 0; k < maxm; ++k) {
				if(!b[j][k])
					continue;
				tmp[i][k] += (LL)a[i][j] * b[j][k];
			}
		}
	for(int i = 0; i < maxm; ++i)
		for(int j = 0; j < maxm; ++j)
			c[i][j] = tmp[i][j] < mod ? tmp[i][j] : tmp[i][j] % mod;
}
void matPow(Matrix &mat, int exp) {
	static Matrix tmp;
	for(int i = 0; i < maxm; ++i)
		for(int j = 0; j < maxm; ++j) {
			tmp[i][j] = mat[i][j];
			mat[i][j] = i == j;
		}
	for( ; exp > 0; exp >>= 1, matMul(tmp, tmp, tmp))
		if(exp & 1)
			matMul(mat, tmp, mat);
}
int t, n, L, R, q;
Matrix f[maxd];
inline void mod_inc(int &x, int y) {
	(x += y) >= mod && (x -= mod);
}
int main() {
	scanf("%d", &t);
	while(t--) {
		scanf("%d%d%d%d", &n, &L, &R, &q);
		int mx = 0;
		for( ; 1 << mx <= R; ++mx);
		for(int i = 0; i < mx; ++i) {
			Matrix &mat = f[i];
			for(int j = 0; j < maxm; ++j)
				for(int k = 0; k < maxm; ++k)
					mat[j][k] = 0;
			int mask = 1 << i, curR = (R & mask) > 0, lowR = R & (mask - 1);
			for(int i = 0; i < half; ++i)
				mod_inc(mat[i][i ^ curR], lowR + 1);
			if(curR)
				for(int i = 0; i < half; ++i)
					mod_inc(mat[i][quat | i], i & quat ? mask : 1);
			if(L) {
				int curL = ((L - 1) & mask) > 0, lowL = (L - 1) & (mask - 1);
				for(int i = 0; i < half; ++i)
					mod_inc(mat[i][half | (i ^ curL)], mod - (lowL + 1));
				if(curL)
					for(int i = 0; i < half; ++i)
						mod_inc(mat[i][half | quat | i], mod - (i & quat ? mask : 1));
			}
			for(int i = half; i < maxm; ++i)
				for(int j = 0; j < maxm; ++j)
					mat[i][j] = mat[i ^ half][j ^ half];
			matPow(mat, n);
		}
		while(q--) {
			int K;
			scanf("%d", &K);
			if(K >= (1 << mx)) {
				puts("0");
				continue;
			}
			if(!R) {
				puts("1");
				continue;
			}
			int ans = 0;
			bool even = 1, odd = 1;
			for(int i = mx - 1; i >= 0 && (even || odd); --i) {
				Matrix &mat = f[i];
				int curL = L && (((L - 1) >> i) & 1), curR = (R >> i) & 1, curK = (K >> i) & 1;
				if(even)
					mod_inc(ans, mat[0][quat | curK]);
				if(odd)
					mod_inc(ans, mat[0][half | quat | curK]);
				int emsk = n & 1 ? curR : 0, omsk = emsk ^ curL ^ curR;
				even &= emsk == curK;
				odd &= omsk == curK;
			}
			Matrix &mat = f[0];
			int curK = K & 1;
			if(even)
				mod_inc(ans, mat[0][curK]);
			if(odd)
				mod_inc(ans, mat[0][half | curK]);
			printf("%d\n", ans);
		}
	}
	return 0;
}

J. Just So You Know

Shortest judge solution: 5085 bytes.

猜测过程实质上构建了一棵决策树,也相当于对 A A A 的所有子串 B B B 进行哈夫曼编码。

考虑 A A A 的后缀树,即所有 A A A 的后缀(加上一个不存在的字符 $)组成的字典树,可知树上每个节点到根的路径形成的字符串两两不同,而这样的字符串在 A A A 中出现的次数等于这个节点的子树里叶子的个数。由于这棵树只会有 n n n 个叶子,所以这棵树至多有 ( n − 1 ) (n - 1) (n1) 次分叉。不妨将只有一个孩子的节点与它的孩子合并,这样整棵树将只剩下 ( 2 n − 1 ) (2 n - 1) (2n1) 个节点,将会很方便统计有多少子串 B B B 相应地在 A A A 中出现了多少次。这个过程可以通过构建后缀数组和构建虚树(即被压缩的后缀树)做到线性复杂度,其中线性时间构建后缀数组可以使用诱导排序。

现在尝试在线性时间内计算构建哈夫曼树的代价,我们需要按照出现次数升序枚举所有可能的 B B B。令 c i c_i ci 表示在 A A A 中出现了 i i i 次的子串数量 ( i = 1 , 2 , ⋯   , n ) (i = 1, 2, \cdots, n) (i=1,2,,n),则有 ∑ i = 1 n i c i = n ( n + 1 ) 2 \sum_{i = 1}^{n}{i c_i} = \frac{n (n + 1)}{2} i=1nici=2n(n+1)。如果出现次数不超过 n n n,我们可以枚举合并出现次数均为 i i i 的节点,或是出现次数分别为 i i i ( i + 1 ) (i + 1) (i+1) 的节点,然后更新相应的 c 2 i c_{2 i} c2i c 2 i + 1 c_{2 i + 1} c2i+1。如果出现次数超过 n n n,则这样的节点不会超过 n 2 \frac{n}{2} 2n 个,使用一个队列维护这样的节点,每次只需要合并队头的两个节点,向队尾增加新节点,即可做到线性复杂度。

顺带一提,虽然标程的时间复杂度是 O ( n ) \mathcal{O}(n) O(n),但是在赛前的测试中,一些时间复杂度 O ( n log ⁡ n ) \mathcal{O}(n \log n) O(nlogn) 的程序也通过了全部的测试数据。

#include <bits/stdc++.h>
using namespace std;
const int maxn = (int)1e6 + 1, maxn2 = maxn << 1 | 1;
int t, n, seq[maxn2], sa[maxn], rk[maxn], ht[maxn], ctr[maxn], pos[maxn], *cur = ht;
bool typ[maxn2];
inline void pushS(int seq[], int x) {
	sa[cur[seq[x]]--] = x;
}
inline void pushL(int seq[], int x) {
	sa[cur[seq[x]]++] = x;
}
inline void inducedSort(int n, int m, int seq[], bool typ[], int n1, int v[]) {
	memset(sa, -1, n * sizeof(int));
	memset(ctr, 0, m * sizeof(int));
	for(int i = 0; i < n; ++i)
		++ctr[seq[i]];
	for(int i = 1; i < m; ++i)
		ctr[i] += ctr[i - 1];
	for(int i = 0; i < m; ++i)
		cur[i] = ctr[i] - 1;
	for(int i = n1 - 1; i >= 0; --i)
		pushS(seq, v[i]);
	for(int i = 1; i < m; ++i)
		cur[i] = ctr[i - 1];
	for(int i = 0; i < n; ++i)
		if(sa[i] > 0 && typ[sa[i] - 1])
			pushL(seq, sa[i] - 1);
	for(int i = 0; i < m; ++i)
		cur[i] = ctr[i] - 1;
	for(int i = n - 1; i >= 0; --i)
		if(sa[i] > 0 && !typ[sa[i] - 1])
			pushS(seq, sa[i] - 1);
}
inline void sais(int n, int m, int seq[], bool typ[], int pos[]) {
	int n1 = typ[n - 1] = 0, ch = rk[0] = -1, *seq1 = seq + n;
	for(int i = n - 2; i >= 0; --i)
		typ[i] = seq[i] == seq[i + 1] ? typ[i + 1] : (seq[i] > seq[i + 1]);
	for(int i = 1; i < n; ++i)
		rk[i] = typ[i - 1] && !typ[i] ? (pos[n1] = i, n1++) : -1;
	inducedSort(n, m, seq, typ, n1, pos);
	for(int i = 0, j, k, x, y; i < n; ++i) {
		if((x = rk[sa[i]]) < 0)
			continue;
		if(ch < 1 || pos[x + 1] - pos[x] != pos[y + 1] - pos[y])
			++ch;
		else
			for(j = pos[x], k = pos[y]; j <= pos[x + 1]; ++j, ++k)
				if((seq[j] << 1 | typ[j]) != (seq[k] << 1 | typ[k])) {
					++ch;
					break;
				}
		seq1[y = x] = ch;
	}
	if(ch + 1 < n1)
		sais(n1, ch + 1, seq1, typ + n, pos + n1);
	else
		for(int i = 0; i < n1; ++i)
			sa[seq1[i]] = i;
	for(int i = 0; i < n1; ++i)
		seq1[i] = pos[sa[i]];
	inducedSort(n, m, seq, typ, n1, seq1);
}
inline void SuffixArray() {
	int m = 101;
	memset(ctr, 0, m * sizeof(int));
	for(int i = 0; i <= n; ++i)
		ctr[seq[i]] = 1;
	for(int i = 1; i < m; ++i)
		ctr[i] += ctr[i - 1];
	for(int i = 0; i <= n; ++i)
		seq[i] = ctr[seq[i]] - 1;
	sais(n + 1, ctr[m - 1], seq, typ, pos);
	for(int i = 0; i < n; ++i)
		rk[sa[i] = sa[i + 1]] = i;
	for(int i = 0, j, k = ht[0] = 0; i < n; ++i) {
		if(k)
			--k;
		if(!rk[i])
			continue;
		for(j = sa[rk[i] - 1]; seq[i + k] == seq[j + k]; ++k);
		ht[rk[i]] = k;
	}
}
int vtot, etot, lnk[maxn2], fa[maxn2], sz[maxn2], dis[maxn2], ord[maxn2];
struct Edge {
	int nxt, v;
} e[maxn2];
inline int newNode() {
	lnk[vtot] = -1;
	fa[vtot] = sz[vtot] = dis[vtot] = 0;
	return vtot++;
}
inline void addEdge(int u, int v, int w) {
	fa[v] = u;
	dis[v] = dis[u] + w;
	e[etot] = (Edge){lnk[u], v};
	lnk[u] = etot++;
}
inline void SuffixTree() {
	vtot = etot = 0;
	int rt = newNode(), last;
	addEdge(rt, last = newNode(), n - sa[0]);
	++sz[last];
	for(int i = 1; i < n; ++i) {
		int p = last;
		for( ; dis[p] > ht[i]; p = fa[p]);
		int dt = ht[i] - dis[p];
		if(dt > 0) {
			int q = newNode(), v = e[lnk[p]].v, w = dis[v] - dis[p];
			fa[q] = p;
			dis[q] = dis[p] + dt;
			e[lnk[p]].v = q;
			addEdge(q, v, w - dt);
			p = q;
		}
		addEdge(p, last = newNode(), n - sa[i] - ht[i]);
		++sz[last];
	}
	vtot = 0;
	ord[vtot++] = rt;
	for(int i = 0; i < vtot; ++i)
		for(int u = ord[i], it = lnk[u]; it != -1; it = e[it].nxt)
			ord[vtot++] = e[it].v;
	for(int i = vtot - 1; i >= 0; --i)
		for(int u = ord[i], it = lnk[u]; it != -1; it = e[it].nxt)
			sz[u] += sz[e[it].v];
}
long long rem[maxn], que[maxn2];
inline long long HaffmanCode() {
	for(int i = 1; i < vtot; ++i)
		rem[sz[i]] += dis[i] - dis[fa[i]];
	int L = 0, R = 0;
	long long pre = 0, cur, ret = 0;
	for(int i = 1; i <= n; ++i) {
		if(!rem[i])
			continue;
		if(pre > 0) {
			cur = pre + i;
			ret += cur;
			pre = 0;
			--rem[i];
			if(cur <= n)
				++rem[cur];
			else
				que[R++] = cur;
		}
		long long half = rem[i] >> 1;
		cur = i << 1;
		ret += cur * half;
		rem[i] -= half << 1;
		if(cur <= n)
			rem[cur] += half;
		else
			while(half--)
				que[R++] = cur;
		if(rem[i] > 0) {
			pre = i;
			--rem[i];
		}
	}
	if(pre > 0 && L < R) {
		cur = pre + que[L++];
		ret += cur;
		pre = 0;
		que[R++] = cur;
	}
	while(L + 1 < R) {
		pre = que[L++];
		cur = pre + que[L++];
		ret += cur;
		que[R++] = cur;
	}
	return ret;
}
inline bool isDigit(char ch) {
	return ch >= '0' && ch <= '9';
}
int main() {
	scanf("%d", &t);
	while(t--) {
		static char buf[3 << 20 | 1];
		scanf("%d ", &n);
		fgets(buf, n * 3, stdin);
		for(int i = 0, j = 0; i < n; ++i) {
			for( ; buf[j] && !isDigit(buf[j]); ++j);
			for(seq[i] = 0; isDigit(buf[j]); seq[i] = (seq[i] << 3) + (seq[i] << 1) + (buf[j++] - '0'));
			++seq[i];
		}
		seq[n] = 0;
		SuffixArray();
		SuffixTree();
		long long fz = HaffmanCode(), fm = n * (n + 1LL) / 2, com = __gcd(fz, fm);
		if(fm == com)
			printf("%lld\n", fz / com);
		else
			printf("%lld/%lld\n", fz / com, fm / com);
	}
	return 0;
}

K. Kaleidoscope

Shortest judge solution: 1248 bytes.

平面展开图是逗你笑的,解决这道题并不需要它。

这是一道 Pólya 定理的经典问题。使用该定理后,我们只需要考虑有多少种方案能给每个轨道选择颜色使得颜色数量满足限制。对于每种情况,若每 d d d 个面都要选择相同的颜色,我们可以用动态规划计算用前 i i i 种颜色涂 j j j 个面并满足颜色数量限制的方案数 f ( i , j ) f(i, j) f(i,j),并且只考虑 d ∣ j d | j dj 的情况。事实上,菱形六面体的旋转只有 60 60 60 种不同的等价类,它们可以分为 4 4 4 类旋转群。时间复杂度可以做到 O ( 4 ⋅ 6 0 2 n ) \mathcal{O}(4 \cdot 60^2 n) O(4602n)

顺带一提,为了最后在模 p p p 意义下做一次除法,你可以让其他部分在模 p p p 乘以除数意义下计算,但要注意 64 64 64 位整型数可能溢出。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 60, maxd = 4, len[maxd + 1] = {1, 2, 3, 5}, coeff[maxd + 1] = {1, 15, 20, 24}, BLEN = 18, BMSK = (1 << 18) - 1;
int t, n, m, a[maxn + 1], sum[maxd + 1];
LL c[maxn + 1][maxn + 1], f[maxn + 1];
inline LL mod_mul(LL x, LL y, LL m) {
	LL yH = y >> BLEN, yL = y & BMSK;
	LL ret = yH ? ((x * yH % m) << BLEN) % m : 0;
	return (ret + x * yL) % m;
}
int main() {
	scanf("%d", &t);
	while(t--) {
		scanf("%d%d", &n, &m);
		if(m == 1) {
			for(int i = 0; i < n; ++i)
				scanf("%*d");
			puts("0");
			continue;
		}
		memset(sum, 0, sizeof sum);
		for(int i = 0; i < n; ++i) {
			scanf("%d", a + i);
			for(int j = 0; j < maxd; ++j)
				sum[j] += a[i] ? (a[i] - 1) / len[j] + 1 : 0;
		}
		LL ans = 0, mod = (LL)maxn * m;
		for(int i = 0; i <= maxn; ++i) {
			c[i][0] = c[i][i] = 1;
			for(int j = 1; j < i; ++j)
				(c[i][j] = c[i - 1][j - 1] + c[i - 1][j]) >= mod && (c[i][j] -= mod);
		}
		for(int i = 0; i < maxd; ++i) {
			int upp = maxn / len[i];
			if(sum[i] > upp)
				continue;
			memset(f, 0, sizeof f);
			f[0] = 1;
			for(int j = 0; j < n; ++j) {
				int low = a[j] ? (a[j] - 1) / len[i] + 1 : 0;
				for(int x = upp; x >= 0; --x) {
					LL res = 0;
					for(int y = low; y <= upp; ++y)
						(res += mod_mul(c[x][y], f[x - y], mod)) >= mod && (res -= mod);
					f[x] = res;
				}
			}
			ans = (ans + coeff[i] * f[upp]) % mod;
		}
		assert(ans % maxn == 0);
		printf("%d\n", (int)(ans / maxn));
	}
	return 0;
}

L. Lost In The Echo

Shortest judge solution: 5116 bytes.

这道题目较为复杂,这里将只给出简要做法。需要提醒的是,这不是一道 OEIS 数列题,因为 OEIS 上没有给出这道题的解法;这也不是一道论文题,因为没有论文介绍它,不过感兴趣的同学可以写一篇论文对其进行研究。

考虑只有加减运算符或只有乘除运算符的表达式,例如 a − b + c a - b + c ab+c a / b ∗ c a / b * c a/bc。我们可以将其改写为 0 + a − b + c 0 + a - b + c 0+ab+c 1 ∗ a / b ∗ c 1 * a / b * c 1a/bc,因此可知涉及 n n n 个变量的这类表达式有 ( 2 n − 1 ) (2^n - 1) (2n1) 种,因为只有 0 − a − b − c 0 - a - b - c 0abc 1 / a / b / c 1 / a / b / c 1/a/b/c 这类表达式是无法得到的。

考虑只有加乘除运算符和括号的表达式,例如 a + b / c a + b / c a+b/c a ∗ ( b + c ) a * (b + c) a(b+c)。我们可以将其划分层次,每一层要么只有加运算符,要么只有乘除运算符,并且相邻两层没有同类运算符,这使得我们可以对其进行统计。考虑由最外层运算符划分的一系列子表达式,我们需要计算每个子表达式的方案数,而这是相似的问题。令 f ( n ) f(n) f(n) 表示涉及 n n n 个变量且最外层为加运算符的表达式数量, g ( n ) g(n) g(n) 表示涉及 n n n 个变量且最外层为乘除运算符的表达式数量。指数型生成函数可以便于我们对带标号的对象进行统计,故定义指数型生成函数 F ( x ) = ∑ n ≥ 1 f ( n ) x n n ! F(x) = \sum_{n \geq 1}{\frac{f(n) x^n}{n!}} F(x)=n1n!f(n)xn, G ( x ) = ∑ n ≥ 1 g ( n ) x n n ! G(x) = \sum_{n \geq 1}{\frac{g(n) x^n}{n!}} G(x)=n1n!g(n)xn,则有 F ( x ) = ∑ k ≥ 2 G k ( x ) k ! F(x) = \sum_{k \geq 2}{\frac{G^k(x)}{k!}} F(x)=k2k!Gk(x), G ( x ) = ∑ k ≥ 2 ( 2 k − 1 ) F k ( x ) k ! G(x) = \sum_{k \geq 2}{\frac{(2^k - 1) F^k(x)}{k!}} G(x)=k2k!(2k1)Fk(x)。在第一个等式中,我们枚举 k k k 个子表达式的无序组合,在它们之间加上加运算符。在第二个等式中,我们枚举 k k k 个子表达式的无序组合后有 ( 2 k − 1 ) (2^k - 1) (2k1) 种方法加上乘除运算符。

不妨定义 E F ( x ) = ∑ k ≥ 2 F k ( x ) n ! E_F(x) = \sum_{k \geq 2}{\frac{F^k(x)}{n!}} EF(x)=k2n!Fk(x), E 2 F ( x ) = ∑ k ≥ 2 ( 2 F ( x ) ) k k ! E_{2 F}(x) = \sum_{k \geq 2}{\frac{(2 F(x))^k}{k!}} E2F(x)=k2k!(2F(x))k, E G ( x ) = ∑ k ≥ 2 G k ( x ) k ! E_G(x) = \sum_{k \geq 2}{\frac{G^k(x)}{k!}} EG(x)=k2k!Gk(x),那么有 F ( x ) = E G ( x ) F(x) = E_G(x) F(x)=EG(x), G ( x ) = E 2 F ( x ) − E F ( x ) G(x) = E_{2 F}(x) - E_F(x) G(x)=E2F(x)EF(x)。通过关于 x x x 求导 E F E_F EF,我们知道 E F ′ ( x ) = ∑ k ≥ 2 F ′ ( x ) F k − 1 ( x ) ( k − 1 ) ! = F ′ ( x ) ∑ k ≥ 1 F k ( x ) k ! = F ′ ( x ) ( F ( x ) + E F ( x ) ) E_{F}'(x) = \sum_{k \geq 2}{\frac{F'(x) F^{k - 1}(x)}{(k - 1)!}} = F'(x) \sum_{k \geq 1}{\frac{F^k(x)}{k!}} = F'(x) \left(F(x) + E_{F}(x)\right) EF(x)=k2(k1)!F(x)Fk1(x)=F(x)k1k!Fk(x)=F(x)(F(x)+EF(x)),那么对于 n ≥ 2 n \geq 2 n2 [ x n ] E F ( x ) = 1 n ( ∑ i = 1 n − 1 i [ x i ] F ( x ) ⋅ [ x n − i ] ( F ( x ) + E F ( x ) ) ) [x^n]E_F(x) = \frac{1}{n}\left(\sum_{i = 1}^{n - 1}{i [x^i] F(x) \cdot [x^{n - i}]\left(F(x) + E_F(x)\right)}\right) [xn]EF(x)=n1(i=1n1i[xi]F(x)[xni](F(x)+EF(x))),这可以通过分治过程配合快速卷积在 O ( n log ⁡ 2 n ) \mathcal{O}(n \log^2 n) O(nlog2n) 时间内计算得出。一些经典的问题给出 G ( x ) G(x) G(x) F ′ ( x ) = G ′ ( x ) F ( x ) F'(x) = G'(x) F(x) F(x)=G(x)F(x) 并需要你计算 F ( x ) F(x) F(x),这是不同于本题目的。本题目中待求解的信息同时出现在卷积式两边,需要合理安排分治卷积的过程才能计算,这个留给读者作为练习。

最后考虑有加减乘除运算符和括号的表达式,例如 a − b / ( c − d ) a - b / (c - d) ab/(cd) a + b / ( d − c ) a + b / (d - c) a+b/(dc)。我们需要注意到分配律所带来的影响,比如上述两个例子是等价的。事实上,除了不包含减运算符的表达式之外,对于任意一个表达式,我们能构造一个与之符号相反的表达式,例如 a + b + c / ( d − e ) a + b + c / (d - e) a+b+c/(de) 对应 c / ( e − d ) − a − b c / (e - d) - a - b c/(ed)ab。如果我们统计忽略符号的表达式的数量,以及不包含减运算符的表达式的数量,那么就能算出答案。如果忽略符号但是允许使用减运算符,则有 F ( x ) = ∑ k ≥ 2 2 k − 1 G k ( x ) k ! F(x) = \sum_{k \geq 2}{\frac{2^{k - 1} G^k(x)}{k!}} F(x)=k2k!2k1Gk(x), G ( x ) = ∑ k ≥ 2 ( 2 k − 1 ) G k ( x ) k ! G(x) = \sum_{k \geq 2}{\frac{(2^k - 1) G^k(x)}{k!}} G(x)=k2k!(2k1)Gk(x)

#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef complex<double> Complex;
const int maxLen = 16, maxm = 1 << maxLen | 1, mod = (int)1e9 + 7, SP = 15, MSK = 32767, SD = 73741817; // (1 << (SP + SP)) % mod
const double pi = acos(-1.0);
int f[maxm], g[maxm];
Complex w[maxm], A[maxm], B[maxm], C[maxm], D[maxm];
inline void FFT(int n, Complex a[], int flag) { // dft(a) or idft(a) * n
	static int bitLen = 0, bitRev[maxm] = {};
	if(n != (1 << bitLen)) {
		for(bitLen = 0; 1 << bitLen < n; ++bitLen);
		for(int i = 1; i < n; ++i)
			bitRev[i] = (bitRev[i >> 1] >> 1) | ((i & 1) << (bitLen - 1));
	}
	for(int i = 0; i < n; ++i)
		if(i < bitRev[i])
			swap(a[i], a[bitRev[i]]);
	for(int i = 1, d = 1; d < n; ++i, d <<= 1)
		for(int j = 0; j < n; j += d << 1)
			for(int k = 0; k < d; ++k) {
				Complex &AL = a[j + k], &AH = a[j + k + d];
				Complex TP = w[k << (maxLen - i)] * AH;
				AH = AL - TP;
				AL = AL + TP;
			}
	if(flag == -1)
		reverse(a + 1, a + n);
}
inline void cyc_conv(int len, int a[], int b[]) { // a = a * b
	for(int i = 0; i < len; ++i) {
		A[i] = Complex(a[i] & MSK, a[i] >> SP);
		B[i] = Complex(b[i] & MSK, b[i] >> SP);
	}
	FFT(len, A, 1);
	FFT(len, B, 1);
	Complex trL(0.5, 0), trH(0, -0.5), tr(0, 1);
	for(int i = 0; i < len; ++i) {
		int j = (len - i) & (len - 1);
		Complex AL = (A[i] + conj(A[j])) * trL;
		Complex AH = (A[i] - conj(A[j])) * trH;
		Complex BL = (B[i] + conj(B[j])) * trL;
		Complex BH = (B[i] - conj(B[j])) * trH;
		C[i] = AL * (BL + BH * tr);
		D[i] = AH * (BL + BH * tr);
	}
	FFT(len, C, -1);
	FFT(len, D, -1);
	for(int i = 0; i < len; ++i) {
		int v11 = (LL)(C[i].real() / len + 0.5) % mod;
		int v12 = (LL)(C[i].imag() / len + 0.5) % mod;
		int v21 = (LL)(D[i].real() / len + 0.5) % mod;
		int v22 = (LL)(D[i].imag() / len + 0.5) % mod;
		a[i] = (v11 + ((LL)(v12 + v21) << SP) + (LL)v22 * SD) % mod;
	}
}
const int maxn = (int)6e4 + 1;
int inv[maxn], a[maxn], b[maxn], c[maxn], d[maxn], a2[maxn], b2[maxn], d2[maxn];
int da2[maxn], db[maxn], db2[maxn], dc[maxn], dd[maxn], dd2[maxn];
int sa2[maxn], sb[maxn], sb2[maxn], sc[maxn], sd[maxn], sd2[maxn], ans[maxn];
inline int mod_add(int x, int y) {
	return (x += y) < mod ? x : x - mod;
}
inline int mod_sub(int x, int y) {
	return (x -= y) < 0 ? x + mod : x;
}
inline void solve(int L, int R) {
	if(L == R) {
		if(L == 1)
			return;
		int ivs = inv[L] = mod - (int)(mod / L * (LL)inv[mod % L] % mod);
		sa2[L] = (LL)sa2[L] * ivs % mod;
		sb[L] = (LL)sb[L] * ivs % mod;
		sb2[L] = (LL)sb2[L] * ivs % mod;
		sc[L] = (LL)sc[L] * ivs % mod;
		sd[L] = (LL)sd[L] * ivs % mod;
		sd2[L] = (LL)sd2[L] * ivs % mod;
		a[L] = mod_sub(sb2[L], sb[L]);
		b[L] = (LL)sa2[L] * inv[2] % mod;
		c[L] = mod_sub(sd2[L], sd[L]);
		d[L] = sc[L];
		a2[L] = mod_add(a[L], a[L]);
		b2[L] = mod_add(b[L], b[L]);
		d2[L] = mod_add(d[L], d[L]);
		da2[L] = (LL)L * a2[L] % mod;
		db[L] = (LL)L * b[L] % mod;
		db2[L] = (LL)L * b2[L] % mod;
		dc[L] = (LL)L * c[L] % mod;
		dd[L] = (LL)L * d[L] % mod;
		dd2[L] = (LL)L * d2[L] % mod;
		return;
	}
	int M = (L + R) >> 1;
	solve(L, M);
	int len;
	for(len = 1; len < R - L + 1; len <<= 1);
	if(R < L + L) {
		auto proc = [&](int a[], int da[], int sa[]) {
			for(int i = 0, j = L; i < len; ++i, ++j) {
				f[i] = j <= M ? da[j] : 0;
				g[i] = j <= R ? mod_add(a[i], sa[i]) : 0;
			}
			cyc_conv(len, f, g);
			for(int i = M + 1, j = M + 1 - L; i <= R; ++i, ++j)
				sa[i] = mod_add(sa[i], f[j]);
			for(int i = 0, j = L; i < len; ++i, ++j) {
				f[i] = j <= R ? da[i] : 0;
				g[i] = j <= M ? mod_add(a[j], sa[j]) : 0;
			}
			cyc_conv(len, f, g);
			for(int i = M + 1, j = M + 1 - L; i <= R; ++i, ++j)
				sa[i] = mod_add(sa[i], f[j]);
		};
		proc(a2, da2, sa2);
		proc(b, db, sb);
		proc(b2, db2, sb2);
		proc(c, dc, sc);
		proc(d, dd, sd);
		proc(d2, dd2, sd2);
	} else {
		int low = max(M + 1, L + L), upp = min(R, M + M);
		auto proc = [&](int a[], int da[], int sa[]) {
			for(int i = 0, j = L; i < len; ++i, ++j) {
				f[i] = j <= M ? da[j] : 0;
				g[i] = j <= M ? mod_add(a[j], sa[j]) : 0;
			}
			cyc_conv(len, f, g);
			for(int i = low, j = low - L - L; i <= upp; ++i, ++j)
				sa[i] = mod_add(sa[i], f[j]);
		};
		proc(a2, da2, sa2);
		proc(b, db, sb);
		proc(b2, db2, sb2);
		proc(c, dc, sc);
		proc(d, dd, sd);
		proc(d2, dd2, sd2);
	}
	solve(M + 1, R);
}
int main() {
	inv[1] = a[1] = b[1] = c[1] = d[1] = db[1] = dc[1] = dd[1] = ans[1] = 1;
	a2[1] = b2[1] = d2[1] = da2[1] = db2[1] = dd2[1] = 2;
	for(int i = 0, iLim = 1 << maxLen; i < iLim; ++i) {
		int j = i, k = iLim >> 1; // 2 pi / iLim
		for( ; !(j & 1) && !(k & 1); j >>= 1, k >>= 1);
		w[i] = Complex(cos(pi / k * j), sin(pi / k * j));
	}
	solve(1, maxn - 1);
	for(int i = 2, prd = 1; i < maxn; ++i) {
		prd = (LL)prd * i % mod;
		(ans[i] = ((LL)a2[i] + b2[i] - c[i] - d[i]) * prd % mod) < 0 && (ans[i] += mod);
	}
	int t, n;
	scanf("%d", &t);
	while(t--) {
		scanf("%d", &n);
		printf("%d\n", ans[n]);
	}
	return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值