CFround(576)Div.1-E - Rectangle Painting 2

5 篇文章 0 订阅
4 篇文章 0 订阅

祭-第一题网络流

- 给定一个 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)

- 现问将棋盘中所有的棋子全变白的最小花费

>> face <<

前置技能 Dinic, 二分图

tutorial: 平生没有做过网络流, 这是第一次, 建议和我一样的同学可以先学匈牙利, 然后学EK, 然后帮助理解Dinic, (好像匈牙利没啥用算了还是先学着吧)

Klpwan.jpg

来看本题, 如果要消掉一个矩形, 要么横着消, 要么竖着消, 也就是说有两种花费, 然后也许你就会联想到如果矩形是二分图里的边的话, 那消掉一个矩形不就该增广路的容量吗, 然后整个问题不就成了在二分图中最大流嘛, 上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();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值