Global Round 18
A. Closing The Gap
题意
给定 \(n\) 个数字,每次操作可以选择其中两个数字 \(a_i, a_j\) ,令 \(a_i-1\) 且 \(a_j + 1\) 。
问若干次操作后,极差最小为多少。
分析
显然如果 \(n | \sum_{i=1}^na_i\) ,那么我们一定能找到方案使得每个数字都相同,那么极差为 \(0\) 。
否则,我们可以取每个数字为 \(\lfloor \dfrac {\sum_{i=1}^n a_i} n \rfloor\) ,剩下的数量小于 \(n\) ,平均地分布在每个数字上,这样极差为 \(1\) 。
Code
/* 终点是一切概率的结束,也是一切期望的开始 */
#include <bits/stdc++.h>
using namespace std;
void solve ()
{
int n, sum = 0; cin >> n;
for (int i = 1, x; i <= n && cin >> x; i ++ ) sum += x;
cout << (sum % n ? 1 : 0) << endl;
}
signed main ()
{
cout.tie(0)->sync_with_stdio(0);
int _; for (cin >> _; _--; ) solve();
return 0;
}
B. And It's Non-Zero
题意
给出范围 \([l, r]\) ,你拥有这个范围内的所有正整数。你可以删除某些数字。
问,最少删除多少数字,满足剩下的数字按位和为 \(0\) 。
分析
要使得一些数字的按位和不为 \(0\) ,那么只需要满足存在某一个位,所有数字在这一个位上都为 \(1\) 。
具体来说,我们可以枚举每一位,求出要使所有数字都在这个位上为 \(1\) 的最小删除数量,求最小值即可。
预处理 \(f(i, j)\) 表示前 \(i\) 个数字第 \(j\) 位为 \(0\) 的数量。
Code
/* 终点是一切概率的结束,也是一切期望的开始 */
#include <bits/stdc++.h>
using namespace std;
const int N = 200010, INF = 0x3f3f3f3f;
int f[N][30]; // 前i个数字第j个位为0的数量
void init ()
{
for (int i = 1; i < N; i ++ )
for (int j = 0; j <= 20; j ++ )
f[i][j] = f[i-1][j] + !(i >> j & 1);
}
void solve ()
{
int l, r, res = INF; cin >> l >> r;
for (int i = 0; i <= 20; i ++ ) res = min(f[r][i] - f[l-1][i], res);
cout << res << endl;
}
signed main ()
{
cout.tie(0)->sync_with_stdio(0);
int _; for (init(), cin >> _; _--; ) solve();
return 0;
}
C. Menorah
题意
有 \(n\) 个蜡烛,给出每个蜡烛的起始状态(亮或者灭)。
每次操作可以选择一个 亮着的 蜡烛,让它保持状态不变,且其他所有蜡烛改变状态。
给出每个蜡烛的目标状态,问从起始状态到目标状态至少需要操作几次,如果无解输出 \(-1\) 。
分析
首先我们可以发现,对于起始状态 != 目标状态的所有蜡烛,他们被操作的次数的奇偶性是相同的;对于起始状态 == 目标状态的所有蜡烛,他们被操作的次数的奇偶性也是相同的。但是上述两类的蜡烛操作次数奇偶性不同。
证明:设 \(x_i\) 表示第 \(i\) 个蜡烛的操作次数。
对于第 \(i\) 个蜡烛而言,它改变状态的次数为 \(S = \sum_{j=1}^{i-1}x_i + \sum_{j=i+1}^{n}x_i\) 。
对于任意一个其他的蜡烛 \(j\) ,它改变的次数为 \(S' = S + x_i - x_j\) 。
如果 \(i\) 蜡烛是需要改变状态的,比如从原始的 \(0\) 变成最终的 \(1\) ,那么对于 \(j\) 而言,如果它也需要改变状态,那么 \(S' = S = 1 \pmod 2\) ,从而推出 \(x_i = x_j \pmod 2\) 。如果 \(j\) 不需要改变状态,那么 \(S' != S \pmod 2\) ,推出 \(x_i != x_j \pmod 2\) 。
同理如果 \(i\) 蜡烛不需要改变状态,我们同理可得上述结论。
然后我们可以发现每个蜡烛如果被操作,它最多被操作一次。
所以我们实际上只有两种可能的解法。
- 只对所有起始和目标不同的蜡烛每个做一次操作。
- 只对所有起始和目标相同的蜡烛每个做一次操作。
那么如何求出解法是否可行?
容易发现,我们的操作对象一定为 \(101010101 \ldots\) 。(这里指的是原始的状态)
我们先对原始状态的 \(1\) 操作,由于这个已经操作过了,所以对剩下没有操作的蜡烛,本来是 \(0\) 的现在变成 \(1\) ,我们对 \(1\) 操作,本质上是对原始状态的 \(0\) 操作。
同时,我们对 \(10\) 操作后,本质上是交换了这两个状态,其他状态没有改变。
第一种解法:
假设起始和目标为 \(01\) 的蜡烛数量为 \(k_0\) ,起始和目标为 \(10\) 的蜡烛数量为 \(k_1\) 。那么我们只能每次交换一个 \(01\) 。所以如果 \(k_0 != k_1\) ,那么无法通过交换来达到目标,无解。否则我们交换 \(2 \times k_0\) 次即可交换所有的 \(01\) 对。
第二种解法:
假设起始和目标为 \(00\) 的蜡烛数量为 \(e_0\) ,起始和目标为 \(11\) 的蜡烛数量为 \(e_1\) 。那么我们需要交换 \(01\) 使得他们与目标都不相同,这样如果最后只剩下一个 \(1\) (也就是 \(e_0 = e_1 - 1\)) ,那么操作这个 \(1\) 就可以把其他所有状态改变,也就是全部变成目标状态。
注意特判起始和目标相同的情况。
Code
/* 终点是一切概率的结束,也是一切期望的开始 */
#include <bits/stdc++.h>
using namespace std;
void solve ()
{
int n; cin >> n;
string a, b; cin >> a >> b;
if (a == b) return cout << 0 << endl, void();
int k1 = 0, k0 = 0;
int e1 = 0, e0 = 0;
for (int i = 0; i < n; i ++ )
{
if (a[i] == '1' && b[i] == '0') ++ k1;
else if (a[i] == '0' && b[i] == '1') ++ k0;
else if (a[i] == '1' && b[i] == '1') ++ e1;
else if (a[i] == '0' && b[i] == '0') ++ e0;
}
if (k1 != k0)
{
if (e0 == e1 - 1) return cout << e0 * 2 + 1 << endl, void();
return cout << -1 << endl, void();
}
if (e0 == e1 - 1) return cout << min(e0 * 2 + 1, k1 + k0) << endl, void();
cout << k1 + k0 << endl;
}
signed main ()
{
cout.tie(0)->sync_with_stdio(0);
int _; for (cin >> _; _--; ) solve();
return 0;
}
D. X(or)-mas Tree
题意
给定 \(n\) 个结点的带权无根树和 \(m\) 个限定条件 \((u, v, w)\) 。求出任意一个满足限制的树。
给出的边权如果为 \(-1\) ,表示这个边权待定。
限定条件 \((u, v, w)\) ,表示从 \(u\) 出发到达 \(v\) 的路径上的所有边的异或和的 \(popcount\) 的奇偶性 。
\(popcount\) 指一个数字二进制位下 \(1\) 的数量。
分析
首先有一个等式 \(popcount(x \bigoplus y) = popcount(x) \bigoplus popcount(y) \pmod 2\) 。
证明:假设 \(popcount(x) = k_1, popcount(y) = k_2\) 。
那么 \(popcount(x \bigoplus y) = k_1 + k_2 - cnt \times 2\) ,其中,\(cnt\) 表示在某一位上 \(x = y = 1\) ,这样的位的个数。
显然 \(k1 + k2 - cnt \times 2 = k_1 + k_2 \pmod 2\) 。
设 \(a(u)\) 表示从结点 \(u\) 到达根节点的路径异或和的 \(popcount\) 。
对于限制条件 \((u, v, w)\) , 其实就是 \(a(u \bigoplus v) = a(u) \bigoplus a(v) = w\) 。
对于给出的边 \((u, v, w)\) ,如果给定了边权,其实也是一个限制条件,因为 \(a(u) \bigoplus a(v) = popcount(w)\) 。
我们先把这些限制条件全部扔到另外一张图上。
容易发现,我们给边权填上 \(0, 1\) 即产生两种不同的奇偶性,只需要填 \(0\) 或者 \(1\) 即可。
那么我们对限制图做染色法求出 \(a(i)\) 即可。
Code
/* 终点是一切概率的结束,也是一切期望的开始 */
#include <bits/stdc++.h>
using namespace std;
const int N = 200010, M = 1200010;
#define pop(x) __builtin_popcount(x)
int n, m;
int h[N], rh[N], e[M], w[M], ne[M], idx;
int a[N];
bool vis[N]; // vis 用来染色
bool cant;
/*
a(i) 表示从i到根节点路径上的异或和的popcount
cant 表示无解
*/
void init (int n)
{
for (int i = 1; i <= n; i ++ ) h[i] = rh[i] = -1;
idx = 0;
for (int i = 1; i <= n; i ++ ) a[i] = vis[i] = 0;
cant = 0;
}
void add (int h[], int a, int b, int c)
{
e[idx] = b; w[idx] = c; ne[idx] = h[a]; h[a] = idx ++ ;
}
void dfs (int u, int fa, int c)
{
if (cant) return ;
a[u] = c; vis[u] = true;
for (int i = rh[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == fa) continue;
if (!vis[j]) dfs(j, u, c ^ w[i]);
else if ((a[j] ^ a[u]) != w[i]) { cant = true; return; }
// 如果j被染色过,需要满足限制,否则无解
}
}
void print (int u, int fa)
{
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == fa) continue;
if (w[i] != -1) cout << u << ' ' << j << ' ' << w[i] << endl;
else cout << u << ' ' << j << ' ' << (a[u] ^ a[j]) << endl;
print(j, u);
}
}
void solve ()
{
cin >> n >> m;
init(n);
for (int i = 1; i < n; i ++ )
{
int a, b, c; cin >> a >> b >> c;
add(h, a, b, c); add(h, b, a, c);
if (c != -1) // 边不为-1,做一条限制
add(rh, a, b, pop(c) & 1), add(rh, b, a, pop(c) & 1);
}
// 后面还有m条限制
for (int i = 1; i <= m; i ++ )
{
int a, b, c; cin >> a >> b >> c;
add(rh, a, b, c); add(rh, b, a, c);
}
// 染色
for (int i = 1; i <= n; i ++ ) if (!vis[i]) dfs(i, -1, 0);
if (cant) return cout << "NO\n", void();
cout << "YES\n";
print(1, -1);
}
signed main ()
{
cout.tie(0)->sync_with_stdio(0);
int _; for (cin >> _; _--; ) solve();
return 0;
}