Codeforces Round #746 (Div. 2)
菜的离谱下大分(
Codeforces Round #746 (Div. 2)
\(\mathcal A\)
给定 \(n\) 个数,求用最少的数加到 \(m\),同一个位置的数不能被连续用两次。
找到最大和次大。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
int main() {
int T = read();
while(T --) {
int n = read(), H = read();
int a = 0, b = 0;
for(int i = 1; i <= n; i ++) {
int x = read();
if(x > a)
b = a, a = x;
else if(x > b)
b = x;
}
int ans = H / (a + b) * 2;
if(H % (a + b)) ans += (H % (a + b) > a) ? 2 : 1;
printf("%d\n", ans);
}
return 0;
}
\(\mathcal B\)
每次只能将相隔至少为 \(k\) 的位置上的数对换,问能否使序列升序排列。
不难发现区间 \([n-k+1,k]\) 是动不了的,看它是否和升序的一样即可。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int n, k, a[N], b[N];
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
int main() {
int T = read();
while(T --) {
n = read(), k = read();
for(int i = 1; i <= n; i ++) b[i] = a[i] = read();
sort(b + 1, b + n + 1);
bool flag = true;
for(int i = n - k + 1; i <= k; i ++)
if(a[i] != b[i]) {flag = false; break;}
puts(flag ? "YES" : "NO");
}
return 0;
}
\(\mathcal C\)
给定一棵有点权的树,问能否将树划分为至少两个,至多 \(k\) 个连通块。
使得所有连通块的异或和相同。
赛时吃了 \(6\) 发罚时才过,菜是原罪(
首先假设能划分,若是偶数个连通块则所有点的异或和一定为 \(0\),反过来,若异或和为 \(0\) 则一定能划分。
否则得到所有的异或和为 \(x\),需要划分为异或和均为 \(x\) 的奇数个连通块。
不难发现一定可以不断合并至三个连通块,所以只要 \(k>2\) 就没有至多多少个的限制了,否则无解。
然后从根往下找,分情况讨论:
- 没有子树的异或和为 \(x\),输出
NO
。 - 有 \(1\) 个子树的异或和为 \(x\),将这个子树砍掉,再做一次,若还有异或和为 \(x\) 的子树则有解,否则无解。
- 有 \(>1\) 个子树异或和为 \(x\),输出
YES
。
注意这里统计的实际上是,子树本身异或和为 \(x\),且子树内部没有异或和为 \(x\) 的子子树 的子树的个数。
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 2e5 + 10;
int n, k, ans, val, a[N], sum[N];
vector<int> G[N];
void Clear() {
for(int i = 1; i <= n; i ++) G[i].clear();
val = ans = 0;
}
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
void dfs1(int u, int fa) {
sum[u] = a[u];
for(int v : G[u]) if(v != fa)
dfs1(v, u), sum[u] ^= sum[v];
}
int pos;
int dfs2(int u, int fa) {
int num = 0;
for(int v : G[u]) if(v != fa) num += dfs2(v, u);
if(sum[u] == val && !num) {pos = u; return 1;}
return num;
}
int tot[N];
int dfs3(int u, int fa) {
tot[u] = a[u];
int num = 0;
for(int v : G[u]) if(v != fa && v != pos) num += dfs3(v, u), tot[u] ^= tot[v];
return num + (tot[u] == val);
}
void Work() {
n = read(), k = read(); Clear();
val = 0;
for(int i = 1; i <= n; i ++) a[i] = read(), val ^= a[i];
for(int i = 1; i < n; i ++) {
int u = read(), v = read();
G[u].push_back(v);
G[v].push_back(u);
}
if(!val) {puts("YES"); return;}
if(k == 2) {puts("NO"); return;}
dfs1(1, 0);
for(int u : G[1]) ans += dfs2(u, 1);
if(!ans) puts("NO");
else if(ans == 1) {
int now = dfs3(1, 0);
if(now) puts("YES");
else puts("NO");
}
else puts("YES");
}
int main() {
int T = read();
while(T --) Work();
return 0;
}
\(\mathcal D\)
交互题,给定一棵树的联通方式,但是不告诉你边权。
每次可以询问一个点集,将回答最大的两点间路径的 \(\gcd\)。
要求输出 \((x,y)\),表示两点间路径的 \(\gcd\) 是全局最大的。
只能询问至多 \(12\) 次。
因为 \(\gcd\) 是随着数字增多单调不增的,所以边权最大的边即为最终答案。
考虑用一次询问得到全局最大边权,然后直接对边进行二分。
考虑到边可能不连通,所以需要排个序,按照 dfs 遍历的顺序排序即可。
这是个直接但是可能会假的做法,正解按照欧拉序二分,它有任意一段区间内的点均是联通的特性。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<iostream>
using namespace std;
#define X first
#define Y second
#define MP make_pair
typedef pair<int, int> PII;
const int N = 1010;
int n, t, val;
bool vis[N]; PII e[N];
vector<int> G[N];
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
int Ask(vector<int> s) {
printf("? %d ", s.size());
for(int i : s) printf("%d ", i);
fflush(stdout);
int now; scanf("%d", &now);
return now;
}
void dfs(int u, int fa) {
if(fa) e[++ t] = MP(fa, u);
for(int v : G[u]) if(v != fa) dfs(v, u);
}
int main() {
n = read();
for(int i = 1; i < n; i ++) {
int u = read(), v = read();
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1, 0);
vector<int> s;
for(int i = 1; i <= n; i ++) s.push_back(i);
val = Ask(s);
int l = 1, r = n - 1;
while(l < r) {
int mid = (l + r) >> 1;
vector<int> p;
for(int i = l; i <= mid; i ++) {
if(!vis[e[i].X]) vis[e[i].X] = true, p.push_back(e[i].X);
if(!vis[e[i].Y]) vis[e[i].Y] = true, p.push_back(e[i].Y);
}
for(int i = l; i <= mid; i ++) vis[e[i].X] = vis[e[i].Y] = false;
int now = Ask(p);
if(now == val) r = mid; else l = mid + 1;
}
printf("! %d %d\n", e[l].X, e[l].Y);
return 0;
}
\(\mathcal E\)
给定序列,求最长的子序列满足 \(a(l)\&a(l+1)\&\cdots\&a(r)>a(l)\operatorname{xor}a(l+1)\operatorname{xor}\cdots\operatorname{xor}a(r)\)。
找性质题。
首先长度为奇数的序列不可能成立,因为 \(\&\) 运算得到有的二进制位 \(\operatorname{xor}\) 都有。
偶数序列,则只需要比 \(\&\) 最高位高的位 \(\operatorname{xor}\) 均为 \(0\) 即可,这个可以 \(O(n)\) 直接做。
对于每一位都这样,总复杂度 \(O(n\log n)\)。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e6 + 10;
int n, a[N], pre[N][2];
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
int main() {
n = read();
for(int i = 1; i <= n; i ++) a[i] = read();
int ans = 0;
memset(pre, -1, sizeof(pre));
for(int o = 0; o <= 19; o ++) {
for(int l = 1, r; l <= n; l = r + 1) {
r = l;
if(!(a[l] >> o & 1)) continue;
while(r < n && a[r + 1] >> o & 1) r ++;
int s = 0;
pre[0][(l - 1) & 1] = l - 1;
for(int i = l; i <= r; i ++) {
int k = a[i] >> (o + 1);
s ^= k;
if(pre[s][i & 1] == -1)
pre[s][i & 1] = i;
else
ans = max(ans, i - pre[s][i & 1]);
}
s = 0;
pre[0][(l - 1) & 1] = -1;
for(int i = l; i <= r; i ++) {
int k = a[i] >> (o + 1);
s ^= k;
pre[s][i & 1] = -1;
}
}
}
printf("%d\n", ans);
return 0;
}
\(\mathcal F1\)
给定 \(0/1\) 网格,有四种翻转操作,求将网格翻转至全 \(0\) 的最小代价:
- 翻转包含 \((1,1)\) 的子矩阵,代价 \(1\)。
- 翻转包含 \((n,1)\) 的子矩阵,代价 \(2\)。
- 翻转包含 \((1,m)\) 的子矩阵,代价 \(4\)。
- 翻转包含 \((n,m)\) 的子矩阵,代价 \(3\)。
显然 \(2,3\) 操作是 useless
的。
一个绝妙的转换,用 \(a(i,j)\) 表示 \((i,j)+(i+1,j)+(i,j+1)+(i+1,j+1)\) 的奇偶。
那么原图全 \(0\) 的充要条件是 \(a(i,j)\) 全 \(0\)。
每次操作 \(1\) 只会改变 \(a(i,j)\),而操作 \(4\) 却会改变 \(a(i-1,j-1),a(n,j-1),a(i-1,m),a(n,m)\)。
显然若四者均为 \(1\) 则省了一个代价,否则无用,当然有用也只能用一次。
因为再次使用操作 \(4\) 则可以用至多 \(6\) 次操作 \(1\) 来替换,因为 \(a(n,m)\) 翻了两次相当于没翻。
故只需要找是否有进行一次操作 \(4\) 的机会即可,时间复杂度 \(O(nm)\)。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 510;
int n, m, a[N][N];
char s[N][N];
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
int main() {
n = read(), m = read();
for(int i = 1; i <= n; i ++) scanf("%s", s[i] + 1);
int ans = 0;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++) {
a[i][j] += (s[i][j] == 'B');
a[i][j] += (s[i][j + 1] == 'B');
a[i][j] += (s[i + 1][j] == 'B');
a[i][j] += (s[i + 1][j + 1] == 'B');
a[i][j] &= 1;
ans += a[i][j];
}
bool flag = false;
if(a[n][m])
for(int i = 2; i <= n && !flag; i ++) for(int j = 2; j <= m; j ++)
if(a[i - 1][j - 1] && a[n][j - 1] && a[i - 1][m]) {flag = true; break;}
printf("%d\n", ans - flag);
}
\(\mathcal F2\)
同上,只是代价变为 \(1\ 3\ 4\ 2\)。
显然 \(2,3\) 还是没用。
但这次只要 \(a(i-1,j-1),a(n,j-1),a(i-1,m)\) 三者均有数就能进行 \(4\) 操作。
分析得到相同的 \(i/j\) 不会被操作 \(4\) 进行两次或以上,那样可以被至多 \(4\) 次操作 \(1\) 替换。
故找到所有 \(a(i-1,j-1),a(n,j-1),a(i-1,m)\) 均为 \(1\) 的 \((i,j)\),做二分图最大匹配即可。
真是绝世好题 QwQ
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 510;
int n, m, a[N][N], mat[N];
bool vis[N];
char s[N][N];
vector<int> G[N];
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
bool dfs(int u) {
for(int v : G[u]) if(!vis[v]) {
vis[v] = true;
if(! mat[v] || dfs(mat[v])) {
mat[v] = u;
return true;
}
}
return false;
}
int main() {
n = read(), m = read();
for(int i = 1; i <= n; i ++) scanf("%s", s[i] + 1);
int ans = 0;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++) {
a[i][j] += (s[i][j] == 'B');
a[i][j] += (s[i][j + 1] == 'B');
a[i][j] += (s[i + 1][j] == 'B');
a[i][j] += (s[i + 1][j + 1] == 'B');
a[i][j] &= 1;
if(i != n || j != m) ans += a[i][j];
}
for(int i = 2; i <= n; i ++)
for(int j = 2; j <= m; j ++)
if(a[i - 1][j - 1] && a[n][j - 1] && a[i - 1][m]) G[i].push_back(j);
int num = 0;
for(int i = 2; i <= n; i ++) {
memset(vis, false, sizeof(vis));
num += dfs(i);
}
printf("%d\n", ans - num + (a[n][m] ^ (num & 1)));
return 0;
}