CF1659A Red Versus Blue
用 B
去均分 R
,咋写都能过。
CF1659B Bit Flipping
可以看作,把每个元素都作了 \(k\) 遍操作。
然后我们可以看作再做 \(k\) 遍操作,每次操作可以翻转一位。
把全部变成 \(1\),剩下的操作全部累在最后一位,使它的负面影响最小。
CF1659C Line Empire
王国只能一次占领(但我感觉没有这个条件也只能这么做)。
然后发现,一旦我占领了一个城市后我没有迁都,那么我再也不会迁都。简单计算推导可以证明这一点。
然后枚举从哪个位置开始迁都就可以了。
CF1659D Reverse Sort Sum
constructive
的题目确实很有意思。
宏观来看,发现对于每个位置先在一段时间里一直是 1
,然后就一直都变成 0
了。
不妨考虑一些简单的情况。对于第一个位置,注意到一旦出现一个 0
,他就再也不会是 1
,则 \(c_1\) 恰好是第一个出现 0
的位置。
简单讨论发现 \(c_i\) 恰好就是第 \(i\) 个 0
的位置(如果第 \(i\) 个位置是 1
)。
则,我们可以轻易一次确定每个位置的值。
int c[MAXN] , a[MAXN];
int main() {
int T;
read(T);
while(T -- > 0) {
int n;
read(n);
for (int i = 1; i <= n; ++i) read(c[i]) , a[i] = 1;
for (int i = 1; i <= n; ++i) {
if(a[i] && c[i]) {
a[c[i] + 1] = 0;
if(c[i] == n) break;
}
else {
a[c[i] + i] = 0;
if(c[i] + i > n) break;
}
}
for (int i = 1; i <= n; ++i) write(a[i] , ' ');
puts("");
}
return 0;
}
CF1659E AND-MEX Walk
带 constructive
的确实比较有难度。
求最小路径权值。优先判断能否找到权值为 0
的路径。因为是 mex
值,就是说我们能够找到一条至少同时包含一位的路径。
然后判断能否为 1
。假设不能为 1
,即无论如何路径中都会出现 1
,因此一定不会出现 2
,否则必不可能出现 1
。
因此现在问题只剩下如何判断能否不造出 1
。那么就要求经过一条偶数边权,同时过程中必须要有其他位伴随 1
存在。
预处理一下,从偶数边权的两端开始dp,计算每个点为起点能否得到这样的路径就好了。
const int MAXN = 1e5 + 5;
int f[35][MAXN] , n , m , chk[MAXN] , dp[MAXN][35];
int findSet(int k , int x) {
if(f[k][x] != x) f[k][x] = findSet(k , f[k][x]);
return f[k][x];
}
vector <pii> G[MAXN];
struct Edge {
int u , v , w;
}E[MAXN];
void Union(int k , int u , int v) {
u = findSet(k , u) , v = findSet(k , v);
f[k][u] = f[k][v];
return;
}
int main() {
read(n),read(m);
for (int i = 1; i <= m; ++i) {
read(E[i].u),read(E[i].v),read(E[i].w);
G[E[i].u].push_back(mp(E[i].v , E[i].w));
G[E[i].v].push_back(mp(E[i].u , E[i].w));
if(!(E[i].w & 1)) {
chk[E[i].u] = chk[E[i].v] = 1;
for (int j = 1; j < 30; ++j) dp[E[i].u][j] = dp[E[i].v][j] = 1;
}
}
queue <pii> q;
for (int i = 1; i <= n; ++i) if(chk[i]) for (int j = 1; j < 30; ++j) q.push(mp(i , j));
while(!q.empty()) {
int x = q.front().fs , y = q.front().sc;
q.pop();
for (auto v:G[x]) {
if((v.sc & 1) && (v.sc & (1 << y)) && !dp[v.fs][y]) {
q.push(mp(v.fs , y));
dp[v.fs][y] = 1;
chk[v.fs] = 1;
}
}
}
for (int i = 0; i < 30; ++i) for (int j = 1; j <= n; ++j) f[i][j] = j;
for (int i = 0; i < 30; ++i) {
int v = (1 << i);
for (int j = 1; j <= m; ++j) if(E[j].w & v) {
Union(i , E[j].u , E[j].v);
}
}
int Q;
read(Q);
while(Q -- > 0) {
int u , v;
read(u),read(v);
int fl = 0;
for (int i = 0; i < 30; ++i) {
if(findSet(i , u) == findSet(i , v)) {
fl = 1;
puts("0");
break;
}
}
if(fl) continue;
if(chk[u]) puts("1");
else puts("2");
}
return 0;
}
CF1659F Tree and Permutation Game
在一棵树上,Alice 始终可以把我们的不在位的点变成两个,因为 Bob 最多只能影响两个位置。
然后我可以很轻易地让他们靠在一起。然后,我可以让一个飞的很远,然后换到正确位置,然后棋子必须移动,但影响不到很远的位置,因此再换一次就好了。
发现很远的要求就是直径达到 3
。因此直径大于等于 3
的树 Alice 必胜。
剩下一个菊花图的情况。
分类讨论一下:
-
如果根节点不在位,并且棋子在根节点上,那 Bob 赢了。因为 Bob 只要保证根节点永远无法归位就好了。
-
如果根节点不在位,棋子在叶子上,那根节点必须一步到位。
-
如果根节点在位,棋子在根节点,发现至少交换次数为奇数就好了,因为任意一个棋子在根节点时,我们都剩奇数次,而到最后只会有两个数未归位,所以棋子在根节点时就可以直接一步归位。
-
如果根节点在位,棋子在叶子节点,下一步棋子必往根节点走,划归为
3
情况即可。
vector <int> G[MAXN];
int n , p[MAXN] , deg[MAXN] , x , f[MAXN] , a[MAXN];
int findSet(int x) {
if(f[x] != x) return f[x] = findSet(f[x]);
return f[x];
}
int main() {
int T;read(T);
while(T -- > 0) {
read(n),read(x);
for (int i = 1; i <= n; ++i) deg[i] = 0;
pii mx = mp(0 , 0);
for (int i = 1; i < n; ++i) {
int u , v;
read(u),read(v);
deg[u] ++ , deg[v] ++;
}
int fl = 1;
for (int i = 1; i <= n; ++i) read(a[i]) , p[a[i]] = i , mx = max(mx , mp(deg[i] , i)) , fl &= (p[i] == i);
int rt = mx.sc;
if(deg[rt] != n - 1 || fl) {
puts("Alice");
continue;
}
if(p[rt] != rt && (x == rt || p[x] == rt)) {
puts("Bob");
continue;
}
else if(p[rt] != rt) {
for (int i = 1; i <= n; ++i) if(p[i] == rt) {
swap(p[i] , p[rt]);
break;
}
x = rt;
}
fl = 1;
for (int i = 1; i <= n; ++i) f[i] = i , fl &= (p[i] == i);
if(fl) {
puts("Alice");
continue;
}
for (int i = 1; i <= n; ++i) f[findSet(i)] = f[findSet(p[i])];
int num = n;
for (int i = 1; i <= n; ++i) if(findSet(i) == i) num --;
if(num == 1 && x != rt && p[x] == x) {
puts("Alice");
continue;
}
num += (x != rt);
if(num & 1) puts("Alice");
else puts("Bob");
}
return 0;
}