【Codeforces】【网络流】【线段树】【扫描线】Oleg and chess (CodeForces - 793G)
题意:
给定一个n*n的矩阵,一个格子上可以放一个车。其中有q个子矩阵,且q个子矩阵互不相交或者是重叠(但边界可以衔接)。这q个子矩阵所覆盖的地方都是不能够放车的。车可以越过子矩阵覆盖的地方进行攻击(车可以横着或者是竖着直走,就像象棋中的那样)。现在问的是一共最多能够放多少个不能够互相攻击的车。
数据范围
1<=n<=10000,0<=q<=10000
输入格式
首先输入两个数字n,q,分别表示矩阵的大小和子矩阵的个数。
接下来有q行,每行四个正整数x1,y1,x2,y2,分别表示每一个子矩阵的左上角的坐标和右下角的坐标。
输出格式
直接输出最多能够放多少个车即可。
思路
如果不是放在网络流的题里面,我绝对会去想贪心
我觉得已经比较详细了
听完题解之后觉得非常的恶心。
首先,如果按照最普通的方式建图的话,就是一个套路性的东西:对于每一行、每一列分别建一个点,然后再让代表第i行的点指向代表第j行的点,表示(i,j)这个地方可以放一个车。当然,如果是被子矩阵覆盖了的话,肯定就不能够相边。最后跑一边最大流就得到了答案。
但是经过一番推敲后我们可以发现,一共有O(n)个点,然后最坏有O(n*n)条边,对于网络流而言是要T的。下面我们考虑优化。
首先让我们把题目中给出的子矩形称作黑色矩形
其实我们需要优化的就是建边的过程,也就是行向列进行连边的问题。我们可以考虑将剩下的空白的部分转化为一个个的小矩形(把这些矩形称作白色矩形),然后利用线段树进行统一的连边。
这样说是很抽象的,下面我们具体探讨一下如何利用线段树优化建图。
假设当前需要加入的白色矩形为(x1,y1,x2,y2),那么在行坐标上就对应于(x1,x2)这个线段,列坐标上就对对应于(y1,y2)这个线段。然后我们在最开始建立两棵线段树,一棵表示行坐标,一棵表示列坐标,然后将(x1,x2)拆解成log(n)个子线段,将(y1,y2)拆解为log(n)个子线段,然后将这两坨子线段互相连边,那么就有log^2(n)条边。这样子就解决了建边的问题了(舒服)。
接下来,我们就只剩下了一个子问题了,那就是如何去找白色矩形(对空白部分进行划分)。首先需要满足两个前提:
1.能够成功划分完所有的空白的区域
2.划分的白色矩阵尽量少
这道题目前的优秀解法是这样的(橙色的是黑色矩阵):

可以看出,对于每一个黑色矩形,它的上下两条边都向两边进行了发散,直到碰到了其它的边或者是边界才停下来,最终就会形成类似于上面所示的一个图。
然后红色的线就自然的将空白的区域划分为了若干个白色矩形。最后经实践证明,一共有O(n)级别的白色矩形。
如何来构造这样一个方案呢?
考虑这样一个算法:
扫描每一个黑色矩形,将每一个子矩阵分成两条线段,一条是位于第y1列的(x1,x2,0),第y2列的(x1,x2,1)(后面的值表示类型),然后根据列坐标进行排序。然后按照列坐标从小到大的顺序枚举这些线段,对于列左边相同的,先枚举类型0的,再枚举类型1的。
首先让我们考虑类型0的:
假设当前的线段是(x1,x2),在第j列。我们需要在全局维护一个a[],其中a[i]表示从当前枚举到的第i行往前走,最远的能够到达的未被黑色矩形覆盖的空白格子的列坐标。
然后从x1到x2进行枚举,并记录一个当前的白色矩形的左边界last,以及上一次左边界发生变化的位置(行),lastpos。假设枚举到了i,那么假如a[i]==last,那么就继续枚举;否则就将(lastpos,last,i-1,j-1)插入。
下面是给出的一个例子:

