【清华集训2017】【LOJ2331】【洛谷P4003】无限之环(费用流)

LOJ传送门

洛谷传送门


解析:

考虑将所有点交替黑白染色,源点向黑点连边,白点向汇点连边。每个点在四周建立四个点,表示这个位置是否被连接,然后所有挨着的点连边,比如 i i i上面的点的下标点和 i i i的上标点连边, i i i左边点的右标点和 i i i的左标点连边。

现在我们需要考虑怎么连接 i i i i i i四周的点。

显然初始状态接触的点直接连接费用为 0 0 0的边。

现在对于可以旋转的点分情况考虑:

1.单插头类型:1,2,4,8

只讨论 1 1 1的情况,其他显然可以通过旋转得到。

i i i向上标点连费用为 0 0 0的边,然后上标点向左右连费用为 1 1 1的边,然后向下标点连费用为 2 2 2的边。

显然这样保证流量为 1 1 1且各个方向流过来的答案是正确的。

2.L型:3,6,12,9

只讨论 3 3 3,其他可以通过旋转得到。

i i i向上标和右标连费用为 0 0 0的点,然后考虑旋转的影响。

旋转一次,我们发现其实对于 L L L型来说相当于做一次轴对称,所以我们只需要将右标向左标连费用为 1 1 1的边,上标向下标连费用为 1 1 1的边,分别表示一次轴对称(旋转)的花费。

3.凸型:7,14,13,11

实际上和 1 1 1的情况极为相似,留给读者自行思考 (或者直接看代码)

跑出来判断一下流满没有,没有流满就无解。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc getchar
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<20|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++; 
	}
	
	inline int getint(){
		re char c;
		while(!isdigit(c=gc()));re int num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
}
using namespace IO;

std::ofstream fout;
using std::cerr;
using std::cout;

inline bool ckmin(int &a,int b){return a>b?(a=b,true):false;}

cs int N=1e4+4;
namespace NetWork{
	struct edge{
		int to,cap,cost,rev;
		edge(cs int &_to,cs int &_cap,cs int &_cost,cs int &_rev):
		to(_to),cap(_cap),cost(_cost),rev(_rev){}
	};
	
	std::vector<edge> G[N];
	typedef std::vector<edge>::iterator iter;
	iter cur[N];
	
	inline void addedge(int u,int v,int cap,int cost,bool col=false){
		if(col)std::swap(u,v);
		G[u].push_back(edge(v,cap,cost,G[v].size()));
		G[v].push_back(edge(u,0,-cost,G[u].size()-1));
	}
	
	int S,T;
	int dist[N];
	bool vis[N];
	inline bool BFS(){
		memset(dist,0x3f,sizeof(int)*(T+1));
		dist[S]=0;
		std::queue<int> q({S});
		while(!q.empty()){
			int u=q.front();q.pop();
			vis[u]=false;
			for(iter re e=G[u].begin();e!=G[u].end();++e){
				if(e->cap&&dist[e->to]>dist[u]+e->cost){
					dist[e->to]=dist[u]+e->cost;
					if(!vis[e->to]){
						vis[e->to]=true;
						q.push(e->to);
					}
				}
			}
		}
		return dist[T]<N;
	}
	
	inline int dfs(int u,cs int &flow){
		if(u==T)return flow;
		int ans=0;vis[u]=true;
		for(iter &e=cur[u];e!=G[u].end();++e)if(e->cap&&dist[e->to]==dist[u]+e->cost&&!vis[e->to]){
			int delta=dfs(e->to,std::min(flow-ans,e->cap));
			if(delta){
				e->cap-=delta;
				G[e->to][e->rev].cap+=delta;
				if((ans+=delta)==flow){
					vis[u]=false;
					return flow;
				}
			}
		}
		vis[u]=false;
		return ans;
	}
	
	int mxflow,totcost;
	inline void Flow(){
		while(BFS()){
			for(int re i=0;i<=T;++i)cur[i]=G[i].begin();
			int tmp=dfs(S,0x3f3f3f3f);
			mxflow+=tmp;
			totcost+=tmp*dist[T];
		}
	}
}
using namespace NetWork;

int n,m,totflow;

signed main(){
	n=getint(),m=getint();
	S=0,T=n*m*5+1;
	for(int re i=0,k=1;i<n;++i)
	for(int re j=0;j<m;++j,++k){
		int turn=0;
#define id(x) ((x)*5)
#define u(x) (((x)-1)*5+1+(turn&3))
#define r(x) (((x)-1)*5+1+((turn+1)&3))
#define d(x) (((x)-1)*5+1+((turn+2)&3))
#define l(x) (((x)-1)*5+1+((turn+3)&3))
		bool col=(i+j)&1;
		if(col)addedge(S,id(k),0x3f3f3f3f,0);
		else addedge(id(k),T,0x3f3f3f3f,0);
		if(i)addedge(d(k-m),u(k),1,0,col);
		if(j)addedge(r(k-1),l(k),1,0,col);
		int typ=getint();
		if(typ&1)addedge(u(k),id(k),1,0,col),++totflow;
		if(typ&2)addedge(r(k),id(k),1,0,col),++totflow;
		if(typ&4)addedge(d(k),id(k),1,0,col),++totflow;
		if(typ&8)addedge(l(k),id(k),1,0,col),++totflow;
		switch(typ){
			case 8:++turn;
			case 4:++turn;
			case 2:++turn;
			case 1:
				addedge(r(k),u(k),1,1,col);
				addedge(d(k),u(k),1,2,col);
				addedge(l(k),u(k),1,1,col);
				break;
			case 9:++turn;
			case 12:++turn;
			case 6:++turn;
			case 3:
				addedge(d(k),u(k),1,1,col);
				addedge(l(k),r(k),1,1,col);
				break;
			case 11:++turn;
			case 13:++turn;
			case 14:++turn;
			case 7:
				addedge(l(k),r(k),1,2,col);
				addedge(l(k),u(k),1,1,col);
				addedge(l(k),d(k),1,1,col);
				break;
		}
	}
	Flow();
	cout<<(mxflow*2==totflow?totcost:-1)<<"\n";
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值