@description - translation@

给定一个 n*n 的棋盘,并划定一些不能放棋子的矩形区域。

现在要在棋盘上放最多的车(读作 ju),使得这些车两两之间不会攻击。

input

第一行整数 n ——棋盘边长(1 <= n <= 10000)。

第二行整数 q ——划定的矩形个数(0 <= q <= 10000)。

接下来 q 行,每一行都是 x1, y1, x2, y2(1 <= x1 <= x2 <= n, 1 <= y1 <= y2 <= n),描述矩阵的左下角与右上角。

保证矩形两两不会相交

output

输出最多的车的个数。

sample input

5

5

1 1 2 1

1 3 1 5

4 1 5 5

2 5 2 5

3 2 3 5

sample output

3

sapmle explain

如图。

@solution@

一道网络流题。

一道建模极其简单,建图极其恶心的网络流题。

@part - 1@

考虑建模。棋盘是一个很经典的二分图,可以是黑白染色建模,也可以是行列建模。考虑到车的攻击方式是同行同列攻击,所以我们选择后者。

假如某一个格子(i, j)没有被划定不能放车,我们就第 i 行与第 j 列连边。再跑一个最大匹配就可以求出最多放置多少车了。

然而显然是会 TLE 的,而且还会 T 的很惨,惨兮兮。

@part - 2@

优化建图的话,因为划定的是规则的矩形,所以我们考虑用线段树来优化建图。

如图是一个内部完全没有限制的矩形,我们用行、列两棵线段树将它的两个横竖的边界拆成log n条线段树上的线段:

然后横着的和竖着的两两连边,连 log^2 n 条边【图片略鬼畜】:



这样就处理完了一个没有限制的矩形。

最后:两棵线段树的底层端点,一棵连 S,一棵连 T,容量都为 1。线段树内部的父子连容量为 inf 的边。

@part - 3@

然而问题又来了:我们给定的是限制的矩形区域。

所以,我们必须把原棋盘切割成若干个内部没有限制的矩形,才能运用上面所提到的优化。

怎么切?下面是一个比较显然的思路:



即对于每一个矩形,它的上下左右边界往两边割。

然而,如果下面这个图……



直接卡成 O(n^2)。

我们发现上面的那种切割方法,有很多小矩形是可以合并成大矩形。所以我们优化一下切割方法:



即上下边界往两边切,遇到其他矩形的边界或棋盘的边界,则停下来。

这样切,可以证明最多只会分出 4*n 个矩形。

怎么证明呢?【感性理解】每一个矩形的上下边界向左右各引一条线,一共 4 条线,每条线可以把一个矩形切割成两个矩形,相当于多增加了 4 个矩形。所以最多 4n 个矩形。

@part - 4@

OK 现在来看看怎么实现切割。

我们用扫描线算法,从左往右扫描。对于每一行,维护扫描线左边距离扫描线最近的矩形边界。如图,我们维护的就是左边的那弯弯曲曲的曲线:



假如遇到矩形左边界,我们就从这个矩形的上边界开始往下暴力遍历(对你没听错就是暴力遍历,这样的确是 O(n^2) 的,但是其实 n 不大,对吧)。假如遇到不平坦的地方(对应到代码中就是相邻两行维护的东西不相等),则说明又产生了新的矩形。我们就进行线段树建图。

假如遇到矩形右边界,更新 “扫描线左边距离扫描线最近的矩形边界”。

注意,这个算法是基于矩阵不相交的前提的。

@accepted code@

