The Millionaire Waltz
以后 ARC 中的优质计数会放到计数合集里面。
ARC162
C
电信诈骗。
Bob 只要每次填 \(K\) 就能保证这个点所有祖先上的子树都非法。于是假设 Alice 有一种填法能使得 \(u\) 合法,Bob 只要往里面填一个 \(K\) 就能使得方案行不通,除非 Alice 需要只用填一个或者根本不用填。
于是子树 \(u\) 合法当且仅当它已经被填满并且 mex 值为 \(K\),或者它只剩下一个空才会被填满并且填满后 mex 值可以为 \(K\)(填之前 mex 就是 \(K\),或者 \(K\) 位置为空并且 \([0, K)\) 内有且仅有 \(1\) 个没有被填上)。
暴力求 mex 即可。
D,E
计数好题。
ARC163
C
tag:贪心
见到 \(\sum\limits_{i = 1}^n \dfrac{1}{A_i}\) 这么奇怪的东西,自然想到考虑一些裂项状物,比如 \(\dfrac{1}{n} - \dfrac{1}{n+1} = \dfrac{1}{n(n+1)}\)。我一开始的构造方法是 \(\dfrac{1}{2}, \dfrac{1}{2} - \dfrac{1}{3}, \dfrac{1}{3} - \dfrac{1}{4}, \dots, \dfrac{1}{n}\)。但是这可能会因为 \(\dfrac{1}{n}\) 和之前重复。
考虑贪心,每次拿出来最大的 \(x\) 裂开成 \(\dfrac{1}{x(x+1)}\) 和 \(\dfrac{1}{x+1}\),正确性不知道,反正感性理解确实对的,测出来也是对的。
总结:
这题是和一位- \(\dfrac{1}{n} - \dfrac{1}{n+1} = \dfrac{1}{n(n+1)}\)
D
计数
E
根本不会,看完题解气笑了。
首先考虑 \(n = 2\) 的情况,通过打一个 \(0\sim 15\) 的表,像这样:
0111 1111 1111 1111
1100 1111 1111 1111
1010 1111 1111 1111
1001 1111 1111 1111
1111 1111 0111 0111
1111 1111 1100 1100
1111 1111 1010 1010
1111 1111 1001 1001
1111 0111 1111 0111
1111 1100 1111 1100
1111 1010 1111 1010
1111 1001 1111 1001
1111 0111 0111 1111
1111 1100 1100 1111
1111 1010 1010 1111
1111 1001 1001 1111
我们可以想到这应该是一个每次规模会乘 \(4\) 的分型,于是从四进制的角度考虑。结合一下上面的表和一些观察,我们发现 \(n = 2\) 的结论应当是:对于存在四进制下某一位满足不同时为零,且这一位存在一个 \(0\),或者这一位相等。
来证明一下,接下来令 \(a, b\) 为两个数:
-
存在 \(0\) 的情况,若两个都是 \(0\) 那么后手必胜,只存在一个为 \(0\) 先手必胜。对于两个相等的情况那么先手必胜。这是显然的。这三种情况作为归纳边界,显然是满足结论的。
-
对必败态进行归纳,证明无论如何操作,总是会变为必胜态。因为是必败态,所以四进制下每一位都有 \(a_i \not= b_j, a_i ,b_j \not= 0\),若 \(a\operatorname{xor} x < a\),那么一定将 \(a\) 的某一个非 \(0\) 的位置变为了 \(0\),又因为四进制下每一位都不相等,通过枚举可以发现修改的这一位要么然会使得这两个四进制位相等要么然修改的四进制位变为 \(0\)。举个例子,\(x = 01, y = 10\),将最后一位 \(x\) 异或上 \(1\) 就会使得 \(x\) 变为 \(0\)。
-
对必胜态进行归纳,证明总存在一种操作变为必败态。
- 对于 \(a_i = b_i\),那么就把这一位异或上 \(a_i\),都变为 \(0\)。
- 对于 \(a_i > b_i = 0\),如果并不是最高位,那么有很多种方法使得异或后两个不相等且都不为 \(0\)。如果就是最高位,那么就异或 \(a_i\),在最高位上 \(b_i\) 异或上 \(a_i\) 就变成了 \(a_i\),一定比原来大,所以取 \(\min\) 仍然保留原来的 \(b_i\)。而 \(a_i\) 在这一位上异或 \(a_i\) 就变为 \(0\),最高位就都是 \(0\) 了,满足必败态。
综上,\(n = 2\) 时先手必胜的充要条件为四进制下存在第 \(i\) 位使得 \(a_i = b_i \not= 0\),或者 \(a_i > b_i = 0\)。
现在拓展到 \(n\) 的情况,根据上面的结论和证明过程不妨大胆猜测:存在四进制下的位数 \(i\),使得 \(A_1\sim A_n\) 中在该位置非 \(0\) 的数均相等。证明可以简单类比一下。
#include <bits/stdc++.h>
using namespace std;
const int N = 100;
int a[N + 10], n;
void init() {
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
if(n == 1) {
cout << "Alice\n";
return ;
}
for(int i = 0; i <= 15; i++) {
vector <int> vec;
int aim = 0;
for(int j = 1; j <= n; j++) {
if(a[j] % 4) aim = a[j] % 4;
vec.push_back(a[j] % 4), a[j] /= 4;
}
int cnt = 0, flag = 1;
for(int j = 0; j < vec.size(); j++) {
if(!vec[j]) cnt++;
if(vec[j] && vec[j] != aim) flag = 0;
}
if(aim && flag) {
cout << "Alice\n";
return ;
}
}
cout << "Bob\n";
}
int main() {
int T; cin >> T;
while(T--) init();
}
总结:
-
不太好说这个抽象离谱的动机。
-
只能说加训博弈论把,这个证明还是值得学习的/fendou
ARC164
D
搞笑诈骗题。
看上去集体移动非常唬人,简单分析一下,如果在一个电荷移动过程中发生了别的地方的抵消,那么一定在它的左侧和右侧,抵消后和抵消前这一部分的电荷总和是不变的,于是可以得到很重要的性质:一个电荷的方向在开始就决定了。那么每个电荷和哪个电荷抵消也就可以直接看成一个括号匹配,提供的权为两个下标之差。于是得到一个序列的权就是向右移动的下标之和减去向左移动的下标之和。
有这个结论即可直接 dp,设 \(f[i, j]\) 为前 \(i\) 个位置中,有 \(j\) 个正号的总权值和,\(g[i, j]\) 为方案数。然后考虑最后一位的符号和方向即可直接转移。
E
tag:dp
爵士好题啊!
很显然这个只和区间断点有关,对于 \(i\) 到 \((i+1)\) 看成一个断点。那么若断点集为 \(x_1, x_2, \dots x_m\),给定的区间必然能用 \([x_i, x_j)\) 表示。于是断点集合应该为 \(l_i, r_{i}+1\) 构成的集合。
得到断点集合后就很容易计算 \(d\) 了。一层提供 \(2^k\) 个断点,所以只要找到最小的 \(d\) 使得 \(2^{d} + 1 \ge m\) 即可。问题在于 \(c\)。对于一个最小的区间,要么然放到倒数第二层,要么然放到最后一层,和相邻区间合并到上面一层,只有这两种决策。约束则是上一层的数量不能超过 \(2^{d-1}\) 个,不进行约束就会出现全部被塞到倒数第二层的情况。
对于贡献,只有最后一层的区间会贡献,一定会和它的兄弟同时贡献,于是一次提供 \(2\) 的贡献。极小的区间在最后一层提供贡献,通过手玩可以发现,对于两个最后一层的儿子 \([l, m], [m + 1, r]\),只有在右端点为 \(m\) 和左端点为 \((m+1)\) 的区间会产生贡献,每个产生的贡献为 \(2\)。这是显然的但是又十分重要的。
于是设 \(f[i, j]\) 为前 \(i\) 个极小区间,有 \(j\) 个在倒数第二层的最小权。若合并两个区间则转移到 \(f[i + 2, j + 1]\)。若单独则合并到 \(f[i + 1, j + 1]\),直接转移即可。
注意边界条件的处理!
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 4096, Q = 2e5;
const ll inf = 1e15;
void upd(ll &x, ll y) {
x = min(x, y);
}
int n, q, cp[Q * 2 + 10];
int cr[N + 10], cl[N + 10];
ll f[N + 3][N + 3];
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> q;
for(int i = 1, x, y; i <= q; i++)
cin >> x >> y,
cp[2 * i - 1] = x, cp[2 * i] = y + 1,
cr[y]++, cl[x]++;
sort(cp + 1, cp + 2 * q + 1);
int len = unique(cp + 1, cp + 2 * q + 1) - cp - 1;
int d = 0;
while((1 << d) + 1 < len + (cp[1] != 1) + (cp[len] != n + 1)) d++;
if(!d) {
cout << 0 << ' ' << q << '\n';
return 0;
}
int lim = (1 << (d - 1));
for(int i = 0; i <= len + 1; i++)
for(int j = 0; j <= lim; j++)
f[i][j] = inf;
f[0][0] = 0;
for(int i = 0; i <= len; i++) {
int b = cp[i + 1];
if(i == 0 && b == 1) {//左边界
f[1][0] = 0;
continue;
}
if(i == len && cp[len] == n + 1) {//右边界
for(int j = 0; j <= lim; j++)
f[len + 1][j] = f[len][j];
continue;
}
for(int j = 0; j <= lim; j++) {
upd(f[i + 1][j + 1], f[i][j]);
upd(f[i + 2][j + 1], f[i][j] + 2ll * (cr[b - 1] + cl[b]));
}
}
ll minn = inf;
for(int j = 0; j <= lim; j++)
minn = min(minn, f[len + 1][j]);
cout << d << ' ' << minn << '\n';
}
总结:
- 感觉这道题属于那种,每一步都很 trivial,但是我就是不会做的题目(大哭
- 动机其实很直接,考虑断点是显然的,之后 \(d\) 直接可求。
- 对于 \(c\) 的计数,分为两部分:一部分是贡献统计,一部分是结构。也许这在别的题是具有启发性的?
ARC165C
C
tag:性质分析。
很好的题目喔!嘟嘟。
sol1:
一个直接的想法是二分答案,另一个直接的想法是这道题答案肯定不会太大。考虑对 \(X\) 判定。显然的结论是:如果存在两条相邻的边边权和小于 \(X\),那么无论如何不可能合法,否则的话只要差超过两条边,这两个就可以在同一个颜色。只有对于经过一条边且边权小于 \(X\) 的才不能为同一个颜色,保留这部分边二分图判定即可。
sol2:
从部分分考虑起,\(m = n - 1\) 怎么做?显然不会让相邻两个点颜色相同,染色后答案即相邻两条边权和最小值,这一点和上面一样。如果多了非树边,若连接两个同色点,那么我们希望这个非树边边权尽可能大,若是异色点当然我们也希望这个尽可能大。第一要义是让边权小的边颜色是不同的,仿照 Kruskal 的流程,建立最小生成树然后染色。然后直接计算答案。
总结:
-
一个总的性质显然是:我们会尽可能让相邻点颜色不相等。然后就可以观察到答案最多是两条边产生的贡献。
-
对于 sol1,动机第一点在于这个太二分了,动机第二点在于观察到答案不会很大,最多只会有两条边得出。所以对两条边和一条边情况分别判定。
-
对于 sol2,则是第一个动机。
D
tag:贪心,调整法
做的时候完全不会,看完题解被气笑了。我的 greedy 水平真是太差了。
因为我们只要找到一组解即可,所以从必要条件出发试试看?对于第一位应当有 \(A_i \le C_i\),构成有向图,强连通缩点后一个 scc 内的点权完全相等。若所有 scc 均只有 \(1\) 个点,那么就出现一组构造方案。否则我们将 scc 内的所有位置缩点成为一个位置,这些位置均相等。对于当前这个位置相等的 \(A_i, C_i\),将 \(A_i, C_i\) 均往后加一。如果第一个串用完了,且长度不相等,那么就是合法的。如果第二个串先于第一个串用完那么就不合法。保留每个位置的 \(A_i, C_i\),每次加边重新计算。时间复杂度为 \(O(n(n+m))\)。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e3, M = 2e3;
struct chk {
int x, y;
};
vector <chk> vd;
int fa[N + 10];
int Find(int x) {
if(fa[x] == x) return x;
else return fa[x] = Find(fa[x]);
}
void Union(int u, int v) {
fa[Find(u)] = Find(v);
}
int n, m, col[N + 10];
int cnt = 0;
vector <int> gra[N + 10], ngr[N + 10];
vector <int> vec[N + 10];
void link(int x, int y) {
gra[x].push_back(y);
ngr[y].push_back(x);
}
namespace kj {
int tot = 0, cl = 0, que[N + 10];
bool vis[N + 10];
void dfs1(int u) {
vis[u] = 1;
for(int i = 0; i < ngr[u].size(); i++) {
int v = ngr[u][i];
if(vis[v]) continue;
dfs1(v);
}
que[++tot] = u;
}
void dfs2(int u) {
col[u] = cl;
vec[cl].push_back(u);
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
if(col[v]) continue;
dfs2(v);
}
}
void kj() {
for(int i = 1; i <= n; i++)
vis[i] = col[i] = 0, vec[i].clear();
cl = 0, tot = 0;
for(int i = 1; i <= n; i++)
if(!vis[i]) dfs1(i);
for(int i = tot; i >= 1; i--) {
int u = que[i];
if(!col[u]) {
++cl;
dfs2(u);
}
}
}
}
int a[N + 10], b[N + 10], c[N + 10], d[N + 10], p[N + 10];
int main() {
// freopen("read.in", "r", stdin);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i++) fa[i] = i;
for(int i = 1; i <= m; i++)
cin >> a[i] >> b[i] >> c[i] >> d[i], p[i] = 1;
cnt = n;
for(int t = 1; t <= n; t++) {
for(int i = 1; i <= m; i++)
if(a[i] <= n && Find(a[i]) != Find(c[i]))
link(Find(a[i]), Find(c[i]));
kj::kj();
bool end = 1;
for(int i = 1; i <= n; i++) {
if(vec[i].size() > 1) {
end = 0;
for(int j = 1; j < vec[i].size(); j++)
Union(vec[i][0], vec[i][j]);
}
}
if(end) {
cout << "Yes\n";
return 0;
}
for(int i = 1; i <= m; i++) {
if(a[i] > n) continue;
while(a[i] <= b[i] && c[i] <= d[i] && Find(a[i]) == Find(c[i])) a[i]++, c[i]++;
if(a[i] > b[i] || c[i] > d[i]) {
if(c[i] > d[i]) {
// cout << i << ' ' << t << endl;
cout << "No\n";
return 0;
}
else if(a[i] > b[i]) a[i] = c[i] = n + 1;
}
}
for(int i = 1; i <= n; i++) gra[i].clear(), ngr[i].clear();
}
cout << "Yes\n";
}
总结:
- 对于一个难以直接构造正确解的问题,考虑从一个部分正确的解出发,逐步调整!
- 考虑必要条件。
E
见计数

浙公网安备 33010602011771号