当扫描到i+2时,我们发现a[i]发生了变化,而此时的last=a[i],lastpos=i,然后我们在这里发现了一个新的矩形,也就是(lastpos,last,i+1,j-1),插入即可。
这样做是十分暴力的,复杂度高达O(n^2),但是可以过
接下来考虑类型1的:
假设当前的线段为(x1,x2),其实这条线段的作用就是用来更新a[]数组的。也就是将a[x1~x2]修改为当前的列坐标即可。记住列坐标相等的类型1线段与类型0线段,类型1一定是在类型0之后做
需要注意的是,在最开始输入完黑色矩阵后,我们还需要加入一个(1,n+1,n+1,n)的一条线段,来保证最后右边的空白区域有白色矩形来填充。
最后网络流的图大概是这样的:

左边为一棵线段树,右边为一棵线段树,叶子分别连向源、汇点,然后中间黄色的边是插入白色矩形的时候连的边。
具体细节就参考代码吧。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define MAXN 10000
#define MAXQ 10000
#define INF 0x3FFFFFFF
using namespace std;
struct Line
{
int p,l,r,typ;
Line(){};
Line(int _p,int _l,int _r,int _typ):p(_p),l(_l),r(_r),typ(_typ){};
};
bool operator < (const Line &a,const Line &b)
{
if(a.p!=b.p) return a.p<b.p;
return a.typ>b.typ;
}
struct edge
{
int to,cap;
edge *nxt,*bck;
}edges[MAXN*500];
edge *ncnt=&edges[0],*Adj[MAXN*10+5];
struct node
{
int ch[2];
}tree[MAXN*10];
int tcnt=0,rt[2];
vector<Line> Lineseq;
vector<int> seq[2];
int n,q,S,T;
int a[MAXN+5];
int d[MAXN*10],vd[MAXN*10];
void AddEdge(int u,int v,int cap)
{
edge *p=++ncnt;
p->to=v,p->cap=cap;
p->nxt=Adj[u],Adj[u]=p;
edge *q=++ncnt;
q->to=u,q->cap=0;
q->nxt=Adj[v],Adj[v]=q;
p->bck=q,q->bck=p;
}
void Build(int &p,int l,int r)
{
p=++tcnt;
if(l==r)
return;
int mid=(l+r)/2;
Build(tree[p].ch[0],l,mid);
Build(tree[p].ch[1],mid+1,r);
}
void Bianli(int p,int l,int r,int typ)
{
if(l==r)
{
if(typ==0) AddEdge(S,p,1);//参考之前网络流的图
else AddEdge(p,T,1);
return;
}
int mid=(l+r)/2;
//最后网络流的图的形状十分特殊
if(typ==0)
AddEdge(tree[p].ch[0],p,INF),AddEdge(tree[p].ch[1],p,INF);
if(typ==1)
AddEdge(p,tree[p].ch[0],INF),AddEdge(p,tree[p].ch[1],INF);
Bianli(tree[p].ch[0],l,mid,typ);
Bianli(tree[p].ch[1],mid+1,r,typ);
}
void Query(int p,int ql,int qr,int l,int r,int typ)
{
if(qr<l||ql>r)
return;
if(ql<=l&&r<=qr)
{
seq[typ].push_back(p);
return;
}
int mid=(l+r)/2;
Query(tree[p].ch[0],ql,qr,l,mid,typ);
Query(tree[p].ch[1],ql,qr,mid+1,r,typ);
}
void Insert_Rectangle(int x1,int y1,int x2,int y2)
{
seq[0].clear(),seq[1].clear();
Query(rt[0],x1,x2,1,n,0);Query(rt[1],y1,y2,1,n,1);
for(int i=0;i<(int)seq[0].size();i++)//先把对应的子线段找出来,然后两两连边
for(int j=0;j<(int)seq[1].size();j++)
AddEdge(seq[0][i],seq[1][j],INF);
}
void Scan()
{
Line cur;
fill(a,a+1+n,1);
for(int i=0;i<(int)Lineseq.size();i++)
{
cur=Lineseq[i];
int las=a[cur.l],lasp=cur.l;//last,lastpos的初值(可以画个图理解一下)
for(int p=cur.l;p<=cur.r;p++)
if(cur.typ==0)
{
if(las!=a[p])
{
if(cur.p!=las)//插入前记得判断这个矩阵是否存在(而不是一根线)
Insert_Rectangle(lasp,las,p-1,cur.p-1);//插入白色矩阵
las=a[p],lasp=p;
}
a[p]=cur.p+1;
}
else
a[p]=cur.p;
if(cur.typ==0&&cur.p!=las)//同样需要进行判断
Insert_Rectangle(lasp,las,cur.r,cur.p-1);
}
}
int aug(int u,int tot)
{
if(u==T)
return tot;
int v,delta,sum=0,mind=T+1;
if(d[u]<mind)
mind=d[u];
for(edge *p=Adj[u];p!=NULL;p=p->nxt)
{
v=p->to;
if(p->cap>0)
{
if(d[u]==d[v]+1)
{
delta=min(tot-sum,p->cap);
delta=aug(v,delta);
sum+=delta;
p->cap-=delta,p->bck->cap+=delta;
if(d[S]>=T+1)
return sum;
if(sum==tot)
break;
}
}
}
if(sum==0)
{
vd[d[u]]--;
if(vd[d[u]]==0)
d[S]=T+1;
d[u]=mind+1;
vd[d[u]]++;
}
return sum;
}
int Isap()
{
int flow=0;
memset(d,0,sizeof(d));
memset(vd,0,sizeof(vd));
vd[0]=T+1;
while(d[S]<T+1)
flow+=aug(S,INF);
return flow;
}
int main()
{
scanf("%d %d",&n,&q);
int x1,y1,x2,y2;
for(int i=1;i<=q;i++)
{
scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
Lineseq.push_back(Line(y1,x1,x2,0));
Lineseq.push_back(Line(y2+1,x1,x2,1));
}
Lineseq.push_back(Line(n+1,1,n,0));//插入特殊矩阵
Lineseq.push_back(Line(n+2,1,n,1));
sort(Lineseq.begin(),Lineseq.end());
Build(rt[0],1,n);Build(rt[1],1,n);//线段树初始化
S=0,T=tcnt+1;
Bianli(rt[0],1,n,0);Bianli(rt[1],1,n,1);//"遍历"来进行线段树之间的连边
Scan();//扫描线
//预处理完毕
int ans=Isap();
printf("%d\n",ans);
return 0;
}
【Codeforces】【网络流】【线段树】【扫描线】Oleg and chess (CodeForces - 793G)的更多相关文章
- Codeforces VK CUP 2015 D. Closest Equals(线段树+扫描线)
题目链接:http://codeforces.com/contest/522/problem/D 题目大意: 给你一个长度为n的序列,然后有m次查询,每次查询输入一个区间[li,lj],对于每一个查 ...
- 51nod 1494 选举拉票 (线段树+扫描线)
1494 选举拉票 题目来源: CodeForces 基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 收藏 关注 现在你要竞选一个县的县长.你去对每一个选民进 ...
- 【Codeforces720D】Slalom 线段树 + 扫描线 (优化DP)
D. Slalom time limit per test:2 seconds memory limit per test:256 megabytes input:standard input out ...
- 【POJ-2482】Stars in your window 线段树 + 扫描线
Stars in Your Window Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 11706 Accepted: ...
- HDU 4419 Colourful Rectangle --离散化+线段树扫描线
题意: 有三种颜色的矩形n个,不同颜色的矩形重叠会生成不同的颜色,总共有R,G,B,RG,RB,GB,RGB 7种颜色,问7种颜色每种颜色的面积. 解法: 很容易想到线段树扫描线求矩形面积并,但是如何 ...
- BZOJ-3228 棋盘控制 线段树+扫描线+鬼畜毒瘤
3228: [Sdoi2008]棋盘控制 Time Limit: 10 Sec Memory Limit: 128 MB Submit: 23 Solved: 9 [Submit][Status][D ...
- BZOJ-3225 立方体覆盖 线段树+扫描线+乱搞
看数据范围像是个暴力,而且理论复杂度似乎可行,然后被卡了两个点...然后来了个乱搞的线段树+扫描线.. 3225: [Sdoi2008]立方体覆盖 Time Limit: 2 Sec Memory L ...
- hdu 5091(线段树+扫描线)
上海邀请赛的一道题目,看比赛时很多队伍水过去了,当时还想了好久却没有发现这题有什么水题的性质,原来是道成题. 最近学习了下线段树扫描线才发现确实是挺水的一道题. hdu5091 #include &l ...
- POJ1151+线段树+扫描线
/* 线段树+扫描线+离散化 求多个矩形的面积 */ #include<stdio.h> #include<string.h> #include<stdlib.h> ...
- POJ-1151-Atlantis(线段树+扫描线+离散化)[矩形面积并]
题意:求矩形面积并 分析:使用线段树+扫描线...因为坐标是浮点数的,因此还需要离散化! 把矩形分成两条边,上边和下边,对横轴建树,然后从下到上扫描上去,用col表示该区间有多少个下边,sum代表该区 ...
随机推荐
- BFC块级格式化上下文
BFC块级格式化上下文 触发条件 overflow 值不为 visible 的块元素 根元素 html 元素 浮动元素(元素的 float 不是 none) 绝对定位元素(元素的 position 为 ...
- MongoDB 3.6.9 集群搭建 - 切片+副本集
1. 环境准备 在Mongo的官网下载Linux版本安装包,然后解压到对应的目录下:由于资源有限,我们采用Replica Sets + Sharding方式来配置高可用.结构图如下所示: 这里我说明下 ...
- Vue Material
Material Design是什么? https://www.zhihu.com/topic/20005114/top-answers 我们挑战自我,为用户创造了崭新的视觉设计语言.与此同时,新的设 ...
- AS中的minSdkVersion、compileSdkVersion、targetSdkVersion、buildTools及tools关系和区别
1.参考文章关于compileSdk.minSdk.targetSdk的文章 http://chinagdg.org/2016/01/picking-your-compilesdkversion-mi ...
- 【ShaderToy】新玩家~❤
最近对shader产生了浓厚兴趣,发现一个超有意思的网站shadertoy.com,各种有意思的shader,很多都是百行以内代码实现,除了学习,作为opgl的练习场所也很不错. 分享今天看的一篇sh ...
- vue学习之组件
组件从注册方式分为全局组件和局部组件. 从功能类型又可以分为偏视图表现的(presentational)和偏逻辑的(动态生成dom),推荐在前者中使用模板,在后者中使用 JSX 或渲染函数动态生成组件 ...
- CEYE平台的使用
0x01 CEYE 是什么 CEYE是一个用来检测带外(Out-of-Band)流量的监控平台,如DNS查询和HTTP请求.它可以帮助安全研究人员在测试漏洞时收集信息(例如SSRF / XXE / R ...
- Web从入门到放弃<7>
从这章开始读<javascript高级程序设计> <1>typeof 返回字符串 / 类型 未定义:undefined 布尔:boolean 字符串:string 数值:num ...
- MySQL平滑删除数据的小技巧【转】
今天接到一位开发同学的数据操作需求,需求看似很简单,需要执行下面的SQL语句: delete from test_track_log where log_time < '2019-1-7 00: ...
- Python爬虫基础之Urllib
一.随时随地爬取一个网页下来 怎么爬取网页?对网站开发了解的都知道,浏览器访问Url向服务器发送请求,服务器响应浏览器请求并返回一堆HTML信息,其中包括html标签,css样式,js脚本等.Chro ...