棋盘游戏(二分图匹配)
OJ:hduoj 1281
VJudge
题意
对一个 n × m n\times m n×m 的棋盘,可以在格子里放一些象棋里面的“车”,并且使得他们不互相攻击。
在保证尽量多的“车”的前提下,棋盘里有些格子是可以避开的,也就是说,不在这些格子上放车,也可以保证尽量多的“车”被放下。但是某些格子若不放子,就无法保证放尽量多的“车”,这样的格子被称做重要点。算出有多少个这样的重要点。
题解
以每一行作为左点集,每一列作为右点集,每一个可放点连一条从行到列的边,建立二分图。
代码
// #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 = 105;
const int maxm = 20005;
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, k;
vector<int> G[maxm], ok[maxm];
int a[maxn][maxn], num[maxn][maxn];
int cnt;
/*
@param cnt: 二分图左边点集的编号最大值
int hungarian(int cnt)
*/
int vis[maxm], matching[maxm];
int dfs(int u) {
_for(i, G[u].size()) {
int v = G[u][i];
if(!vis[v] && ok[u][i]) {
vis[v] = 1;
if(matching[v] == -1 || dfs(matching[v])) {
matching[v] = u;
return 1;
}
}
}
return 0;
}
int hungarian(int cnt, int tot) {
int ans = 0;
_rep(i, 1, tot) matching[i] = -1;
_rep(i, 1, cnt) {
_rep(i, 1, tot) vis[i] = 0;
if(dfs(i)) ++ans;
}
return ans;
}
void init() {
_rep(i, 1, n) _rep(j, 1, m) a[i][j] = 0, num[i][j] = 0;
_rep(i, 1, n * m) G[i].clear(), ok[i].clear();
cnt = 0;
}
void sol(int ttt) {
init();
_for(i, k) {
int x = read(), y = read();
G[x].push_back(y + n);
ok[x].push_back(1);
}
int ans = 0;
int val = hungarian(n, n + m);
_rep(i, 1, n) {
_for(j, G[i].size()) {
ok[i][j] = 0;
if(hungarian(n, n + m) < val) ++ans;
ok[i][j] = 1;
}
}
printf("Board %d have %d important blanks for %d chessmen.\n", ttt, ans, val);
}
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 ttt = 1;
while(cin>>n>>m>>k) {
sol(ttt++);
}
if(debug) {
end = clock();
printf("time:%.2fs\n", 1.0 * (end - beg) / CLOCKS_PER_SEC);
}
return 0;
}
Swap(二分图匹配)
OJ:hduoj 2819
VJudge
题意
给你一个 n × n n\times n n×n 的矩阵,判断是否能通过执行若干次交换两行使得矩阵的对角线上的元素都为 1 1 1。
如果可以实现,输出一种交换方案。
题解
以行号为左点集,列号为右点集,元素为 1 1 1 就从行到列连一条边,建立二分图。
判断最大匹配数是否等于 n n n。
之后我们通过交换行操作构造方案,然后我们只需要关心 m a t c h i n g matching matching 数组中的行号对应的元素。假设行号对应的元素为 a a a 数组,那么此时 a a a 数组就是一个 [ 1 − n ] [1-n] [1−n] 的排列。
问题就转化成了通过若干次交换 a a a 中的两个数,使得 a a a 数组按升序排序。
以下就是一个小思维,不再赘述。
代码
// #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 n;
int a[maxn], b[maxn];
vector<int> G[maxn];
void init() {
_rep(i, 1, n + n) 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;
matching[u] = v;
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;
}
int sol() {
init();
_rep(i, 1, n) _rep(j, 1, n) if(read() == 1) G[i].push_back(j + n);
if(hungarian(n) != n) return -1;
_rep(i, 1, n) a[i] = matching[i] - n, b[matching[i] - n] = i;
vector< pair<int, int> > ans;
_rep(i, 1, n) {
if(a[i] != i) {
ans.push_back(m_p(i, b[i]));
b[a[i]] = b[i];
a[b[i]] = a[i];
a[i] = i;
b[i] = i;
}
}
printf("%d\n", ans.size());
_for(i, ans.size()) printf("R %d %d\n", ans[i].first, ans[i].second);
return 0;
}
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) {
if(sol() == -1) printf("-1\n");
}
if(debug) {
end = clock();
printf("time:%.2fs\n", 1.0 * (end - beg) / CLOCKS_PER_SEC);
}
return 0;
}
Rain on your Parade(二分图匹配)
题意
您正在举行派对。但是几分钟内就会下大雨。您的客人不希望自己被弄湿。但是您有一些雨伞,可以保护一些客人。
给你每个客人的 x , y x,y x,y 坐标和速度 v v v,雨伞的 x ′ , y ′ x',y' x′,y′ 坐标以及开始下雨的时间 t t t,请找出最多有多少客人可以拿到雨伞。并且任何两个客人不共用一把雨伞。
题解
O ( n 2 ) O(n^2) O(n2) 时间复杂度内计算出每个客人能拿到的伞,分别连边,之后跑二分图匹配即可。
这道题对时间和空间的要求都很高,我试过用 d i n i c dinic dinic 算法跑最大流,会超空间。所以这道题是个HK算法的模板题。
代码
#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 = 3005;
// const int maxm = 18000005;
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 x, y, v;
poi(){
}
poi(int x, int y, int v):x(x), y(y), v(v) {
}
}a[maxn];
int t, n, m;
/*
二分图匹配-HK算法
初始化:清空邻接表
参数:
Maxmatch:返回最大匹配数
ln:左边点集的大小
注意:点的编号从0开始
*/
vector<int> G[maxn];
int Mx[maxn], My[maxn]; //Mx存与左侧集合相连接的右边的点,My存与右边的
int dx[maxn], dy[maxn]; //层次编号
int dis; //标识是否找到增广路及找到的增广路的深度
int used[maxn]; //dfs中标识是否走过
bool bfs(int ln) {
queue<int> q;
dis = inf;
memset(dx, -1, sizeof(dx));
memset(dy, -1, sizeof(dy));
_for(i, ln) {
if (Mx[i] == -1) {
//如果没有匹配过
q.push(i);
dx[i] = 0;
}
}
while (q.size()) {
int u = q.front();
q.pop();
if (dx[u] > dis) break; //如果已经找到增广路
int sz = G[u].size();
_for(i, sz) {
int v = G[u][i];
if (dy[v] == -1) {
dy[v] = dx[u] + 1;
if (My[v] == -1) dis = dy[v];
else {
dx[My[v]] = dy[v] + 1;
q.push(My[v]);
}
}
}
}
return dis != inf;
}//找出增广路并保存路径
bool dfs(int u) {
int sz = G[u].size();
_for(i, sz) {
int v = G[u][i];
if (!used[v] && dy[v] == dx[u] + 1) {
used[v] = 1;
if (My[v] != -1 && dy[v] == dis) continue;
if (My[v] == -1 || dfs(My[v])) {
My[v] = u;
Mx[u] = v;
return 1;
}
}
}
return 0;
}
int Maxmatch(int ln) {
int ans = 0;
memset(Mx, -1, sizeof(Mx));
memset(My, -1, sizeof(My));
while (bfs(ln)) {
memset(used, 0, sizeof(used));
_for(i, ln) {
if (Mx[i] == -1 && dfs(i)) {
ans++;
}
}
}
return ans;
}
void init() {
_for(i, m) G[i].clear();
}
void sol(int tt) {
_for(i, n) {
int x = read(), y = read(), v = read();
a[i] = poi(x, y, v);
}
m = read();
init();
_for(i, m) {
int x = read(), y = read();
_for(j, n) if((x - a[j].x)*(x - a[j].x) + (y - a[j].y)*(y - a[j].y) <= t*t*a[j].v*a[j].v) {
G[i].push_back(j);
}
}
printf("Scenario #%d:\n", tt);
printf("%d\n\n", Maxmatch(m));
}
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();
_rep(i, 1, T) {
t = read(), n = read();
sol(i);
}
if(debug) {
end = clock();
printf("time:%.2fs\n", 1.0 * (end - beg) / CLOCKS_PER_SEC);
}
return 0;
}
Oil Skimming(二分图匹配)
OJ: hduoj 4185
VJudge
题意
有一个 n × n n\times n n×n 的网格,在标记为#
的区域放尽可能多的 1 × 2 1\times 2 1×2 或 2 × 1 2\times 1 2×1 的矩形,矩形之间不能有重叠。求出矩形的最大数量。
题解
有一个很巧妙地策略,对每一对相邻的#
之间连边,其中边的左端是左点集中的点,右端是右点集中的点。
然后直接求最大匹配。由于两个格子组成一个矩形,而我们每个点都向另一个点连一条边,所以这时的匹配相当于在同一个矩形位置覆盖了两个矩形。只需要将最后的结果除以2即可。
代码
#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 = 605;
const int maxm = 3605;
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;
int num[maxn][maxn];
char s[maxn][maxn];
int cnt;
/*
二分图匹配-HK算法
初始化:清空邻接表
参数:
Maxmatch:返回最大匹配数
ln:左边点集的大小
注意:点的编号从0开始
*/
vector<int> G[maxn * maxn];
int Mx[maxn * maxn], My[maxn * maxn]; //Mx存与左侧集合相连接的右边的点,My存与右边的
int dx[maxn * maxn], dy[maxn * maxn]; //层次编号
int dis; //标识是否找到增广路及找到的增广路的深度
int used[maxn * maxn]; //dfs中标识是否走过
bool bfs(int ln) {
queue<int> q;
dis = inf;
memset(dx, -1, sizeof(dx));
memset(dy, -1, sizeof(dy));
_for(i, ln) {
if (Mx[i] == -1) {
//如果没有匹配过
q.push(i);
dx[i] = 0;
}
}
while (q.size()) {
int u = q.front();
q.pop();
if (dx[u] > dis) break; //如果已经找到增广路
int sz = G[u].size();
_for(i, sz) {
int v = G[u][i];
if (dy[v] == -1) {
dy[v] = dx[u] + 1;
if (My[v] == -1) dis = dy[v];
else {
dx[My[v]] = dy[v] + 1;
q.push(My[v]);
}
}
}
}
return dis != inf;
}//找出增广路并保存路径
bool dfs(int u) {
int sz = G[u].size();
_for(i, sz) {
int v = G[u][i];
if (!used[v] && dy[v] == dx[u] + 1) {
used[v] = 1;
if (My[v] != -1 && dy[v] == dis) continue;
if (My[v] == -1 || dfs(My[v])) {
My[v] = u;
Mx[u] = v;
return 1;
}
}
}
return 0;
}
int Maxmatch(int ln) {
int ans = 0;
memset(Mx, -1, sizeof(Mx));
memset(My, -1, sizeof(My));
while (bfs(ln)) {
memset(used, 0, sizeof(used));
_for(i, ln) {
if (Mx[i] == -1 && dfs(i)) {
ans++;
}
}
}
return ans;
}
void init() {
_for(i, cnt) G[i].clear();
cnt = 0;
_rep(i, 1, n) _rep(j, 1, n) num[i][j] = 0;
}
void sol() {
init();
_rep(i, 1, n) scanf("%s", s[i] + 1);
_rep(i, 1, n) _rep(j, 1, n) if(s[i][j] == '#') num[i][j] = cnt++;
_rep(i, 1, n) _rep(j, 1, n) {
if(s[i][j] == '#') {
if(s[i - 1][j] == '#') G[num[i][j]].push_back(num[i - 1][j]);
if(s[i + 1][j] == '#') G[num[i][j]].push_back(num[i + 1][j]);
if(s[i][j + 1] == '#') G[num[i][j]].push_back(num[i][j + 1]);
if(s[i][j - 1] == '#') G[num[i][j]].push_back(num[i][j - 1]);
}
}
printf("%d\n", Maxmatch(cnt) / 2);
}
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();
printf("Case %d: ", i + 1);
sol();
}
if(debug) {
end = clock();
printf("time:%.2fs\n", 1.0 * (end - beg) / CLOCKS_PER_SEC);
}
return 0;
}