二分图 与 网络流

Hdu 4240 Route Redundancy

题意:
给定一张n个点m条边的有向图,边具有流量限制,给定起点与终点
问整张图从起点到终点的“最大总流量”与“单条路径的最大流量”的比值是多少
思路:
先根据要求建图跑一遍最大流,最大总流量便能求出
“单条路径的最大流量”也就是此时网络中从起点到终点的最长路,搜索即可
用dfs来搜索能减少时间复杂度(别问,问就是用 bfs TLE了)
当前边的流量,就是起点 u -> 终点 v 这条边的反向边的容量,再在搜索过程中更新每个节点即可

int n,m,id,s,t;
struct edge{
	ll to,cap,rev;
}; 
vector<edge>mp[maxn];
ll level[maxn];//顶点到源点的距离标号 
ll iter[maxn]; //当前弧,在其之前的边已经没有用了 
void add(int from,int to,ll cap){
	mp[from].push_back({to,cap,mp[to].size()});
	mp[to].push_back({from,0,mp[from].size()-1});
}
void bfs(int s){
	memset(level,-1,sizeof(level));
	queue<int>q;
	level[s]=0;
	q.push(s);
	while(!q.empty()){
		int v=q.front();q.pop();
		for(int i=0;i<mp[v].size();i++){
			edge &e=mp[v][i];
			if(e.cap>0&&level[e.to]<0){
				level[e.to]=level[v]+1;
				q.push(e.to);
			}
		}
	}
}
int dfs(int v,int t,ll f){
	if(v==t)return f;
	for(ll &i=iter[v];i<mp[v].size();i++){
		edge &e=mp[v][i];
		if(e.cap>0&&level[v]<level[e.to]){
			int d=dfs(e.to,t,min(f,e.cap));
			if(d>0){
				e.cap-=d;
				mp[e.to][e.rev].cap+=d;
				return d;
			}
		}
	}
	return 0;
}
ll max_flow(int s,int t){
	ll flow=0;
	while(1){
		bfs(s);
		if(level[t]<0)return flow;
		memset(iter,0,sizeof(iter));
		ll f;
		while((f=dfs(s,t,INF))>0)flow+=f;
	}
	return flow;
}
ll vis[1005];
void dfss(int u){
	for(int i=0;i<mp[u].size();i++){
		int v=mp[u][i].to;
		int flow=min(mp[v][mp[u][i].rev].cap,vis[u]);
		if(vis[v]<flow){
			vis[v]=flow;
			dfss(v);
		}
	}
}
int main()
{
	scanf("%d",&t);
	while(t--){
		int tt;
		scanf("%d%d%d%d%d",&id,&n,&m,&s,&tt);
		for(int i=1;i<=m;i++){
			int a,b,c;
			scanf("%d%d%d",&a,&b,&c);
			add(a+1,b+1,c);
		}
		
		int ans=max_flow(s+1,tt+1);
		memset(vis,0,sizeof(vis));
		vis[s+1]=INF;
		dfss(s+1);
		double res=1.0*vis[tt+1];
		res=1.0*ans/res;
		printf("%d %.3lf\n",id,res);
		for(int i=1;i<=n;i++)mp[i].clear();
	}
    return 0;
}

Hdu 6582 Path

题意:
给定n个点m条边的一张有向图
Jerry每次都会沿着最短路从1号点走到n号点
现在Tom想断掉某些路,使得Jerry必须走更长的路,断掉一条路径的花费即该路的长度
问Tom的最小花费
思路:
题目只问从起点到终点需要割多少边,那么我们可以先用最短路算法跑出可能的最短路径。
再用跑出来的路线创建网络,再求最小割即可。
重点:
在 s - t 流中,要将 s 和 t 分为两个不同集合 的问题,此时最小割 == 最大流。
小 tip:在复原路径时,我们只要遍历原来的所有边,如果两个端点 a , b之间的距离差 ==那么就说明这条边在最短路上,建边即可。

