Codeforces Round #576 (Div. 1)
C:
给
3
n
3n
3n 个点和
m
m
m 条边,打印出一个
n
n
n 条边的边独立集或者
n
n
n 个点的点独立集。可以贪心地枚举每一条边,构成边独立集,如果最后得到的边独立集大于
n
n
n ,那么打印边独立集。否则不包含在边独立集中的点肯定是点独立集,且至少数量为
n
n
n ,此时打印点独立集即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5+7;
int vis[N];
struct Edge {
int x, y;
};
vector<int> ans;
int main() {
int T;
scanf("%d", &T);
for(int t=1; t<=T; ++t) {
int n, m;
scanf("%d%d", &n, &m);
ans.clear();
for(int i=1; i<=m; ++i) {
int x, y;
scanf("%d%d", &x, &y);
if(vis[x]!=t&&vis[y]!=t) {
ans.push_back(i);
vis[x]=vis[y]=t;
}
}
if(ans.size()<n) {
ans.clear();
for(int i=1; i<=3*n; ++i) {
if(vis[i]!=t) {
ans.push_back(i);
}
}
puts("IndSet");
} else puts("Matching");
for(int i=0; i<n; ++i) {
printf("%d%c", ans[i], i+1==n?'\n':' ');
}
}
}
D:
给定一个
n
×
n
n\times n
n×n 的正方形,
n
≤
50
n \leq 50
n≤50 。有些元素是黑的,有些元素是白的,每次可以选择一个
w
×
h
w \times h
w×h 的矩形(
w
w
w 和
h
h
h 任取),从而把矩形内的元素涂白,花费为
m
a
x
(
w
,
h
)
max(w, h)
max(w,h)。问至少多少花费可以将所有点涂白。很容易看出,对于一个
W
×
H
W \times H
W×H 的矩形,其中
W
≥
H
W \geq H
W≥H ,最多需要花费
W
W
W 将它涂白。为了降低这个花费,枚举每个全白行或全白列,然后得到一个新花费为分隔成两部分的花费之和,如果新花费小于原花费,则更新。因此需要枚举每个矩形再加上枚举行和列,然后进行dp,总的复杂度为
O
(
n
5
)
O(n^5)
O(n5) 。
虽然算出复杂度是
3
×
1
0
8
3 \times 10^8
3×108 ,但是在CF上400ms就跑完了。。。可见CF跑得飞快。。。
#include <bits/stdc++.h>
using namespace std;
const int N = 53;
int G[N][N];
int dp[N][N][N][N];
int rp[N][N], cp[N][N];
int main() {
int n;
scanf("%d", &n);
for(int i=1; i<=n; ++i) {
char s[N];
scanf("%s", s+1);
for(int j=1; j<=n; ++j) {
if(s[j]=='#') {
G[i][j]=1;
}
rp[i][j] = rp[i][j-1] + G[i][j];
}
}
for(int j=1; j<=n; ++j) {
for(int i=1; i<=n; ++i) {
cp[i][j] = cp[i-1][j] + G[i][j];
}
}
for(int w=0; w<n; ++w) {
for(int h=0; h<n; ++h) {
for(int i=1; i+w<=n; ++i) {
for(int j=1; j+h<=n; ++j) {
int x1=i,y1=j,x2=i+w,y2=j+h;
int &d = dp[x1][y1][x2][y2];
if(w==0&&h==0) {
d = G[i][j];
continue;
}
d = max(w+1, h+1);
for(int r=x1; r<=x2; ++r) {
if(rp[r][y2]-rp[r][y1-1]==0) {
int res = 0;
if(r!=x1) res += dp[x1][y1][r-1][y2];
if(r!=x2) res += dp[r+1][y1][x2][y2];
d = min(d, res);
}
}
for(int c=y1; c<=y2; ++c) {
if(cp[x2][c]-cp[x1-1][c]==0) {
int res = 0;
if(c!=y1) res += dp[x1][y1][x2][c-1];
if(c!=y2) res += dp[x1][c+1][x2][y2];
d = min(d, res);
}
}
// printf("%d %d %d %d %d\n", x1, x2, y1, y2, d);
}
}
}
}
printf("%d\n", dp[1][1][n][n]);
}
E:
题意跟D类似,但是花费变成了min,n也变成了1e9 。给出的不是黑点而是黑块。
因为是min,很容易看出问题可以转化为至少用多少条垂直线和水平线才能覆盖所有的黑点。这是一个网络流经典问题。假定所有行是二分图的一边,所有列是二分图的另一边,那么对于一个黑点,要么选行,要么选列,因此对于每一对行列,如果其交点为黑点,那么将它们在二分图中连边,这样的话每一条边的两端都至少要选一个(选行或是选列),从而问题转化为了最小点覆盖。
因为这题是黑块,因此可以根据x和y的坐标将其划分成若干的区间,x和y对应的区间交出的块要么全黑要么全白。因此可以压缩成一个点,然后用上述方法解决即可。
因为Dinic的复杂度是
O
(
n
2
m
)
O(n^2m)
O(n2m) 。
因此本题的复杂度为
O
(
m
4
)
O(m^4)
O(m4) 。
#include <vector>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <queue>
#include <cstring>
using namespace std;
const int N = 207;
const int INF = 0x3f3f3f3f;
int head[N], cur[N], cnt, dep[N], s, t;
struct Edge {
int u, v, w, nxt;
}e[N*N];
void addedge(int u, int v, int w) {
// printf("add %d %d %d\n", u, v, w);
e[cnt] = {u, v, w, head[u]};
head[u] = cnt++;
e[cnt] = {v, u, 0, head[v]};
head[v] = cnt++;
}
bool bfs() {
memset(dep, -1, sizeof(dep));
memcpy(cur, head, sizeof(head));
dep[s] = 0;
queue<int> q;
q.push(s);
while(!q.empty()) {
int u = q.front(); q.pop();
for(int i=head[u]; i!=-1; i=e[i].nxt) {
if(dep[e[i].v]==-1&&e[i].w) {
dep[e[i].v] = dep[u]+1;
q.push(e[i].v);
}
}
}
return dep[t]!=-1;
}
int dfs(int u, int w) {
if(u==t) return w;
int used = 0;
for(int i=cur[u]; i!=-1; i=e[i].nxt) {
cur[u]=i;
if(dep[e[i].v]==dep[u]+1&&e[i].w) {
int flow = dfs(e[i].v, min(w-used, e[i].w));
used += flow;
e[i].w -= flow;
e[i^1].w += flow;
if(used == w) break;
}
}
return used;
}
struct Line {
int p;
bool operator < (const Line& rhs) const {
return p<rhs.p;
}
bool operator == (const Line& rhs) const {
return p==rhs.p;
}
};
struct Segment {
int l, r;
};
int x1[N], x2[N], y1[N], y2[N];
vector<int> xb, yb;
vector<Segment> xs, ys;
vector<Segment> get_segment(vector<int> b) {
vector<Segment> res;
sort(b.begin(), b.end());
b.erase(unique(b.begin(), b.end()), b.end());
int prev=-1;
// puts("print seg");
for(int p : b) {
if(prev!=-1) {
// printf("seg: %d %d\n", prev+1, p);
res.push_back({prev+1, p});
}
prev = p;
}
return res;
}
bool check(int k, int i, int j) {
bool xj = ((x1[k]>=xs[i].l&&x1[k]<=xs[i].r)||(x2[k]>=xs[i].l&&x2[k]<=xs[i].r));
bool yj = ((y1[k]>=ys[j].l&&y1[k]<=ys[j].r)||(y2[k]>=ys[j].l&&y2[k]<=ys[j].r));
bool cover = x1[k]<=xs[i].l&&x2[k]>=xs[i].r&&y1[k]<=ys[j].l&&y2[k]>=ys[j].r;
return (xj&&yj)||cover;
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
for(int i=0; i<m; ++i) {
scanf("%d%d%d%d", &x1[i], &y1[i], &x2[i], &y2[i]);
xb.push_back(x1[i]-1);
xb.push_back(x2[i]);
yb.push_back(y1[i]-1);
yb.push_back(y2[i]);
}
xb.push_back(0);
xb.push_back(n);
yb.push_back(0);
yb.push_back(n);
xs = get_segment(xb);
ys = get_segment(yb);
int xn = xs.size();
int yn = ys.size();
s = xn+yn;
t = s+1;
memset(head, -1, sizeof(head));
for(int i=0; i<xn; ++i) addedge(s, i, xs[i].r-xs[i].l+1);
for(int i=0; i<yn; ++i) addedge(xn+i, t, ys[i].r-ys[i].l+1);
for(int i=0; i<xn; ++i) {
for(int j=0; j<yn; ++j) {
bool ok = false;
for(int k=0; k<m; ++k) {
ok |= check(k, i, j);
}
if(ok) {
// printf("(%d, %d)->(%d, %d)\n", xs[i].l, xs[i].r, ys[j].l, ys[j].r);
addedge(i, j+xn, INF);
}
}
}
// puts("done");
int ans = 0;
while(bfs()) ans += dfs(s, INF);
printf("%d\n", ans);
}
F:
题意是给定
n
n
n 个数,要求把它们分成两个子集,这两个子集内部所有元素的gcd都为
1
1
1 。
因为
2
2
2 到
23
23
23 一共有
9
9
9 个素数,它们乘起来后如果再乘一个素数就超过
1
0
9
10^9
109 了,因此数组中的每个数最多有
9
9
9 个素因子。如果有答案,我们总能构成一个小于等于
10
10
10 个元素的集合:首先选一个起始数,然后每次选一个数干掉其中的一个素因子。
这样的话,小集合中的
10
10
10 个数其实可以干掉任何数。对于大集合,其实其中有效的部分也是一个小集合。我们假设答案为一个小集合
S
1
S_1
S1 和一个包含小集合
S
2
S_2
S2 的大集合
T
T
T 。因此我们随机选两个数,只要这两个数不同时在同一个小集合中,那么就能得到解,因此每次的成功率为
n
−
10
n
\frac{n-10}{n}
nn−10 。因此在时限内多随机化选取几次成功的概率就趋向于正无穷了。
这题的关键是要能看出最大为10的小集合就能构造出一个gcd为0的集合,然后就可以随机化乱搞了。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
int a[N], id[N], ans[N];
int main() {
srand(233333333);
int n;
scanf("%d", &n);
for(int i=0; i<n; ++i) {
scanf("%d", &a[i]);
id[i] = i;
}
auto start = clock();
while(clock()-start<=0.4*CLOCKS_PER_SEC) {
random_shuffle(id, id+n);
int l=0, r=0;
for(int i=0; i<n; ++i) {
int t = __gcd(l, a[id[i]]);
if(l!=t) {
l=t;
ans[id[i]]=1;
} else {
r=__gcd(r, a[id[i]]);
ans[id[i]]=2;
}
}
if(l==1&&r==1) {
puts("YES");
for(int i=0; i<n; ++i) printf("%d%c", ans[i], i+1==n?'\n':' ');
return 0;
}
}
puts("NO");
return 0;
}