题意:

给定一个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)的更多相关文章

  1. Codeforces VK CUP 2015 D. Closest Equals(线段树+扫描线)

    题目链接:http://codeforces.com/contest/522/problem/D 题目大意:  给你一个长度为n的序列,然后有m次查询,每次查询输入一个区间[li,lj],对于每一个查 ...

  2. 51nod 1494 选举拉票 (线段树+扫描线)

    1494 选举拉票  题目来源: CodeForces 基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题  收藏  关注 现在你要竞选一个县的县长.你去对每一个选民进 ...

  3. 【Codeforces720D】Slalom 线段树 + 扫描线 (优化DP)

    D. Slalom time limit per test:2 seconds memory limit per test:256 megabytes input:standard input out ...

  4. 【POJ-2482】Stars in your window 线段树 + 扫描线

    Stars in Your Window Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 11706   Accepted:  ...

  5. HDU 4419 Colourful Rectangle --离散化+线段树扫描线

    题意: 有三种颜色的矩形n个,不同颜色的矩形重叠会生成不同的颜色,总共有R,G,B,RG,RB,GB,RGB 7种颜色,问7种颜色每种颜色的面积. 解法: 很容易想到线段树扫描线求矩形面积并,但是如何 ...

  6. BZOJ-3228 棋盘控制 线段树+扫描线+鬼畜毒瘤

    3228: [Sdoi2008]棋盘控制 Time Limit: 10 Sec Memory Limit: 128 MB Submit: 23 Solved: 9 [Submit][Status][D ...

  7. BZOJ-3225 立方体覆盖 线段树+扫描线+乱搞

    看数据范围像是个暴力,而且理论复杂度似乎可行,然后被卡了两个点...然后来了个乱搞的线段树+扫描线.. 3225: [Sdoi2008]立方体覆盖 Time Limit: 2 Sec Memory L ...

  8. hdu 5091(线段树+扫描线)

    上海邀请赛的一道题目,看比赛时很多队伍水过去了,当时还想了好久却没有发现这题有什么水题的性质,原来是道成题. 最近学习了下线段树扫描线才发现确实是挺水的一道题. hdu5091 #include &l ...

  9. POJ1151+线段树+扫描线

    /* 线段树+扫描线+离散化 求多个矩形的面积 */ #include<stdio.h> #include<string.h> #include<stdlib.h> ...

  10. POJ-1151-Atlantis(线段树+扫描线+离散化)[矩形面积并]

    题意:求矩形面积并 分析:使用线段树+扫描线...因为坐标是浮点数的,因此还需要离散化! 把矩形分成两条边,上边和下边,对横轴建树,然后从下到上扫描上去,用col表示该区间有多少个下边,sum代表该区 ...

随机推荐

  1. 导出python的环境

    1.导出 pip freeze >  packegas.txt 2.在其他环境安装 pip install -r  packages.txt

  2. 关于ddl(新增字段)对数据库锁表|读写操作的影响_资料

    1.对一个表执行ddl(新增字段)会不会阻塞表,影响读写? 在一次项目升级之前需要执行一个新增字段的脚本(alter table...),表的数据量是260多万,执行时间是72秒,感觉略长,不知道会不 ...

  3. python脚本 用sqoop把mysql数据导入hive

    转:https://blog.csdn.net/wulantian/article/details/53064123 用python把mysql数据库的数据导入到hive中,该过程主要是通过pytho ...

  4. 第十六节:语法总结(3)(C#6.0和C#7.0新语法)

    一. C# 6.0 新语法 1. 自动属性初始化可以赋值 /// <summary> /// 自动属性初始化 /// </summary> public class UserI ...

  5. H5取经之路——CSS基础语法

    一.CSS常用选择器 [选择器的命名规则] * 1.只能有字母数字下划线组成,不能有其他任何字符 * 2.开头不能是数字 [通用选择器] * 1.写法:*{} * 2.选中页面中的所有标签 * 3.优 ...

  6. Map和Collection

    Map:key---Value(一对儿数据) HashMap:无序存放,key不允许重复 HashTable:无序存放,key不允许重复 key是set集合,value是collection集合 Co ...

  7. 人工智能-调百度AI接口+图灵机器人

    1.登陆百度AI的官网 1.注册:没有账号注册 2.创建应用 3.创建应用 4.查看应用的ID 5.Python代码 from aip import AipSpeech APP_ID = " ...

  8. jetty启动设置端口

    nohup java -jar start.jar jetty.port=10010 命令不能在后台运行,ctrl+c程序就自动停止了,可以在命令后面加个&符号,就可以了 nohup java ...

  9. 在visual studio 2013中编译Lua5.3.1

    注:以下是基于 别人的教程或笔记来操作并按照自己的操作记录的纯文字版编译和hello lua过程. 原图文版链接: 原文链接 1.创建空的解决方案: 文件->新建->项目->其他项目 ...

  10. 关于button去掉自带阴影效果的方法

    在button的属性设置里加上: style=”?android:attr/borderlessButtonStyle” 即: <Button android:layout_width=&quo ...