1. 程式人生 > >3189 Steady Cow Assignment 二分 + 列舉 + (最大流||二分圖多重匹配)

3189 Steady Cow Assignment 二分 + 列舉 + (最大流||二分圖多重匹配)

題意:有n頭豬,m個豬圈,每個豬圈都有一定的容量,每隻豬對每個豬圈的喜好度不同(所有豬圈在每個豬心中都有一個排名),要求所有的豬都進豬圈,但是要求所有豬的喜好度排名最低的和最高的差值的絕對值最小。

思路:這題網上很多做法,但綜合來說無非  二分、列舉、最大流、二分圖多重匹配,二分就是二分題目中問的差值的絕對值,列舉就是列舉上界或者下屆,最大流或者二分圖多重匹配就是用來檢查前面列舉的解可不可行。

我是先列舉上界,然後針對每個上界進行二分割槽間長度,然後最大流檢查。

看到網上一個較快的程式碼省略了二分的步驟,用尺取法直接列舉上界和下界,然後根據檢查結果移動上下界,這也是個可行的辦法。

程式碼:

//ISAP int
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define ll long long
#define MAXN 10005
#define inf 0x3f3f3f3f
using namespace std;
int n;//實際建圖點數(最好比圖中總點數大一點)
struct Edge{
	int v,next;
	int cap,flow;
}edge[MAXN*100];
int cur[MAXN],pre[MAXN],gap[MAXN],path[MAXN],dep[MAXN];
int cnt=0;//實際儲存總邊數 
void init()
{
	cnt=0;
	memset(pre,-1,sizeof(pre)); 
}
void add(int u,int v,int w,int rw=0)//加邊 有向圖三個引數,無向圖四個 
{
	edge[cnt].v=v;
	edge[cnt].cap=w;
	edge[cnt].flow=0;
	edge[cnt].next=pre[u];
	pre[u]=cnt++;
	edge[cnt].v=u;
	edge[cnt].cap=rw;
	edge[cnt].flow=0;
	edge[cnt].next=pre[v];
	pre[v]=cnt++;
}
bool bfs(int s,int t)//其實這個bfs可以融合到下面的迭代裡,但是好像是時間要長 
{
	memset(dep,-1,sizeof(dep));
	memset(gap,0,sizeof(gap));
	gap[0]=1;
	dep[t]=0;
	queue<int>q;
	while(!q.empty())
	q.pop();
	q.push(t);//從匯點開始反向建層次圖 
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=pre[u];i!=-1;i=edge[i].next)
		{
			int v=edge[i].v;
			if(dep[v]==-1&&edge[i^1].cap>edge[i^1].flow)//注意是從匯點反向bfs,但應該判斷正向弧的餘量
			{
				dep[v]=dep[u]+1;
				gap[dep[v]]++;
				q.push(v);
				//if(v==s)//感覺這兩句優化加了一般沒錯,但是有的題可能會錯,所以還是註釋出來,到時候視情況而定 
				//break;
			}	
		}
	}
	return dep[s]!=-1; 
}
int isap(int s,int t)
{
	bfs(s,t);
	memcpy(cur,pre,sizeof(pre));
	int u=s;
	path[u]=-1;
	int ans=0;
	while(dep[s]<n)//迭代尋找增廣路 
	{
		if(u==t)
		{
			int f=inf;
			for(int i=path[u];i!=-1;i=path[edge[i^1].v])//修改找到的增廣路 
				f=min(f,edge[i].cap-edge[i].flow);
			for(int i=path[u];i!=-1;i=path[edge[i^1].v])
			{
				edge[i].flow+=f;
				edge[i^1].flow-=f;
			}
			ans+=f;
			u=s;
			continue;
		}
		bool flag=false;
		int v;
		for(int i=cur[u];i!=-1;i=edge[i].next)
		{
			v=edge[i].v;
			if(dep[v]+1==dep[u]&&edge[i].cap-edge[i].flow)
			{
				cur[u]=path[v]=i;//當前弧優化 
				flag=true;
				break;
			}
		}
		if(flag)
		{
			u=v;
			continue;
		}
		int x=n;
		if(!(--gap[dep[u]]))return ans;//gap優化 
		for(int i=pre[u];i!=-1;i=edge[i].next)
		{
			if(edge[i].cap-edge[i].flow&&dep[edge[i].v]<x)
			{
				x=dep[edge[i].v];
				cur[u]=i;//常數優化 
			}
		}
		dep[u]=x+1;
		gap[dep[u]]++;
		if(u!=s)//當前點沒有增廣路則後退一個點 
		u=edge[path[u]^1].v;
	 } 
	 return ans;
}
int N,B;
int w[1100][22],c[22];
bool check(int l,int r)
{
	int s=0,t=B+N+1;
	init();
	for(int i=1;i<=N;i++)
	{
		add(s,i,1);
		for(int j=l;j<=r;j++)
		{
			add(i,w[i][j]+N,1);
		}
	}
	for(int i=1;i<=B;i++)
	add(i+N,t,c[i]);
	n=t+1;
	return isap(s,t)>=N;
}
int get(int k)
{
	int l=k,r=B,mid;
	bool flag=0;
	while(l<=r)
	{
		mid=(l+r)>>1;
		if(check(k,mid))
		r=mid-1,flag=1;
		else
		l=mid+1;
	}
	if(flag)
	return r+1-k+1;
	else
	return inf;
}
int main()
{
	cin>>N>>B;
	for(int i=1;i<=N;i++)
	{
		for(int j=1;j<=B;j++)
		{
			scanf("%d",&w[i][j]);
		}
	}
	for(int i=1;i<=B;i++)
	cin>>c[i];
	int ans=inf;
	for(int i=1;i<=B;i++)
	ans=min(ans,get(i));
	cout<<ans;
}