版权声明:本文为博主原创文章,未经博主允许也可以转载。 https://blog.csdn.net/qq_35541672/article/details/85228301
题意
给出一个n*n的棋盘,其中有q个矩形区域不能放棋子(但是不会隔开攻击),问最多能摆放多少个互不攻击的车
n,q<=10000(时限6.5s,而且是cf)
题解
首先考虑如果已经知道了一些可以放的单个位置,该怎么做
就一个正常的二分图建模网络流对吧
如果是知道了一些可以放的矩形区域呢?我们可以用线段树来优化建图,用两个线段树分别代表连续的x或y的一段
那么现在的问题就是怎么从不能放的得到可以放的
最直观的想法就是每个不能放的地方横竖剖开
然而这样遇到一条对角线就成功爆炸
所以要扫描线
更具体一点,是这样的
我们位于一个a[i]表示纵坐标为i时它最右边的值
将每个矩形拆成左右两根线段,左边那根用来更新它左边的矩形其实就是暴力找右边的a[i]连续的段,右边那根用来维护a[i]
(其实本质上来讲用左边那根维护未尝不可)
这个维护复杂度是…最坏O(n^2)的…然而CF能过…
最后再用[1,n]更新一下,得到最右边的那个矩形
em…就这样了。
然而这道题ISAP过不去…
好久没写Dinic了…
Dinic我待会再补,先放个ISAP版的代码(这个代码把ISAP改成Dinic就能过)
upd. Dinic代码已补上,同时发生了玄学错误
调了好久…一大堆调试信息懒得删了
//!CF 793G
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define lch tree[x].l
#define rch tree[x].r
using namespace std;
typedef long long ll;
const int N=1e4+5,M=4e5+5;
const int INF=0x3f3f3f3f;
struct node{//扫描线
int x,l,r,f;
node(){}
node(int _x,int _l,int _r,int _f):x(_x),l(_l),r(_r),f(_f){}
}line[2*N];
bool cmp(node a,node b){
if(a.x!=b.x)
return a.x<b.x;
return a.f<b.f;//同一位置更新优先
}
struct mode{
int l,r;
}tree[N*8];
int rt1,rt2,tcnt;
struct lode{
int u,v,cap,nxt;
}edge[M*8];
int head[M],mcnt=1;
int S,T;
int a[N];
int n,m;
void add_edge(int u,int v,int cap){
mcnt++;
edge[mcnt].u=u;
edge[mcnt].v=v;
edge[mcnt].cap=cap;
edge[mcnt].nxt=head[u];
head[u]=mcnt;
}
void add(int u,int v,int cap){
add_edge(u,v,cap);
add_edge(v,u,0);
//printf("%d->%d %d\n",u,v,cap);
}
void Build(int &x,int fa,int l,int r,int dir){//建线段树
x=++tcnt;
if(fa){
if(!dir)
add(x,fa,INF);
else
add(fa,x,INF);
}
if(l==r)
return ;
int mid=(l+r)>>1;
Build(lch,x,l,mid,dir);
Build(rch,x,mid+1,r,dir);
}
void Build2(int x,int l,int r,int dir){//建ST到叶子
if(l==r){
if(dir)
add(S,x,1);
else
add(x,T,1);
return ;
}
int mid=(l+r)>>1;
Build2(lch,l,mid,dir);
Build2(rch,mid+1,r,dir);
}
void Query(int x,int l,int r,int pl,int pr,int y,int dir){//区间连边
if(pl>pr||x==0)//这里不能省...
return ;
if(pl<=l&&r<=pr){
if(!dir)
add(x,y,INF);
else
add(y,x,INF);
return ;
}
int mid=(l+r)>>1;
if(pl<=mid)
Query(lch,l,mid,pl,pr,y,dir);
if(pr>mid)
Query(rch,mid+1,r,pl,pr,y,dir);
}
void Insert(int x,int l,int r){//左边线段的插入
int pos=-1,last=-1,y;
for(int i=l;i<=r+1;i++){
if(i<=r)
y=a[i]+1,a[i]=-1;
if(y!=last||i==r+1){
if(pos!=-1){
int z=++tcnt;
Query(rt1,1,n,pos,i-1,z,0);
Query(rt2,1,n,last,x,z,1);
/*printf("%d %d %d %d\n\t",pos,last,x,z);
for(int i=1;i<=5;i++)
printf("%d ",a[i]);
puts("");*/
}
pos=i;
last=y;
}
}
}
void Update(int x,int l,int r){//右边线段的更新
for(int i=l;i<=r;i++)
a[i]=x;
}
//以下是Dinic
int dist[M];
int cur[M];//当前弧
queue<int>q;
bool bfs(){
for(int i=1;i<=tcnt;i++)
dist[i]=-1,cur[i]=head[i];
dist[S]=0;
q.push(S);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].v;
if(~dist[v]||!edge[i].cap)
continue ;
dist[v]=dist[u]+1;
q.push(v);
}
}
return ~dist[T];
}
int dfs(int u,int CAP){
if(u==T||!CAP)
return CAP;
int flow=0;
for(int &i=cur[u];i;i=edge[i].nxt){
int v=edge[i].v,cap=edge[i].cap;
if(dist[v]==dist[u]+1&&cap){
int f=dfs(v,min(cap,CAP));
if(!f)
continue ;
flow+=f;
CAP-=f;
if(edge[i].cap!=INF)
edge[i].cap-=f;
if(edge[i^1].cap!=INF)
edge[i^1].cap+=f;
if(!CAP)//这里注释掉就T了???
break;
}
}
return flow;
}
int Dinic(){
int max_flow=0;
while(bfs()){
max_flow+=dfs(S,INF);
}
return max_flow;
}
void Read(int &x){
x=0;
char c=getchar();
while(c<'0'||c>'9')
c=getchar();
while(c>='0'&&c<='9')
x=x*10+c-'0',c=getchar();
}
int main()
{
Read(n),Read(m);
//scanf("%d%d",&n,&m);
Build(rt1,0,1,n,0);
Build(rt2,0,1,n,1);
for(int i=1;i<=m;i++){
int l,d,u,r;
Read(l),Read(d),Read(r),Read(u);
//scanf("%d%d%d%d",&l,&d,&r,&u);
line[i]=node(l-1,d,u,1);
line[i+m]=node(r,d,u,-1);
}
sort(line+1,line+m*2+1,cmp);
for(int i=1;i<=2*m;i++){
//printf("*%d %d %d %d\n",line[i].x,line[i].l,line[i].r,line[i].f==1?0:1);
if(line[i].f==1)
Insert(line[i].x,line[i].l,line[i].r);
else
Update(line[i].x,line[i].l,line[i].r);
}
Insert(n,1,n);
S=++tcnt;
T=++tcnt;
Build2(rt1,1,n,1);
Build2(rt2,1,n,0);
int ans=Dinic();
printf("%d\n",ans);
}