祭-第一题网络流
- 给定一个
n
×
n
n\times n
n×n大小的棋盘, 其中有若干矩形区域放满了黑色的棋子
- 每消除一块儿
n
×
m
n\times m
n×m的矩形的花费是
m
i
n
(
n
,
m
)
min(n, m)
min(n,m)
- 现问将棋盘中所有的棋子全变白的最小花费
前置技能 Dinic, 二分图
tutorial: 平生没有做过网络流, 这是第一次, 建议和我一样的同学可以先学匈牙利, 然后学EK, 然后帮助理解Dinic, (好像匈牙利没啥用算了还是先学着吧)
来看本题, 如果要消掉一个矩形, 要么横着消, 要么竖着消, 也就是说有两种花费, 然后也许你就会联想到如果矩形是二分图里的边的话, 那消掉一个矩形不就该增广路的容量吗, 然后整个问题不就成了在二分图中最大流嘛, 上Dinic呗, 但是转念一想, n的范围1e9, 这个加边那得多大, 于是想到肯定要离散化, 那么问题来了, 咋个离散化
想啊, n要么是row, 要么是column, 所以肯定是要离散这两维的, 我们先放一放, 来看二分图, 建立源点和汇点, 源点可以连是要连矩形的横着消的花费, 汇点可以连竖着消的花费, 那中间咋整呢, 显然, 如果该横纵坐标对应的点是黑棋的话, 那肯定要连, 那权肯定不能影响该条路的容量啊, 所以我们给他设个无限大的边权. 接下来就是一些小细节了
这里我感觉右端点++的原因, 当矩形被分成很多段的时候, 为了保证每个部分不重不漏地被算一次, 我们可以使得右端点++, 这样既不会因为某个从起点到另一个起点的右端点被反复计算, 也可以保证从起点到终点的范围被完整的覆盖一次
upd:这样做把区间由闭区间转化为左开右闭区间,离散化比较方便。举个例子,假设有个区间是[1, 3], 一个区间是[4, 6],在题目中这两个区间是挨着的,但是一个左端点是3,一个右端点是4,这样会判断为不可合并。把左端点减1之后,区间转化为了(0, 3]和(3, 6],这样就可以判断为可合并了。
#include <bits/stdc++.h>
using namespace std;
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define _rev(i, a, b) for (int i = (a); i >= (b); --i)
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rof(i, a, b) for (int i = (a); i > (b); --i)
#define oo 0x3f3f3f3f
#define ll long long
#define db double
#define eps 1e-8
#define bin(x) cout << bitset<10>(x) << endl;
#define what_is(x) cerr << #x << " is " << x << "s" << endl;
#define met(a, b) memset(a, b, sizeof(a))
#define all(x) x.begin(), x.end()
#define pii pair<int, int>
const int maxn = 300;
int s, t, head[maxn], cnt = 1, d[maxn], n, m;
struct node{
int nxt, to, cost;
}way[maxn *maxn * 2 * 6];
int nxt(){
int ret;
scanf("%d", &ret);
return ret;
}
struct rec{
int c1, r1, c2, r2;
}A;
vector<rec> rec_list;
struct discrete{
vector<int> data;
int tot;
void insert(int x){tot++, data.emplace_back(x); }
void pre_work(){
sort(all(data));
tot = unique(all(data)) - data.begin();
}
int get_val(int x){
return lower_bound(data.begin(), data.begin() + tot, x) - data.begin() + 1;
}
}row, col;
void addedge(int from, int to, int cost){
way[++cnt].cost = cost;
way[cnt].to = to;
way[cnt].nxt = head[from];
head[from] = cnt;
}
bool bfs(){
queue<int> q;
q.push(s);
met(d, 0);
d[s] = 1;
while(!q.empty()){
int cur = q.front();
q.pop();
for(int i = head[cur];i;i = way[i].nxt){
int to = way[i].to, cost = way[i].cost;
if(cost && !d[to]){
d[to] = d[cur] + 1;
if(t == to)return 1;
q.push(to);
}
}
}
return 0;
}
int dfs(int cur, int flow){
if(cur == t)return flow;
int rest = flow, k;
for(int i = head[cur]; i && rest; i = way[i].nxt){
int to = way[i].to, cost = way[i].cost;
if(cost && d[to] == d[cur] + 1){
int k = dfs(to, min(rest, cost));
if(!k)d[to] == 0;
way[i].cost -= k;
way[i^1].cost += k;
rest -= k;
}
}
return flow - rest;
}
void Dinic(){
int ans = 0;
while(bfs()) ans += dfs(s, oo);
cout << ans << endl;
}
signed main(){
n = nxt(), m = nxt();
_rep(i, 1, m){
A.r1 = nxt(), A.c1 = nxt(), A.r2 = nxt(), A.c2 = nxt();
A.r2++, A.c2++;
rec_list.push_back(A);
row.insert(A.r1), row.insert(A.r2), col.insert(A.c1), col.insert(A.c2);
}
row.pre_work(), col.pre_work();
s = 0, t = row.tot + col.tot + 3;
_for(i, 1, row.tot){
addedge(s, i, row.data[i] - row.data[i-1]), addedge(i, s, 0);
}
_for(i, 1, col.tot){
addedge(i + row.tot, t, col.data[i] - col.data[i-1]), addedge(t, i + row.tot, 0);
}
_for(k, 0, m){
_for(i, col.get_val(rec_list[k].c1), col.get_val(rec_list[k].c2)){
_for(j, row.get_val(rec_list[k].r1), row.get_val(rec_list[k].r2)){
addedge(j, i + row.tot, oo), addedge(i + row.tot, j, 0);
}
}
}
Dinic();
}