Codeforces Round #576 (Div. 1)
Contest Info
[Practice Link](https://codeforc.es/contest/1198)
Solved | A | B | C | D | E | F |
---|---|---|---|---|---|---|
6/6 | O | O | Ø | Ø | Ø | Ø |
- O 在比赛中通过
- Ø 赛后通过
- ! 尝试了但是失败了
- - 没有尝试
Solutions
A. MP3
题意:
给出一个序列\(a_i\),要求最多只能有\(K\)个不同的数字,\(K\)需要满足:
现在可以有一种操作来改变序列中的数的大小,即\([l, r]\),让小于\(l\)的数变成\(l\),大于\(r\)的数变成\(r\)。
问最少需要变掉几个数使得满足要求。
思路:
显然变掉的数肯定是\([l, r]\)两端的,并且考虑\([l, r]\)的区间长度越长越好,我们先求出最大的满足的\(K\),然后枚举起点,算一下最多保留多少个数即可。
注意找\(k\)的时候最多找到\(32\)就可以了,因为\(2^{32} > n\)
代码:
#include <bits/stdc++.h>
using namespace std;
#define debug(...) { printf("# "); printf(__VA_ARGS__); puts(""); }
const int N = 1e6 + 10;
int n, a[N], b[N], I;
struct Hash {
int a[N], cnt;
Hash() {
cnt = 0;
}
void add(int x) {
a[++cnt] = x;
}
void work() {
sort(a + 1, a + 1 + cnt);
cnt = unique(a + 1, a + 1 + cnt) - a - 1;
}
int get(int x) {
return lower_bound(a + 1, a + 1 + cnt, x) - a;
}
}hs;
int main() {
while (scanf("%d%d", &n, &I) != EOF) {
hs = Hash();
memset(b, 0, sizeof b);
for (int i = 1; i <= n; ++i) scanf("%d", a + i), hs.add(a[i]);
hs.work();
for (int i = 1; i <= n; ++i) a[i] = hs.get(a[i]);
for (int i = 1; i <= n; ++i) ++b[a[i]];
for (int i = 1; i <= hs.cnt; ++i) b[i] += b[i - 1];
I *= 8;
int k = 0;
while (1ll * (k + 1) * n <= I) {
++k;
if (k >= 64) break;
}
k = min(k, 30);
int Max = 1ll << k;
int res = 0;
for (int i = 0; i <= hs.cnt; ++i) {
res = max(b[min(hs.cnt, i + Max)] - b[i], res);
}
printf("%d\n", n - res);
}
return 0;
}
B. Welfare State
题意:
有一个序列\(a_i\),有\(q\)次操作,两种操作类型:
- 将\(a_p\)变成\(x\)
- 将所有小于\(x\)的数都变成\(x\),问所有操作完成之后的序列变成啥样。
思路:
考虑第二种操作对一个数产生影响当且仅当那个位置的数的最后一次第一种操作已经完成,假设\(a_i\)的第一种操作的最后一次发生的时间为\(t_i\),那么\(t_i - q\)这个操作区间里的第二次操作的最大值会对\(a_i\)产生影响,按\(t_i\)排序,暴力一下即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define debug(...) { printf("# "); printf(__VA_ARGS__); puts(""); }
#define pii pair <int, int>
#define fi first
#define se second
const int N = 1e6 + 10;
int n, q;
pii a[N];
int b[N], res[N];
vector <vector<int>> vec;
int main() {
while (scanf("%d", &n) != EOF) {
for (int i = 1; i <= n; ++i) scanf("%d", &a[i].fi), a[i].se = 0, res[i] = a[i].fi;
scanf("%d", &q);
vec.clear(); vec.resize(q + 10);
int op, p, x;
for (int i = 1; i <= q; ++i) {
scanf("%d", &op);
if (op == 1) {
scanf("%d%d", &p, &x);
a[p] = pii(x, i);
res[p] = x;
} else {
scanf("%d", &x);
b[i] = x;
}
}
for (int i = 1; i <= n; ++i) {
vec[a[i].se].push_back(i);
}
int Max = 0;
for (int i = q; i >= 0; --i) {
Max = max(Max, b[i]);
for (auto it : vec[i]) {
res[it] = max(res[it], Max);
}
}
for (int i = 1; i <= n; ++i) printf("%d%c", res[i], " \n"[i == n]);
}
return 0;
}
C. Matching vs Independent Set
题意:
给出一张\(3n\)个点,\(m\)条边的无向图,问能否找到\(Matching\)或者\(IndSet\)。
- \(Matching\):找到\(n\)条边,不共享端点。
- \(IndSet\):找到\(n\)个点,任意两点不相邻。
思路:
我们暴力枚举边,如果这条边的两个端点没有被标记过,那么就将这条边加入\(Matching\)并且标记两个端点。
最后如果加入的边有\(n\)条,那么直接输出这个\(Matching\)。
否则剩下的点都是不相邻的,并且点数大于等于\(n\),因为\(Matching\)没有花掉\(2n\)个点。
代码:
#include <bits/stdc++.h>
using namespace std;
#define debug(...) { printf("# "); printf(__VA_ARGS__); puts(""); }
const int N = 1e6 + 10;
int n, m, need;
int e[N][2], vis[N];
vector <int> res;
int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
need = n; n *= 3;
memset(vis, 0, (n + 10) * sizeof (vis[0]));
for (int i = 1; i <= m; ++i) {
scanf("%d%d", e[i], e[i] + 1);
}
res.clear();
for (int i = 1; i <= m; ++i) {
if (!vis[e[i][0]] && !vis[e[i][1]]) {
vis[e[i][0]] = vis[e[i][1]] = 1;
res.push_back(i);
}
}
if ((int)res.size() >= need) {
puts("Matching");
for (int i = 0; i < need; ++i)
printf("%d%c", res[i], " \n"[i == need - 1]);
} else {
puts("IndSet");
res.clear();
for (int i = 1; i <= n; ++i) if (!vis[i])
res.push_back(i);
for (int i = 0; i < need; ++i)
printf("%d%c", res[i], " \n"[i == need - 1]);
}
}
return 0;
}
D. Rectangle Painting 1
题意:
在一个\(n \cdot n\)的矩形上,有黑点和白点,现在可以有一种操作:
选择一个子矩形,大小为\(h \times w\),将子矩形内的所有点变成白点,代价为\(max(h, w)\)。
思路:
考虑\(f[i][j][k][l]\)表示左上角为\((i, k)\), 右下角为\((j, l)\)的子矩形的最小花费。
转移:
- 直接将这个子矩形染白,代价为\(max(j - i + 1, l - k +1)\)
- 枚举宽的转移点,切成两办转移
- 枚举长的转移点,切成两半转移
可以这样转移的原因是花费只跟长和宽的一边有关。
代码:
#include <bits/stdc++.h>
using namespace std;
#define debug(...) { printf("# "); printf(__VA_ARGS__); puts(""); }
const int N = 50 + 10;
int n;
char s[N][N];
int f[N][N][N][N];
void Min(int &x, int y) {
if (x > y) x = y;
}
int main() {
while (scanf("%d", &n) != EOF) {
for (int i = 1; i <= n; ++i) scanf("%s", s[i] + 1);
memset(f, 0x3f, sizeof f);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
f[i][i][j][j] = (s[i][j] == '#' ? 1 : 0);
}
}
for (int dx = 1; dx <= n; ++dx) {
for (int dy = 1; dy <= n; ++dy) {
for (int x = 1; x + dx - 1 <= n; ++x) {
for (int y = 1; y + dy - 1 <= n; ++y) {
int w = max(dx, dy);
int ex = x + dx - 1, ey = y + dy - 1;
Min(f[x][ex][y][ey], w);
for (int i = x; i < ex; ++i) {
Min(f[x][ex][y][ey], f[x][i][y][ey] + f[i + 1][ex][y][ey]);
}
for (int i = y; i < ey; ++i) {
Min(f[x][ex][y][ey], f[x][ex][y][i] + f[x][ex][i + 1][ey]);
}
}
}
}
}
printf("%d\n", f[1][n][1][n]);
}
return 0;
}
E. Rectangle Painting 2
题意:
在\(n \cdot n\)的矩形上,有黑点和白点,每次可以选择一个矩形\(w \cdot h\)将其然白,费用非\(min(w, h)\),问使得所有黑点变白的最小代价是多少?
思路:
考虑费用是\(min(h, w)\),也就是说染一行的代价和染一列的代价都是\(1\)。
那么我们考虑列与列之间怎么染,可以一块一块染,将所有黑块的坐标离散化,从\(S\)向每块连一条边,流量为其长度。
同理,行与行之间也这么建边。
再考虑行与列之间,我们考虑一个行块和一个列块能相连当他们同属于一个黑块的时候。
那么就连一条边,流量为\(INF\),然后跑最小割即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define INFLL 0x3f3f3f3f3f3f3f3f
const int N = 1e5 + 10;
struct Dicnic {
static const int M = 2e6 + 10;
static const int N = 1e5 + 10;
struct Edge {
int to, nxt;
ll flow;
Edge() {}
Edge(int to, int nxt, ll flow) : to(to), nxt(nxt), flow(flow) {}
} edge[M];
int S, T;
int head[N], tot;
int dep[N];
void init() {
memset(head, -1, sizeof head);
tot = 0;
}
void set(int S, int T) {
this->S = S;
this->T = T;
}
void addedge(int u, int v, int w, int rw = 0) {
edge[tot] = Edge(v, head[u], w);
head[u] = tot++;
edge[tot] = Edge(u, head[v], rw);
head[v] = tot++;
}
bool BFS() {
memset(dep, -1, sizeof dep);
queue<int> q;
q.push(S);
dep[S] = 1;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; ~i; i = edge[i].nxt) {
if (edge[i].flow && dep[edge[i].to] == -1) {
dep[edge[i].to] = dep[u] + 1;
q.push(edge[i].to);
}
}
}
return dep[T] >= 0;
}
ll DFS(int u, ll f) {
if (u == T || f == 0) return f;
ll w, used = 0;
for (int i = head[u]; ~i; i = edge[i].nxt) {
if (edge[i].flow && dep[edge[i].to] == dep[u] + 1) {
w = DFS(edge[i].to, min(f - used, edge[i].flow));
edge[i].flow -= w;
edge[i ^ 1].flow += w;
used += w;
if (used == f) return f;
}
}
if (!used) dep[u] = -1;
return used;
}
ll solve() {
ll ans = 0;
while (BFS()) {
ans += DFS(S, INFLL);
}
return ans;
}
}dicnic;
int S, T, n, m;
struct Hash {
int a[N], cnt;
void init() {
cnt = 0;
}
void add(int x) {
a[++cnt] = x;
}
void work() {
sort(a + 1, a + 1 + cnt);
cnt = unique(a + 1, a + 1 + cnt) - a - 1;
}
int get(int x) {
return lower_bound(a + 1, a + 1 + cnt, x) - a;
}
}hx, hy;
struct node {
int x[2], y[2];
node() {}
void scan() {
scanf("%d%d%d%d", x, y, x + 1, y + 1);
// --x[0]; --y[0];
++x[1]; ++y[1];
hx.add(x[0]); hx.add(x[1]);
hy.add(y[0]); hy.add(y[1]);
}
}a[N];
int main() {
while (scanf("%d%d", &n, &m) != EOF) {
hx.init(); hy.init();
hx.add(1); hx.add(n + 1);
hy.add(1); hy.add(n + 1);
for (int i = 1; i <= m; ++i) a[i].scan();
hx.work(); hy.work();
S = 0; T = 1;
dicnic.init();
for (int i = 2; i <= hx.cnt; ++i) {
dicnic.addedge(S, i, hx.a[i] - hx.a[i - 1]);
}
for (int i = 2; i <= hy.cnt; ++i) {
dicnic.addedge(i + hx.cnt, T, hy.a[i] - hy.a[i - 1]);
}
for (int i = 2; i <= hx.cnt; ++i) {
for (int j = 2; j <= hy.cnt; ++j) {
for (int k = 1; k <= m; ++k) {
if (a[k].x[0] <= hx.a[i - 1] && a[k].x[1] >= hx.a[i] && a[k].y[0] <= hy.a[j - 1] && a[k].y[1] >= hy.a[j]) {
dicnic.addedge(i, hx.cnt + j, INF);
break;
}
}
}
}
dicnic.set(S, T);
printf("%lld\n", dicnic.solve());
}
return 0;
}
F. GCD Groups 2
题意:
有\(n\)个数,要将这\(n\)个数分到两组,使得每组数的\(gcd\)都是\(1\)。
思路:
考虑随机,每次都先判断当前数加入\(A\)组是否能降低\(A\)组的\(gcd\),如果不能就直接加入\(B\)组,然后最后判断\(A、B\)两组的\(gcd\)是否都为\(1\),如果是就输出。
否则继续随机,时间到了就停止,输出\(NO\)。
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 100010
#define pii pair <int, int>
#define fi first
#define se second
int n, vis[N];
pii a[N];
time_t st, ed;
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
void solve() {
mt19937 rd(time(0));
st = time(NULL); ed = time(NULL);
while (difftime(ed * 1000, st * 1000) <= 200) {
memset(vis, 0, sizeof vis);
shuffle(a + 1, a + 1 + n, rd);
int x = 0, y = 0;
int i;
for (i = 1; i <= n; ++i) {
if (gcd(x, a[i].fi) == x) {
y = gcd(y, a[i].fi);
vis[a[i].se] = 2;
} else {
x = gcd(x, a[i].fi);
vis[a[i].se] = 1;
if (x == 1) break;
}
}
for (++i; i <= n; ++i) {
vis[a[i].se] = 2;
y = gcd(y, a[i].fi);
}
if (y == 1) {
puts("YES");
for (int i = 1; i <= n; ++i)
printf("%d%c", vis[i], " \n"[i == n]);
return;
}
ed = time(NULL);
}
puts("NO");
}
int main() {
while (scanf("%d", &n) != EOF) {
for (int i = 1; i <= n; ++i) scanf("%d", &a[i].fi), a[i].se = i;
solve();
}
return 0;
}