Hawk-and-Chicken(2010 ACM-ICPC Multi-University Training Contest(19)——Host by HDU)(Tarjin)
judge: HDUOJ3639
题意
n n n 个学生,每个学生都有一张选票,一共进行 m m m 次投票。每次会有一个学生投票给另一个学生。
票可以累加传递,但学生不能从自己手里获得选票。
样例1. 当 A A A->
C C C, B B B->
C C C, C C C->
D D D, C C C->
E E E时, A A A, B B B的票数为 0 0 0, C C C的票数为 2 2 2, D D D, E E E的票数为 3 3 3。
样例2. 当 A A A->
B B B, B B B->
C C C, C C C->
A A A时, A A A, B B B, C C C都获得 2 2 2票。
获得最多票的学生获胜,当有多个学生都持有最多票数的票时,他们同时为获胜者。求出获胜者的票数以及获胜者的名单。
题解
由于样例2,可以知道每个连通块内的学生的选票相同。所以我们需要缩点。
需要注意的是缩完点之后,关系依然是个图而不是树。
比较棘手的情况就是样例1里的情况,投票链的开始和结尾都有多个端点。所以就需要枚举每一个端点,顺序或者逆序遍历整个图。我选择了逆序遍历。也就是枚举每一个只有入度的点作为起点,反向搜索给自己投票的人。
为了避免重复统计,枚举不同起点时需要给沿途加上不同的记号,以标识哪些点是这次走过和未走过的。
代码
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define m_p make_pair
#define p_i pair<int, int>
#define _for(i, a) for(register int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(register int i = (a), lennn = (b); i <= lennn; ++i)
#define outval(a) cout << "Debuging...|" << #a << ": " << a << "\n"
#define mem(a, b) memset(a, b, sizeof(a))
#define mem0(a) memset(a, 0, sizeof(a))
#define fil(a, b) fill(a.begin(), a.end(), b);
#define scl(x) scanf("%lld", &x)
#define sc(x) scanf("%d", &x)
#define pf(x) printf("%d\n", x)
#define pfl(x) printf("%lld\n", x)
#define abs(x) ((x) > 0 ? (x) : -(x))
#define PI acos(-1)
#define lowbit(x) (x & (-x))
#define dg if(debug)
#define nl(i, n) (i == n - 1 ? "\n":" ")
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;
typedef long long LL;
// typedef __int128 LL;
typedef unsigned long long ULL;
const int maxn = 100005;
const int maxm = 1000005;
const int maxp = 30;
const int inf = 0x3f3f3f3f;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1000000007;
const double eps = 1e-8;
const double e = 2.718281828;
int debug = 0;
inline int read() {
int x(0), f(1); char ch(getchar());
while (ch<'0' || ch>'9') {
if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0'&&ch <= '9') {
x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
struct poi {
};
int n, m;
int num[maxn];
int vis[maxn];
int numOfScc[maxn]; //每个连通块的节点个数
int ind[maxn];
vector<int> G[maxn], g[maxn];
int scccnt; //强连通分量的数量
int sccno[maxn]; //每个点所在的强连通分量的编号,编号从1开始递增
int dfn[maxn]; //深度优先搜索遍历时结点 u 被搜索的次序
int low[maxn]; //u或u的子树能够追溯到的最早的栈中节点的次序号
int tclock; //用于递增dfn
stack<int> q;
void init() {
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
memset(sccno, 0, sizeof(sccno));
memset(num, 0, sizeof(num));
memset(vis, 0, sizeof(vis));
memset(numOfScc, 0, sizeof(numOfScc));
memset(ind, 0, sizeof(ind));
scccnt = tclock = 0;
_for(i, maxn) G[i].clear(), g[i].clear();
while(q.size()) q.pop();
}
void tarjin(int u) {
dfn[u] = low[u] = ++tclock;
q.push(u);
for (int v : G[u]) {
if (!dfn[v]) {
tarjin(v);
low[u] = min(low[u], low[v]);
}
else if (!sccno[v]) {
low[u] = min(low[u], dfn[v]);
}
}
if (dfn[u] == low[u]) {
scccnt++;
int v = -1;
while (v != u) {
v = q.top();
q.pop();
sccno[v] = scccnt;
}
}
}
int dfs(int u, int fl) {
if(vis[u] == fl) return num[u] + 1;
vis[u] = fl;
num[u] = numOfScc[u] - 1;
for(int v : g[u]) {
if(vis[v] != fl) num[u] += dfs(v, fl);
}
return num[u] + 1;
}
void sol(int ttt) {
init();
_for(i, m) {
int u = read(), v = read();
G[u].push_back(v);
}
_for(i, n) {
if(!dfn[i]) tarjin(i);
}
_for(i, n) ++numOfScc[sccno[i]];
_for(u, n) {
for(int v : G[u]) {
if(sccno[u] != sccno[v]) {
g[sccno[v]].push_back(sccno[u]);
++ind[sccno[u]];
}
}
}
_rep(u, 1, scccnt) {
sort(g[sccno[u]].begin(), g[sccno[u]].end());
g[sccno[u]].erase(unique(g[sccno[u]].begin(), g[sccno[u]].end()), g[sccno[u]].end());
}
int ma = -1;
_rep(i, 1, scccnt) if(ind[i] == 0) {
num[i] = dfs(i, i) - 1;
ma = max(ma, num[i]);
}
vector<int> ans;
printf("Case %d: %d\n", ttt, ma);
_for(i, n) if(num[sccno[i]] == ma) ans.push_back(i);
_for(i, ans.size()) printf("%d%s", ans[i], nl(i, ans.size()));
}
int main() {
//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
// debug = 1;
#endif
time_t beg, end;
if(debug) beg = clock();
int T = read();
_for(i, T) {
n = read(), m = read();
sol(i + 1);
}
if(debug) {
end = clock();
printf("time:%.2fs\n", 1.0 * (end - beg) / CLOCKS_PER_SEC);
}
return 0;
}
Fire Net(二分图匹配 or 网络流)
OJ: HDUOJ 1045
VJudge
题意
有一个 n × n n\times n n×n 的网格地图,其中有一些位置是墙,其余地方是空地。
地堡可以攻击上下左右四个方向上的其他地堡,不过墙可以隔绝地堡的攻击。
求出这个地图上最多可以放几个地堡,且它们不互相攻击。
题解
以这张图为例:
将地图以横向划分成若干个区域,每个区域是一个冲突域,冲突域内最多只能放一个地堡。
同时,纵向也可以划分成若干个区域。
这样我们就有了实体,当我们在 ( 2 , 1 ) (2,1) (2,1) 位置放置地堡时,就相当于选择了 3 3 3 号和 6 6 6 号区域,本着同一个冲突域内最多只有一个地堡的原则, 3 3 3 号和 6 6 6 号区域覆盖的网格内都不能再放置别的地堡。
就相当于把横向的区域划分作为左边点集,纵向的区域划分作为右边点集,相交的区域之间连边。在 ( 2 , 1 ) (2,1) (2,1) 位置放地堡就相当于选了 3 3 3 号和 6 6 6 号之间的边加入匹配。
然后就可以进行二分图最大匹配或者跑最大流了。
代码
网络流-最大流
// #pragma GCC optimize(2)
#include <bits/stdc++.h>
#define m_p make_pair
#define p_i pair<int, int>
#define _for(i, a) for(register int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(register int i = (a), lennn = (b); i <= lennn; ++i)
#define outval(a) cout << "Debuging...|" << #a << ": " << a << "\n"
#define mem(a, b) memset(a, b, sizeof(a))
#define mem0(a) memset(a, 0, sizeof(a))
#define fil(a, b) fill(a.begin(), a.end(), b);
#define scl(x) scanf("%lld", &x)
#define sc(x) scanf("%d", &x)
#define pf(x) printf("%d\n", x)
#define pfl(x) printf("%lld\n", x)
#define abs(x) ((x) > 0 ? (x) : -(x))
#define PI acos(-1)
#define lowbit(x) (x & (-x))
#define dg if(debug)
#define nl(i, n) (i == n - 1 ? "\n":" ")
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;
typedef long long LL;
// typedef __int128 LL;
typedef unsigned long long ULL;
const int maxn = 105;
const int maxm = 25;
const int maxp = 30;
const int inf = 0x3f3f3f3f;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1000000007;
const double eps = 1e-8;
const double e = 2.718281828;
int debug = 0;
inline int read() {
int x(0), f(1); char ch(getchar());
while (ch<'0' || ch>'9') {
if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0'&&ch <= '9') {
x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
int n;
char s[maxn][maxn];
int num[maxn][maxn];
struct edge {
int to, cap, rev; //用于表示边的结构体(终点,容量,反向边)
edge() {
}
edge(int t, int cap, int rev) :to(t), cap(cap), rev(rev) {
}
};
vector <edge> mf_G[maxm]; //图的邻接表表示
int mf_dis[maxm]; //顶点到源点的距离标号
int mf_cur[maxm]; //当前弧,在其之前的边已经没有用了
void mf_init(){
for(int i = 0; i < maxn; i++) mf_G[i].clear();
}
void mf_addedge(int from, int to, int cap) {
mf_G[from].push_back(edge(to, cap, mf_G[to].size()));
mf_G[to].push_back(edge(from, 0, mf_G[from].size() - 1));
}
void mf_bfs(int s) {
//通过mf_bfs计算从源点出发的距离标号
memset(mf_dis, -1, sizeof(mf_dis));
queue <int> q;
mf_dis[s] = 0;
q.push(s);
while (!q.empty()) {
int v = q.front(); q.pop();
for (int i = 0; i < mf_G[v].size(); i++) {
edge &e = mf_G[v][i];
if (e.cap > 0 && mf_dis[e.to] < 0) {
mf_dis[e.to] = mf_dis[v] + 1;
q.push(e.to);
}
}
}
}
int mf_dfs(int v, int t, int f) {
//通过mf_dfs寻找增广路
if (v == t) return f;
for (int &i = mf_cur[v]; i < mf_G[v].size(); i++) {
edge &e = mf_G[v][i];
if (e.cap > 0 && mf_dis[v] < mf_dis[e.to]) {
int d = mf_dfs(e.to, t, min(f, e.cap));
if (d > 0) {
e.cap -= d;
mf_G[e.to][e.rev].cap += d;
return d;
}
}
}
return 0;
}
int maxflow(int s, int t) {
int flow = 0;
for (;;) {
mf_bfs(s); //计算层次图
if (mf_dis[t] < 0) return flow; //找不到s-t路径
memset(mf_cur, 0, sizeof(mf_cur)); //初始化当前弧
int f;
while ((f = mf_dfs(s, t, inf)) > 0) {
//更新最大流
flow += f;
}
}
return flow;
}
void init() {
mf_init();
}
void sol() {
init();
_rep(i, 1, n) scanf("%s", s[i] + 1);
int cnt = 0;
_rep(i, 1, n) {
int pos = 1;
while(pos <= n) {
while(pos <= n && s[i][pos] != '.') ++pos;
if(pos > n) continue;
++cnt;
while(pos <= n && s[i][pos] == '.') num[i][pos++] = cnt;
}
}
int S = 0, tem = cnt;
_rep(i, 1, cnt) mf_addedge(S, i, 1);
_rep(i, 1, n) {
int pos = 1;
while(pos <= n) {
while(pos <= n && s[pos][i] != '.') ++pos;
if(pos > n) continue;
++cnt;
while(pos <= n && s[pos][i] == '.') mf_addedge(num[pos++][i], cnt, 1);
}
}
int T = cnt + 1;
_rep(i, tem + 1, cnt) mf_addedge(i, T, 1);
printf("%lld\n", maxflow(S, T));
}
int main() {
//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
debug = 1;
#endif
time_t beg, end;
if(debug) beg = clock();
while(n = read(), n) {
sol();
}
if(debug) {
end = clock();
printf("time:%.2fs\n", 1.0 * (end - beg) / CLOCKS_PER_SEC);
}
return 0;
}
二分图匹配-匈牙利算法
// #pragma GCC optimize(2)
#include <bits/stdc++.h>
#define m_p make_pair
#define p_i pair<int, int>
#define _for(i, a) for(register int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(register int i = (a), lennn = (b); i <= lennn; ++i)
#define outval(a) cout << "Debuging...|" << #a << ": " << a << "\n"
#define mem(a, b) memset(a, b, sizeof(a))
#define mem0(a) memset(a, 0, sizeof(a))
#define fil(a, b) fill(a.begin(), a.end(), b);
#define scl(x) scanf("%lld", &x)
#define sc(x) scanf("%d", &x)
#define pf(x) printf("%d\n", x)
#define pfl(x) printf("%lld\n", x)
#define abs(x) ((x) > 0 ? (x) : -(x))
#define PI acos(-1)
#define lowbit(x) (x & (-x))
#define dg if(debug)
#define nl(i, n) (i == n - 1 ? "\n":" ")
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;
typedef long long LL;
// typedef __int128 LL;
typedef unsigned long long ULL;
const int maxn = 105;
const int maxm = 25;
const int maxp = 30;
const int inf = 0x3f3f3f3f;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1000000007;
const double eps = 1e-8;
const double e = 2.718281828;
int debug = 0;
inline int read() {
int x(0), f(1); char ch(getchar());
while (ch<'0' || ch>'9') {
if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0'&&ch <= '9') {
x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
int n;
char s[maxn][maxn];
int num[maxn][maxn];
int vis[maxn], matching[maxn];
vector <int> G[maxm];
int dfs(int u) {
for(int &v : G[u]) {
if(!vis[v]) {
vis[v] = 1;
if(!matching[v] || dfs(matching[v])) {
matching[v] = u;
return 1;
}
}
}
return 0;
}
int hungarian(int cnt) {
int ans = 0;
_rep(i, 1, cnt) {
memset(vis, 0, sizeof(vis));
if(dfs(i)) ++ans;
}
return ans;
}
void init() {
for(int i = 0; i < maxn; i++) G[i].clear();
memset(matching, 0, sizeof(matching));
}
void sol() {
init();
_rep(i, 1, n) scanf("%s", s[i] + 1);
int cnt = 0;
_rep(i, 1, n) {
int pos = 1;
while(pos <= n) {
while(pos <= n && s[i][pos] != '.') ++pos;
if(pos > n) continue;
++cnt;
while(pos <= n && s[i][pos] == '.') num[i][pos++] = cnt;
}
}
int tem = cnt;
_rep(i, 1, n) {
int pos = 1;
while(pos <= n) {
while(pos <= n && s[pos][i] != '.') ++pos;
if(pos > n) continue;
++cnt;
while(pos <= n && s[pos][i] == '.') G[num[pos++][i]].push_back(cnt);
}
}
printf("%lld\n", hungarian(tem));
}
int main() {
//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
debug = 1;
#endif
time_t beg, end;
if(debug) beg = clock();
while(n = read(), n) {
sol();
}
if(debug) {
end = clock();
printf("time:%.2fs\n", 1.0 * (end - beg) / CLOCKS_PER_SEC);
}
return 0;
}
The Accomodation of Students(二分图匹配)
OJ:HDUOJ 2444
VJudge
题意
有若干对彼此认识的学生。你的任务是将学生分成两组,以便同一组中的任何两个学生都不认识。如果可以实现此目标,则将他们安排在双人间。注意只有彼此认识的学生才能住在同一房间。
计算安排到双人间的对数或者判断出无法分成两组。
题解
首先考虑将学生分成两组,可以采用 D F S DFS DFS 染色的方式进行,每隔一个人染成相同颜色,相同颜色的人分到一组。如果中间遇到认识的两个人颜色一样就说明无法完成分组。
染完色之后就可以跑匈牙利算法了。
代码
// #pragma GCC optimize(2)
#include <bits/stdc++.h>
#define m_p make_pair
#define p_i pair<int, int>
#define _for(i, a) for(int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(int i = (a), lennn = (b); i <= lennn; ++i)
#define outval(a) cout << "Debuging...|" << #a << ": " << a << "\n"
#define mem(a, b) memset(a, b, sizeof(a))
#define mem0(a) memset(a, 0, sizeof(a))
#define fil(a, b) fill(a.begin(), a.end(), b);
#define scl(x) scanf("%lld", &x)
#define sc(x) scanf("%d", &x)
#define pf(x) printf("%d\n", x)
#define pfl(x) printf("%lld\n", x)
#define abs(x) ((x) > 0 ? (x) : -(x))
#define PI acos(-1)
#define lowbit(x) (x & (-x))
#define dg if(debug)
#define nl(i, n) (i == n - 1 ? "\n":" ")
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;
typedef long long LL;
// typedef __int128 LL;
typedef unsigned long long ULL;
const int maxn = 205;
const int maxm = 1000005;
const int maxp = 30;
const int inf = 0x3f3f3f3f;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1000000007;
const double eps = 1e-8;
const double e = 2.718281828;
int debug = 0;
inline int read() {
int x(0), f(1); char ch(getchar());
while (ch<'0' || ch>'9') {
if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0'&&ch <= '9') {
x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
struct poi {
};
int vis[maxn], matching[maxn];
vector<int> G[maxn];
int n, m;
int col[maxn];
int dfs(int u) {
for(int &v : G[u]) {
if(!vis[v]) {
vis[v] = 1;
if(matching[v] == -1 || dfs(matching[v])) {
matching[v] = u;
return 1;
}
}
}
return 0;
}
int hungarian(int cnt) {
int ans = 0;
memset(matching, -1, sizeof(matching));
_rep(i, 1, cnt) {
memset(vis, 0, sizeof(vis));
if(col[i] == 1 && dfs(i)) ++ans;
}
return ans;
}
int getColor(int u, int f) {
col[u] = (f ? 1 : 2);
for(int &v : G[u]) {
if(!col[v] && getColor(v, f ^ 1) == 0 || col[v] == (f ? 1 : 2)) return 0;
}
return 1;
}
void init() {
mem0(col);
_rep(i, 1, n) G[i].clear();
}
int sol() {
init();
_for(i, m) {
int u = read(), v = read();
G[u].push_back(v);
G[v].push_back(u);
}
_rep(i, 1, n) if(!col[i] && getColor(i, 0) == 0) return 0;
printf("%d\n", hungarian(n));
return 1;
}
int main() {
//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
debug = 1;
#endif
time_t beg, end;
if(debug) beg = clock();
while(cin>>n>>m) {
if(!sol()) printf("No\n");
}
if(debug) {
end = clock();
printf("time:%.2fs\n", 1.0 * (end - beg) / CLOCKS_PER_SEC);
}
return 0;
}
Courses(裸二分图匹配)
OJ:hduoj 1083
vjudge
题意
有 n n n 门课程, m m m 个学生,每个学生喜欢若干门课。你可以从一个学生喜欢的课程里选出一门课,让该学生担任课代表。
问能不能找出 n n n 个不同的学生担任 n n n 门课的课代表。
题解
直接建图跑匈牙利算法。
代码
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define m_p make_pair
#define p_i pair<int, int>
#define _for(i, a) for(int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(int i = (a), lennn = (b); i <= lennn; ++i)
#define outval(a) cout << "Debuging...|" << #a << ": " << a << "\n"
#define mem(a, b) memset(a, b, sizeof(a))
#define mem0(a) memset(a, 0, sizeof(a))
#define fil(a, b) fill(a.begin(), a.end(), b);
#define scl(x) scanf("%lld", &x)
#define sc(x) scanf("%d", &x)
#define pf(x) printf("%d\n", x)
#define pfl(x) printf("%lld\n", x)
#define abs(x) ((x) > 0 ? (x) : -(x))
#define PI acos(-1)
#define lowbit(x) (x & (-x))
#define dg if(debug)
#define nl(i, n) (i == n - 1 ? "\n":" ")
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;
typedef long long LL;
// typedef __int128 LL;
typedef unsigned long long ULL;
const int maxn = 405;
const int maxm = 1000005;
const int maxp = 30;
const int inf = 0x3f3f3f3f;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1000000007;
const double eps = 1e-8;
const double e = 2.718281828;
int debug = 0;
inline int read() {
int x(0), f(1); char ch(getchar());
while (ch<'0' || ch>'9') {
if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0'&&ch <= '9') {
x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
struct poi {
};
int n, m;
vector<int> G[maxn];
void init() {
_rep(i, 1, n + m) G[i].clear();
}
/*
@param cnt: 二分图左边点集的编号最大值
int hungarian(int cnt)
*/
int vis[maxn], matching[maxn];
int dfs(int u) {
for(int &v : G[u]) {
if(!vis[v]) {
vis[v] = 1;
if(matching[v] == -1 || dfs(matching[v])) {
matching[v] = u;
return 1;
}
}
}
return 0;
}
int hungarian(int cnt) {
int ans = 0;
memset(matching, -1, sizeof(matching));
_rep(i, 1, cnt) {
memset(vis, 0, sizeof(vis));
if(dfs(i)) ++ans;
}
return ans;
}
void sol() {
init();
_rep(i, 1, n) {
int num = read();
_for(j, num) G[i].push_back(read() + n);
}
printf("%s\n", hungarian(n) == n ? "YES":"NO");
}
int main() {
//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
debug = 1;
#endif
time_t beg, end;
if(debug) beg = clock();
int T = read();
_for(i, T) {
n = read(), m = read();
sol();
}
if(debug) {
end = clock();
printf("time:%.2fs\n", 1.0 * (end - beg) / CLOCKS_PER_SEC);
}
return 0;
}