【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代表该区 ...
随机推荐
- jmeter笔记(1)--原理,下载与安装
Apache JMeter是Apache组织开发的基于Java的压力测试工具.用于对软件做压力测试,它最初被设计用于Web应用测试,但后来扩展到其他测试领域. 它可以用于测试静态和动态资源,例如静态文 ...
- 洛谷P2822 组合数问题(题解)
https://www.luogu.org/problemnew/show/P2822(题目传送) 先了解一下有关组合数的公式:(m在上,n在下) 组合数通项公式:C(n,m)=n!/[m!(n-m) ...
- html页面中引入自签名证书的js web资源出现net::ERR_CERT_AUTHORITY_INVALID
其实是浏览器客户端对自签名的内容认为不安全引起的,临时方法可以再浏览器中先直接访问下那个自签名的https地址,然后再访问有引用的那个页面就可以了. 以下内容引用自https://www.morong ...
- Fiddler--Filters
本篇主要介绍Fiddler中Filters(过滤器)选项功能. 先看看Filters的界面: 一.模块一 当勾选“Use Filters”,Filters才开始工作:否则Filters中的设置内容将无 ...
- Windows下配置eclipse写WordCount
1 下载插件 hadoop-eclipse-plugin-2.7.2.jar github上下载源码后需要自己编译.这里使用已经编译好的插件即可 2 配置插件 把插件放到..\eclipse\plug ...
- 我是怎么知道 PTHREAD_MUTEX_INITIALIZER 是什么鬼东西的 ??
很简单 写这么几句代码 1 #include <pthread.h> 2 3 PTHREAD_MUTEX_INITIALIZER cpp 一解析就出来了 1714 # 238 " ...
- [再寄小读者之数学篇](2014-06-23 二阶导数估计 [中国科学技术大学2013年高等数学B 考研试题])
设 $f(x)$ 二阶连续可导, $f(0)=f(1)=0$, $\dps{\max_{0\leq x\leq 1}f(x)=2}$. 证明: $$\bex \min_{0\leq x\leq 1}f ...
- sonarqube6.7部署文档
应用介绍:sonarqube是一个用于代码质量管理的开源平台,用于管理源代码的质量通过插件形式:可以支持包括Java.C#/C++.PL/SQL.Cobol.javascrip.Groovy等等二十几 ...
- 基于Windows,Python,Theano的深度学习框架Keras的配置
1.安装Anaconda 面向科学计算的Python IDE--Anaconda 2.打开Anaconda Prompt 3.安装gcc环境 (1)conda update conda (2)cond ...
- 使用Roslyn编译项目的示例
using System; using System.Collections.Generic; using System.IO; using Microsoft.CodeAnalysis; using ...