Codeforces Round #576 (Div. 1)
C:
給 個點和 條邊,打印出一個 條邊的邊獨立集或者 個點的點獨立集。可以貪心地枚舉每一條邊,構成邊獨立集,如果最後得到的邊獨立集大於 ,那麼打印邊獨立集。否則不包含在邊獨立集中的點肯定是點獨立集,且至少數量爲 ,此時打印點獨立集即可。
#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:
給定一個 的正方形, 。有些元素是黑的,有些元素是白的,每次可以選擇一個 的矩形( 和 任取),從而把矩形內的元素塗白,花費爲 。問至少多少花費可以將所有點塗白。很容易看出,對於一個 的矩形,其中 ,最多需要花費 將它塗白。爲了降低這個花費,枚舉每個全白行或全白列,然後得到一個新花費爲分隔成兩部分的花費之和,如果新花費小於原花費,則更新。因此需要枚舉每個矩形再加上枚舉行和列,然後進行dp,總的複雜度爲 。
雖然算出複雜度是 ,但是在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的複雜度是 。
因此本題的複雜度爲 。
#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:
題意是給定 個數,要求把它們分成兩個子集,這兩個子集內部所有元素的gcd都爲 。
因爲 到 一共有 個素數,它們乘起來後如果再乘一個素數就超過 了,因此數組中的每個數最多有 個素因子。如果有答案,我們總能構成一個小於等於 個元素的集合:首先選一個起始數,然後每次選一個數幹掉其中的一個素因子。
這樣的話,小集合中的 個數其實可以幹掉任何數。對於大集合,其實其中有效的部分也是一個小集合。我們假設答案爲一個小集合 和一個包含小集合 的大集合 。因此我們隨機選兩個數,只要這兩個數不同時在同一個小集合中,那麼就能得到解,因此每次的成功率爲 。因此在時限內多隨機化選取幾次成功的概率就趨向於正無窮了。
這題的關鍵是要能看出最大爲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;
}