ll dist[maxn];
void dijkstra(int st){
    priority_queue<P> pq;
    memset(dist,INF,sizeof dist);
    dist[st]=0;pq.push(P(0,st));
    while(!pq.empty()){
        int v=pq.top().second;pq.pop();
        for(auto pd:vv[v])if(dist[v]+pd.cap<dist[pd.to])
        dist[pd.to]=dist[v]+pd.cap,pq.push(P(-dist[pd.to],pd.to));
    }
}
int main()
{
	scanf("%d",&t);
	while(t--){
		int tt;
		scanf("%d%d",&n,&m);
		for(int i=1;i<=m;i++){
			int a,b,c;scanf("%d%d%d",&a,&b,&c);
			add1(a,b,c);
		}
		dijkstra(1);
		for(int i=1;i<=n;i++){
			for(int j=0;j<vv[i].size();j++){
				edge e=vv[i][j];
				if(dist[e.to]==dist[i]+e.cap){ //这里用来建立新的网络
					add(i,e.to,e.cap);
				}
			}
		}
		printf("%d\n",max_flow(1,n));
		for(int i=1;i<=n;i++)mp[i].clear(),vv[i].clear();
	}
    return 0;
}

Special Fish

题意:
给你n条鱼(n<=100),每条鱼可以攻击其他鱼,每条鱼有一个价值。一次攻击产生的价值 是两条鱼价值的异或。每条鱼只能攻击一次,并且只能被攻击一次。问攻击后产生的最大总价值。

思路:
很简单的建图,边权取负去跑最小费用最大流。
然后我就一直wa了。
!!问题在于,最小费用最大流的前提是——最大流!!!
这道题我们只想最大化价值,如果强行去跑最大流,可能会使价值更小。那么我们需要从出点 i1 指向 终点T 代表这条鱼可以不攻击。

typedef pair<ll,ll> P;
struct edge{
    int to;
    ll cap,cost;
    int rev;
};
vector<edge>mp[maxn];
int V;
ll h[maxn];
ll dist[maxn];
int prevv[maxn],preve[maxn];
void add(int from,int to,ll cap,ll cost){
    mp[from].push_back({to,cap,cost,mp[to].size()});
    mp[to].push_back({from,0,-cost,mp[from].size()-1});
}
ll MCMF(int s,int t,ll &f){
    ll res=0;
    V++;
    fill(h,h+V,0);
    while(f>0){
        priority_queue<P,vector<P>,greater<P> >q;
        fill(dist,dist+V,INF);
        dist[s]=0;
        q.push({P(0,s)});
        while(!q.empty()){
            P p=q.top();q.pop();
            int v=p.second;
            if(dist[v]<p.first)continue;
            for(int i=0;i<mp[v].size();++i){
                edge &e=mp[v][i];
                if(e.cap>0&&dist[e.to]>dist[v]+e.cost+h[v]- h[e.to]){
                    dist[e.to]=dist[v]+e.cost+h[v]-h[e.to];
                    prevv[e.to]=v;
                    preve[e.to]=i;
                    q.push(P(dist[e.to],e.to));
                }
            }
        }
        if(dist[t]==INF){
            f=INF-f;
            return res;
        }
        for(int v=1;v<=V;++v)h[v]+=dist[v];
        ll d=f;
        for(int v=t;v!=s;v=prevv[v])
            d=min(d,mp[prevv[v]][preve[v]].cap);
        f-=d;
        res+=d*h[t];
        for(int v=t;v!=s;v=prevv[v]){
            edge &e=mp[prevv[v]][preve[v]];
            e.cap-=d;
            mp[v][e.rev].cap+=d;
        }
    }
    return res;
}
int n,m;
int s[maxn];
char ss[maxn];
int main()
{
	while(~scanf("%d",&n)){
		if(!n)break;
		for(int i=1;i<=n;i++)scanf("%d",&s[i]);
		int S=2*n+1,T=2*n+2;
		for(int i=1;i<=n;i++){
			scanf("%s",ss);
			for(int j=0;j<n;j++){
				if(ss[j]=='1'){
					add(i,n+j+1,1,-(s[i]^s[j+1]));
				}
			}
		}
		for(int i=1;i<=n;i++){
			add(S,i,1,0);add(n+i,T,1,0);
			add(i,T,1,0);
		}
		ll ans,f=INF; 
		V=T;
		ans=MCMF(S,T,f);
		printf("%lld\n",-ans);
		for(int i=1;i<=T;i++)mp[i].clear(); 
	}
	return 0;
}

