【BZOJ】2965: 保护古迹-平面图转对偶图(最小左转法)+最小割

传送门:bzoj2965


题解

平面图转对偶图后跑个最小割即可。
p ≤ 10 p\leq 10 p10,所以直接 2 p 2^{p} 2p枚举所有情况即可。

平面图转对偶图的具体过程:

首先将每条边拆成两条有向边,将每个点的出边按斜率排序。

这里假设有向边左侧的面属于内部。

枚举未访问的边,从当前边终点不断向外拓展:每次选择严格小于当前边反向边斜率的斜率最大的一条边(显然在为当前极小平面的边)走(若不存在严格小于当前边反向边斜率的边,则是最外的无限平面,走斜率最大的即可),直到走回初始边起点。

upd.听说这叫最小左转法?

最后需要注意拓展的同时记录下有向面积(为负即是无限平面)和在所有有向边左侧的古迹点(在当前极小面内)。


代码

#include<bits/stdc++.h>
#define pb push_back
#define vi vector<int>::iterator
#define ops(x) ((x&1)?(x+1):(x-1))
#define trs(x) ot[x]?num+2:x
typedef double db;
using namespace std;
const db eps=1e-8;
const int N=2e5+10,inf=0x7f7f7f7f;

int hty,num,cot,n,m,ans[11],bel[11],tg[N];
vector<int>fr[102];
bool in[11],ot[N];

struct P{
   db x,y;
   P(db x_=0.0,db y_=0.0):x(x_),y(y_){};
   inline P operator -(const P&ky){return P(x-ky.x,y-ky.y);}
   inline db operator ^(const P&ky){return x*ky.y-y*ky.x;}
}ht[11],p[101];

struct Line{
	P st,ed,dir;db ag;int w,x,y;
	Line(){};
	Line(P st_,P ed_,int w_,int x_,int y_)
	{st=st_;ed=ed_;dir=ed-st;ag=atan2(dir.y,dir.x);w=w_;x=x_;y=y_;}
}le[N];

struct E{int u,v,w;}e[N];

inline int dcmp(db x){if(fabs(x)<eps) return 0;return x>0?1:-1;}

inline bool cmp(int x,int y)
{
	if(dcmp(le[x].ag-le[y].ag)) return le[x].ag<le[y].ag;
	return dcmp(le[x].dir^le[y].dir)>0;
	//>0 而不是>=0 (必须严格大于)不然upper_bound会出锅  
}

inline bool onlf(P a,Line b){return dcmp(b.dir^(a-b.st))>-1;}
void dfs(int nw,int id)
{
	int i,j,st=le[nw].x,pos=le[nw].y;tg[nw]=id;
	db sum=p[st]^p[pos];vi it;
	for(i=1;i<=hty;++i) in[i]=onlf(ht[i],le[nw]);
    for(;st^pos;){
    	//没有判等于所以不能lower_bound 
    	it=--upper_bound(fr[pos].begin(),fr[pos].end(),ops(nw),cmp);
    	if(it==fr[pos].begin()) 
		  it=fr[pos].end();
		nw=*(--it);tg[nw]=id;
		for(i=1;i<=hty;++i) if(in[i]) in[i]=onlf(ht[i],le[nw]);
		j=le[nw].y;sum+=p[pos]^p[j];pos=j;
	}
	for(i=1;i<=hty;++i) if(in[i]) bel[i]=id;
	if(sum<0) ot[id]=1;
}

namespace Flow{
	
	int cur[N],head[N],to[N],nxt[N],w[N],dep[N],tot,S,T;
	
	inline void init()
	{memset(head,0,(num+5)<<2);tot=1;}
	
	inline void lk(int u,int v,int flw)
	{
		to[++tot]=v;nxt[tot]=head[u];head[u]=tot;w[tot]=flw;
		to[++tot]=u;nxt[tot]=head[v];head[v]=tot;w[tot]=0;
	}
	
	queue<int>que;
	inline bool bfs()
	{
		memset(dep,0xff,(num+5)<<2);
		int i,j,x;dep[S]=0;que.push(S);
		for(;!que.empty();){
			x=que.front();que.pop();
			for(i=head[x];i;i=nxt[i]){
				j=to[i];if((~dep[j])||(!w[i])) continue;
				dep[j]=dep[x]+1;que.push(j);
			}
		}
		return (dep[T]>-1);
	}
	
	int dfs(int x,int f)
	{
		if(x==T) return f;
		int &i=cur[x],j,ss=0,res;
		for(;i;i=nxt[i]){
			j=to[i];if((dep[j]!=dep[x]+1)||(!w[i])) continue;
			res=dfs(j,min(f-ss,w[i]));if(res){
				w[i]-=res;w[i^1]+=res;ss+=res;
				if(ss==f) return f;
			}
		}
		if(!ss) dep[x]=-1;
		return ss;
	}
	
	inline void sol(int mask)
	{
		int sz=0,re=0,i,x,y;init();
		for(i=0;i<hty;++i) 
		  if((mask>>i)&1)
		   {sz++;lk(S,bel[i+1],inf);}
		for(i=1;i<=cot;++i)
		  lk(e[i].u,e[i].v,e[i].w);
		for(;bfs();){
			memcpy(cur,head,(num+5)<<2);
			re+=dfs(S,inf);
		}
		ans[sz]=min(ans[sz],re);
	}
}

int main(){
	int i,j,x,y,z;
	memset(ans,0x7f,sizeof(ans));
	
	scanf("%d%d%d",&hty,&n,&m);cot=m<<1;
	for(i=1;i<=hty;++i) scanf("%lf%lf",&ht[i].x,&ht[i].y);
	for(i=1;i<=n;++i) scanf("%lf%lf",&p[i].x,&p[i].y);
	for(i=1;i<=m;++i){
		scanf("%d%d%d",&x,&y,&z);j=(i<<1)-1;
		le[j]=Line(p[x],p[y],z,x,y);fr[x].pb(j); 
		le[j+1]=Line(p[y],p[x],z,y,x);fr[y].pb(j+1);
	}
	for(i=1;i<=n;++i) sort(fr[i].begin(),fr[i].end(),cmp);
	
	for(i=1;i<=cot;++i) if(!tg[i]) dfs(i,++num);
	for(i=1;i<=cot;i+=2){
		x=trs(tg[i]);y=trs(tg[i+1]);z=le[i].w;
		e[i]=(E){x,y,z};e[i+1]=(E){y,x,z};
	}
	
	Flow::S=num+1;Flow::T=num+2;
	for(i=(1<<hty)-1;i;--i) Flow::sol(i);
	for(i=hty;i>1;--i) ans[i-1]=min(ans[i-1],ans[i]);
	for(i=1;i<=hty;++i) printf("%d\n",ans[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值