目录

  • @description - translation@
  • @solution@
    • @part - 1@
    • @part - 2@
    • @part - 3@
    • @part - 4@
  • @accepted code@
  • @details@


@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 才过的。

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

转载于:https://www.cnblogs.com/Tiw-Air-OAO/p/10176234.html

@codeforces - 793G@ Oleg and chess相关推荐

  1. codeforces B. Boboniu Plays Chess

    题目 题目: 我们有一个起始点,现在你要从这个点出发,然后访问所有的点并且只能访问一次,每一次走可以像车一样,到达一行或一列的任何一个点(经过不算到达),现在你需要打印出路径. 思路: 因为可以随意到 ...

  2. 网络流的各种加边优化

    1.最显然的前缀优化. 2.线段树优化[CF 793G]Oleg and Chess dalao博客 3.倍增优化 CF786E ALT 最小割+倍增lca 这个dalao讲的好 AC Code:(图 ...

  3. Team Silver_Bullet 训练记录

    Team members Vec Grunt henryrabbit To do list team:起队名 每周组队训练 补题 Vec: Grunt:切题,切题,切更多的题!做好切所有类型题的觉悟! ...

  4. Codeforces Round #732 (Div. 2) D. AquaMoon and Chess 组合数学 + 找规律

    传送门 文章目录 题意: 思路: 题意: 给你一个010101串,当且仅当某个111的某一边i+1,i−1i+1,i-1i+1,i−1有111,这个111可以跟i+2,i−2i+2,i-2i+2,i− ...

  5. Codeforces Round #313 (Div. 1) C. Gerald and Giant Chess DP

    C. Gerald and Giant Chess Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest ...

  6. CodeForces - 1569B Chess Tournament

    CodeForces - 1569B AYIT609第一周周赛(2021) A chess tournament will be held soon, where n chess players wi ...

  7. Codeforces 38B - Chess

    38B - Chess 思路:懂点象棋的规则就可以,看看哪些点可以放马. 代码: #include<bits/stdc++.h> using namespace std; #define ...

  8. Codeforces Round #294 (Div. 2) -- A. A and B and Chess

    大水,先区分大小写,分别累加,最后比较一下大小就好了. Description A and B are preparing themselves for programming contests. T ...

  9. A. Rainbow Dash, Fluttershy and Chess Coloring(思维) Codeforces Round #662 (Div. 2)

    原题链接:https://codeforces.com/contest/1393/problem/A 题意:给定一个正方形区域上色,要求上色的块相邻之间不能相同,也就是我们会在每回合上不同的颜色,要求 ...

最新文章

  1. ios图像处理第2部分:核心图形,核心图像,GPUImage
  2. 【收藏】cgroup的简单使用
  3. java经典100例算法题_10道java经典算法题,每一题都能帮你提升java水平!
  4. [mybatis]Mapper XML Files_获取自增主键的值
  5. 【渝粤教育】国家开放大学2019年春季 1362应用语言学 参考试题
  6. 【德国】德累斯顿工业大学 机器学习和图像分析研究助理/博士生 招生
  7. 如何让一个div里面的div垂直居中?
  8. C++新特性探究(18.2):C++11 unique_ptr智能指针详解
  9. 17muduo_base库源码分析(八)
  10. 【java笔记】线程(2):多线程的原理
  11. 简单的Python小游戏制作
  12. 2020-10-22标准正态分布表(scipy.stats)
  13. excel查看编码格式_Excel表格中格式转换的这些套路,你都get了吗?
  14. P2P网贷谋求“去担保”:引入保险 收益下滑
  15. Android仿微信语音聊天界面
  16. LED点阵显示,有关特殊国别(阿拉伯,希伯来,泰文)字符排版和乱码问题解决
  17. 小鸡啄米之React组件内部的API
  18. ant design vue pro 支持多页签模式 页签可以缓存
  19. PS-twoday-移动工具
  20. 三年级计算机绘画第二课堂教案,美术第二课堂计划讲解.docx

热门文章

  1. Win10跳过开机登录界面
  2. 通达信程序化交易接口使用步骤
  3. 移动端rpx px,rem em区别
  4. 人似秋鸿来有信,事如春梦了无痕
  5. 计算机图形学四:着色-Shading
  6. 高仿QQ发送语音界面
  7. 基于Kaldi下babel项目的语音关键词检索(KWS)
  8. 02.yuyv转rgb888代码优化
  9. 分享招聘工作流程图模板及绘制技巧
  10. d3js绘制y坐标轴_如何用D3绘制各类样式的x坐标轴