Cordon Bleu

题意:
有n个货物在(xi,yi)的位置,有m个运输员在(xi,yi),餐厅在(x,y)上。一次运输指的是,一个快递员从出发点到货物的位置再到餐厅;一个运输员也可以在到餐厅之后,再出发去货物地点再回到餐厅。
问运输所有货物的最少运输距离(运输过程的 曼哈顿 距离和)
思路:
首先,快递员和货物肯定是属于两个集合,并且集合内不会连边,那么这就是一张二分图。
问题就在于货物有可能是从餐厅出发的。
想法就是我们在快递员的集合中加入(n - 1)个点代表餐厅,那么就保证了至少有一个快递员是从自己这里出发的;
然后去跑一个二分图最大权匹配即可。(边权取负)

ps:
之前都没怎么好好学km,觉得费用流能解决最大权匹配的问题,但是这道题T了。
发现先建费用图,再建二分图会有一定的启发性。
KM板子中使用矩阵存边的,一个代表一个集合,所有直接n,m即可。

const int INF=0x3f3f3f3f;
const int N=4e3;

int n,m,match[N],pre[N];
bool vis[N];
int mp[2004][2004];
int val1[N],val2[N],slack[N];
void bfs(int p)
{
    memset(pre,0,sizeof pre);
    memset(slack,INF,sizeof slack);
    match[0]=p;
    int x=0,nex=0;
    do{
        vis[x]=true;
        int y=match[x],d=INF;
        for(int i=1;i<=m;i++)
        {
            if(!vis[i])
            {
                if(slack[i]>val1[y]+val2[i]-mp[y][i])
                {
                    slack[i]=val1[y]+val2[i]-mp[y][i];
                    pre[i]=x;
                }
                if(slack[i]<d)
                {
                    d=slack[i];
                    nex=i;
                }
            }
        }
        for(int i=0;i<=m;i++)
        {
            if(vis[i])
                val1[match[i]]-=d,val2[i]+=d;
            else
                slack[i]-=d;
        }
        x=nex;
    }while(match[x]);
    while(x)
    {
        match[x]=match[pre[x]];
        x=pre[x];
    }
}

int KM()
{
    memset(match,0,sizeof match);
    memset(val1,0,sizeof val1);
    memset(val2,0,sizeof val2);
    for(int i=1;i<=n;i++)
    {
        memset(vis,false,sizeof vis);
        bfs(i);
    }
    int res=0;
    for(int i=1;i<=m;i++)
        res+=mp[match[i]][i];
    return res;
}

int x1[maxn],y1[maxn];
int x2[maxn],y2[maxn];
int x,y;
int main()
{
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)
			scanf("%d%d",&x1[i],&y1[i]);
		for(int i=1;i<=m;i++)
			scanf("%d%d",&x2[i],&y2[i]);
		scanf("%d%d",&x,&y);
        for(int i=1;i<=m;i++){
        	for(int j=1;j<=n;j++){
        		ll dis=abs(x2[i]-x1[j])+abs(y2[i]-y1[j])+
        			abs(x-x1[j])+abs(y-y1[j]);
				mp[j][i]=-dis;
			}
		}
		for(int i=1;i<n;i++){
			for(int j=1;j<=n;j++){
				ll dis=abs(x-x1[j])+abs(y-y1[j]);
				mp[j][m+i]=-2*dis;
			}
		}
		m=n+m-1;
		printf("%lld\n",-KM());
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值