「Codeforces 793G」Oleg and chess

传送门


problem

有一个 n × n n\times n n×n 的棋盘,给出 q q q 个矩阵,这 q q q 个矩阵里不能放棋子,矩阵无交。

定义两颗棋子( ( x 1 , y 1 ) (x_1,y_1) (x1,y1) ( x 2 , y 2 ) (x_2,y_2) (x2,y2))冲突当且仅当 x 1 = x 2 x_1=x_2 x1=x2 或者 y 1 = y 2 y_1=y_2 y1=y2

问棋盘中最多可以放多少互不冲突的棋子。

数据范围: 1     ≤     n   ≤     10000 1  ≤  n ≤  10000 1n10000 0   ≤     q   ≤     10000 0 ≤  q ≤  10000 0q10000


solution

网络流首先应该是不难看出来的。

如果一个格子能放棋子,我们就从它的行向列连边,最后二分图最大匹配就是答案。

但是如果暴力建边,边数是 O ( n 2 ) O(n^2) O(n2) 的,显然会 T

优化方法?考虑线段树优化建图

那么,如果是矩阵之内的格子能放,那直接用区间连区间的思路就可以了。

反过来,我们用扫描线,用线段树处理能放的区间,具体就是,我们一行一行扫,对于整个被矩形覆盖的区间就不连,如果没有被完全覆盖

还是看代码吧,感觉有点口胡。。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)


code

#include<bits/stdc++.h>
#define N 5000005
#define inf 0x3f3f3f3f
using namespace std;
int n,q,S,T,t=1,cnt,tot;
int first[N],v[N],w[N],nxt[N];
struct Line{int y1,y2,flag;};
vector<Line>l[N];
void add(int x,int y,int z){
	nxt[++t]=first[x],first[x]=t,v[t]=y,w[t]=z;
	nxt[++t]=first[y],first[y]=t,v[t]=x,w[t]=0;
}
namespace Segment_Tree{
	int point[N],cov[N];
	#define mid ((l+r)>>1)
	void pushup(int root){
		point[root]=++tot;
		if(!cov[root<<1])  add(point[root],point[root<<1],inf);
		if(!cov[root<<1|1])  add(point[root],point[root<<1|1],inf);
	}
	void build(int root,int l,int r){
		if(l==r)  {point[root]=n+l;return;}
		build(root<<1,l,mid),build(root<<1|1,mid+1,r);
		pushup(root);
	}
	void Modify(int root,int l,int r,int x,int y,int val){
		if(l>=x&&r<=y)  {cov[root]+=val;return;}
		if(x<=mid)  Modify(root<<1,l,mid,x,y,val);
		if(y> mid)  Modify(root<<1|1,mid+1,r,x,y,val);
		pushup(root);
	}
	#undef mid
}
using namespace Segment_Tree;
namespace DINIC{
	int d[N],f[N];
	bool bfs(){
		memset(d,-1,sizeof(d));
		memcpy(f,first,sizeof(f));
		queue<int>Q;Q.push(S),d[S]=0;
		while(!Q.empty()){
			int x=Q.front();Q.pop();
			for(int i=first[x];i;i=nxt[i]){
				int to=v[i];
				if(w[i]&&d[to]==-1)  d[to]=d[x]+1,Q.push(to);
			}
		}
		return d[T]!=-1;
	}
	int dinic(int now,int flow){
		if(now==T)  return flow;
		int delta,ans=0;
		for(int &i=f[now];i;i=nxt[i]){
			int x=v[i];
			if(w[i]&&d[x]==d[now]+1){
				delta=dinic(x,min(flow,w[i]));
				w[i]-=delta,w[i^1]+=delta,flow-=delta,ans+=delta;
				if(!flow)  return ans;
			}
		}
		return ans;
	}
}
using namespace DINIC;
int main(){
	int x1,y1,x2,y2;
	scanf("%d%d",&n,&q);
	while(q--){
		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
		l[x1].push_back((Line){y1,y2,1});
		l[x2+1].push_back((Line){y1,y2,-1});
	}
	S=0,T=tot=2*n+1;
	for(int i=1;i<=n;++i)  add(S,i,1),add(n+i,T,1);
	build(1,1,n);
	for(int i=1;i<=n;++i){
		for(int j=0;j<l[i].size();++j){
			Line x=l[i][j];
			Modify(1,1,n,x.y1,x.y2,x.flag);
		}
		if(!cov[1])  add(i,point[1],inf);
	}
	int ans=0;
	while(bfs())  ans+=dinic(S,inf);
	printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值