口胡完毕。至于代码量,我不清楚我不知道,大家自己慢慢调,总会调出来的 qwq。

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN = 10000;
const int MAXM = 100000;
const int MAXK = 2000000;
const int INF = (1<<30);
struct FlowGraph{
struct edge{
int to, cap, flow;
edge *nxt, *rev;
}edges[2*MAXK + 5], *adj[MAXM + 5], *ecnt=&edges[0];
int S, T, d[MAXM + 5], vd[MAXM + 5];
void addedge(int u, int v, int c) {
edge *p = (++ecnt);
p->to = v, p->cap = c, p->flow = 0;
p->nxt = adj[u], adj[u] = p;
edge *q = (++ecnt);
q->to = u, q->cap = 0, q->flow = 0;
q->nxt = adj[v], adj[v] = q;
p->rev = q, q->rev = p;
}
int aug(int x, int tot) {
if( x == T ) return tot;
int mind = T+1, sum = 0;
for(edge *p=adj[x];p!=NULL;p=p->nxt) {
if( p->cap > p->flow ) {
if( d[p->to] + 1 == d[x] ) {
int del = aug(p->to, min(tot-sum, p->cap-p->flow));
p->flow += del, p->rev->flow -= del, sum += del;
if( d[S] == T+1 ) return sum;
if( sum == tot ) return sum;
}
mind = min(mind, d[p->to]);
}
}
if( sum == 0 ) {
vd[d[x]]--;
if( vd[d[x]] == 0 )
d[S] = T+1;
d[x] = mind + 1;
vd[d[x]]++;
}
return sum;
}
int max_flow() {
int flow = 0;
while( d[S] < T+1 )
flow += aug(S, INF);
return flow;
}
}G;
int cnt = 0;
struct SegmentTree{
int le, ri, num;
}t[2][4*MAXN + 5];
vector<int>v[2];
void build_segtree(int x, int l, int r, int n) {
t[n][x].le = l, t[n][x].ri = r, t[n][x].num = (++cnt);
if( l == r ) return ;
int mid = (l + r) >> 1;
build_segtree(x<<1, l, mid, n);
build_segtree(x<<1|1, mid+1, r, n);
}
void build_edge_segtree(int x, int n) {
if( t[n][x].le == t[n][x].ri ) {
if( n == 0 ) G.addedge(G.S, t[n][x].num, 1);
else G.addedge(t[n][x].num, G.T, 1);
}
else {
if( n == 0 ) {
G.addedge(t[n][x<<1].num, t[n][x].num, INF);
G.addedge(t[n][x<<1|1].num, t[n][x].num, INF);
}
else {
G.addedge(t[n][x].num, t[n][x<<1].num, INF);
G.addedge(t[n][x].num, t[n][x<<1|1].num, INF);
}
build_edge_segtree(x<<1, n);
build_edge_segtree(x<<1|1, n);
}
}
void get_segment(int x, int l, int r, int n) {
if( l <= t[n][x].le && t[n][x].ri <= r ) {
v[n].push_back(t[n][x].num);
return ;
}
if( l > t[n][x].ri || r < t[n][x].le )
return ;
get_segment(x<<1, l, r, n);
get_segment(x<<1|1, l, r, n);
}
void build_edge_area(int x1, int y1, int x2, int y2) {
if( x1 > x2 || y1 > y2 ) return ;
v[0].clear(), v[1].clear();
get_segment(1, x1, x2, 0);
get_segment(1, y1, y2, 1);
for(int i=0;i<v[0].size();i++)
for(int j=0;j<v[1].size();j++)
G.addedge(v[0][i], v[1][j], INF);
}
struct node{
int le, ri;
node(int _l=0, int _r=0):le(_l), ri(_r){}
};
vector<node>vec[MAXN + 5][2];
int left[MAXN + 5];
int main() {
int n, q;
scanf("%d%d", &n, &q);
build_segtree(1, 1, n, 0); build_segtree(1, 1, n, 1); G.T = cnt + 1;
build_edge_segtree(1, 0); build_edge_segtree(1, 1);
for(int i=1;i<=q;i++) {
int x1, y1, x2, y2;
scanf("%d%d%d%d", &y1, &x1, &y2, &x2);
vec[x1][0].push_back(node(y1, y2));
vec[x2][1].push_back(node(y1, y2));
}
vec[n+1][0].push_back(node(1, n));
for(int i=1;i<=n+1;i++) {
for(int j=0;j<vec[i][0].size();j++) {
int lst = vec[i][0][j].le;
for(int k=vec[i][0][j].le+1;k<=vec[i][0][j].ri;k++)
if( left[k] != left[k-1] )
build_edge_area(left[k-1]+1, lst, i-1, k-1), lst = k;
build_edge_area(left[vec[i][0][j].ri]+1, lst, i-1, vec[i][0][j].ri);
}
for(int j=0;j<vec[i][1].size();j++)
for(int k=vec[i][1][j].le;k<=vec[i][1][j].ri;k++)
left[k] = i;
}
//注意我们必须要先处理矩形的左边再处理矩形的右边,不然遇到宽度为 1 的矩形就直接 GG 了。
int ans = G.max_flow();
printf("%d\n", ans);
}

@details@

一开始我写的从上往下的扫描线,结果发现 TLE 在 144th 组数据上。

气的我一怒之下把扫描线改成从左往右的。

然后……它就 AC 了???

听说机房里的另外一个人遇到了一样的情况,然后他把 isap 换成了 dinic 才过的。

好玄妙啊,果然是网络流。

@codeforces - 793G@ Oleg and chess的更多相关文章

  1. 【Codeforces】【网络流】【线段树】【扫描线】Oleg and chess (CodeForces - 793G)

    题意: 给定一个n*n的矩阵,一个格子上可以放一个车.其中有q个子矩阵,且q个子矩阵互不相交或者是重叠(但边界可以衔接).这q个子矩阵所覆盖的地方都是不能够放车的.车可以越过子矩阵覆盖的地方进行攻击( ...

  2. Solution -「CF 793G」Oleg and Chess

    \(\mathcal{Description}\)   Link.   给一个 \(n\times n\) 的棋盘,其中 \(q\) 个互不重叠的子矩阵被禁止放棋.问最多能放多少个互不能攻击的车.   ...

  3. Codeforces 734D. Anton and Chess(模拟)

    Anton likes to play chess. Also, he likes to do programming. That is why he decided to write the pro ...

  4. Codeforces 1173B Nauuo and Chess

    题目链接:http://codeforces.com/problemset/problem/1173/B 思路参考:https://www.cnblogs.com/blowhail/p/1099123 ...

  5. 【Codeforces】【网络流】【树链剖分】【线段树】ALT (CodeForces - 786E)

    题意 现在有m个人,每一个人都特别喜欢狗.另外还有一棵n个节点的树. 现在每个人都想要从树上的某个节点走到另外一个节点,且满足要么这个人自带一条狗m,要么他经过的所有边h上都有一条狗. 2<=n ...

  6. Gym100947E || codeforces 559c 组合数取模

    E - Qwerty78 Trip Time Limit:2000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u S ...

  7. codeforces613E

    Puzzle Lover CodeForces - 613E Oleg Petrov loves crossword puzzles and every Thursday he buys his fa ...

  8. 【Codeforces 738A】Interview with Oleg

    http://codeforces.com/contest/738/problem/A Polycarp has interviewed Oleg and has written the interv ...

  9. Codeforces Round #379 (Div. 2) D. Anton and Chess 水题

    D. Anton and Chess 题目连接: http://codeforces.com/contest/734/problem/D Description Anton likes to play ...

随机推荐

  1. TZ_06_SpringMVC的入门程序

    SpringMVC的入门程序 1. 创建WEB工程,引入开发的jar包 1. 具体的坐标如下 2. 配置核心的控制器(配置DispatcherServlet) 1. 在web.xml配置文件中核心控制 ...

  2. 不同尺寸设计图 rem 断点数据记录

    320px宽的设计图 @media screen and (min-width: 320px) { html { font-size: 100px; } } @media screen and (mi ...

  3. [翻译] MaxMind DB 文件格式规范

    MaxMind DB 文件格式规范来源:http://maxmind.github.io/MaxMind-DB/翻译:御风(TX:965551582)2017-03-23 -------------- ...

  4. 洛谷P1979 [NOIP2013提高组Day2T3]华容道

    P1979 华容道 题目描述 [问题描述] 小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次.于是,他想到用编程来完成华容道:给定一种局面, 华容道是否根本就无法完成,如果能完成, 最少 ...

  5. 洛谷P2327 [SCOI2005]扫雷 [2017年5月计划 清北学堂51精英班Day1]

    P2327 [SCOI2005]扫雷 题目描述 输入输出格式 输入格式: 第一行为N,第二行有N个数,依次为第二列的格子中的数.(1<= N <= 10000) 输出格式: 一个数,即第一 ...

  6. js实现放大镜特效的实现方法

    <!doctype html><html lang="en"><head> <meta charset="UTF-8" ...

  7. 【GDOI2017 day1】取石子游戏 线段树+区间合并

    题面 如果给你一棵有根树,树根为 1,并且树的每个结点上有一个权值,现在我想知道每个点,除它所在子树以外的结点权值集合的 mex,怎么做呢? 在这里,mex 是定义在集合上的函数,mex(S) 表示 ...

  8. iView3.x Anchor(锚点)组件 导航锚点

    iView3.x Anchor(锚点)组件 导航锚点 iview 3.x框架中新添了一个Anchor(锚点组件),用这个组件去做页面的分类导航正好合适,但是苦于官方文档太过抽象研究了一整天,才勉强可以 ...

  9. 【如花美眷】初探weex

    我想我更喜欢weex的原因,应该是weex可以直接运行在浏览器中,而不是像react-native需要运行在模拟设备中. 我想这个原因足以让我使用vue而不是RN. 初探就是稍微运行一下,来看步骤 可 ...

  10. JQuery--动画队列以及清空队列.stop()方法

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...