2015-2016 下半學期 第五週 訓練

1、hdu4411

題意:

有N+1個點,每個點與編號大於自己的點之間有一條有權邊(權重通過floyd求得),現有k個人位於0處,要從k個人中選出若干個人遍歷其它點並最終回到0點,使每個點(除0外)都被訪問恰好一次,問最小費用之和爲多少。

題解:

每個點至多走一次,顯然需要把一個點拆成兩個,一個出點一個入點之間費用爲0流量爲1,超級源點拆爲流量爲k費用爲距離的邊,由於原圖無環,所以可以將i和i‘之間的費用設爲-M,流量設爲1,M應該大於源點和匯點間最長鏈的長度。由於每次都找最短路徑,因此這些邊一定會被有限考慮,因此可以保證這些邊恰好走了一次。爲了保證不走增廣後產生的負權反向邊,源點和匯點之間連一條流量爲k費用爲0的點。

代碼:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
#define maxn 1010
#define maxm 20010
const int inf = 0x3f3f3f3f;
struct Nod {
	int b, nxt;
	int cap, cst;
	void init(int b, int nxt, int cap, int cst) {
		this->b = b;
		this->nxt = nxt;
		this->cap = cap;
		this->cst = cst;
	}
};
struct MinCost {
	int E[maxn];
	int n;
	Nod buf[maxm * 2];
	int len;
	int p[maxn];
	void init(int n) {
		this->n = n;
		memset(E, 255, sizeof(E));
		len = 0;
	}
	void addCap(int a, int b, int cap, int cst) {
	//	printf("%d %d %d\n",a,b,cst);
		buf[len].init(b, E[a], cap, cst);
		E[a] = len++;
		buf[len].init(a, E[b], 0, -cst);
		E[b] = len++;
	}
	bool spfa(int source, int sink) {
		static queue<int> q;
		static int d[maxn];
		memset(d, 63, sizeof(d));
		memset(p, 255, sizeof(p));
		d[source] = 0;
		q.push(source);
		int u, v;
		while (!q.empty()) {
			u = q.front();
			q.pop();
			for (int i = E[u]; i != -1; i = buf[i].nxt) {
				v = buf[i].b;
				if (buf[i].cap > 0 && d[u] + buf[i].cst < d[v]) {
					d[v] = d[u] + buf[i].cst;
					p[v] = i;
					q.push(v);
				}
			}
		}
		return d[sink] != inf;
	}
	int solve(int source, int sink) {
		int minCost = 0, maxFlow = 0;//需要maxFlow的話,想辦法返回
		while (spfa(source, sink)) {
			int neck = inf;
			for (int t = p[sink]; t != -1; t = p[buf[t ^ 1].b])//buf[t^壹].b是父節點
				neck = min(neck, buf[t].cap);
			maxFlow += neck;
			for (int t = p[sink]; t != -1; t = p[buf[t ^ 1].b]) {
				buf[t].cap -= neck;
				//printf("%d\n",buf[t].b);
				buf[t ^ 1].cap += neck;
				minCost += buf[t].cst * neck;
			}
			//printf("-----\n");
		}
		return minCost;
	}
} mc;
int map[110][110],n;
void floyd() {
	for (int k =0; k <= n; k++)
		for (int i =0; i <= n; i++)
			for (int j=0; j <= n; j++){
				if (map[i][k] + map[k][j] < map[i][j])
					map[i][j] = map[i][k]+map[k][j];
			}
}
int main() {
	int m, k, a, b, c;
	while (scanf("%d%d%d",&n,&m,&k)&&n) {
		for (int i = 0; i <= n; i++)
			for (int j = 0;j<= n; j++)
				map[i][j] = inf;
		while (m--) {
			scanf("%d%d%d", &a, &b, &c);
			map[b][a]=map[a][b] = min(map[a][b],c);
		}
		floyd();
		mc.init(n * 2 + 3);
		mc.addCap(0, n * 2 + 1, k, 0);
		mc.addCap(n*2+1,n*2+2,k,0);
		int temp=1<<23;
		for (int i = 1; i <= n; i++) {
			mc.addCap(i, i + n,1,-temp);
			mc.addCap(n*2+1,i,1,map[i][0]);
			mc.addCap(i+n, n*2+2,1,map[i][0]);
			for (int j = i+1;j<=n;j++)
				mc.addCap(i+n,j,1,map[i][j]);
		}
		printf("%d\n",mc.solve(0,n*2+2)+temp*n);
	}
	return 0;
}

2、hdu4971

題意:

n(n <= 20)個項目,m(m <= 50)個技術問題,做完一個項目可以有收益profit (<= 1000),做完一個項目必須解決相應的技術問題,解決一個技術問題需要付出cost ( <= 1000),技術問題之間有先後依賴關係,求最大收益。

題解:

每個點有權值,點之間存在依賴關係,所以是最大權閉合圖。對於最大權閉合圖我們的解決方法一般是,從S向所有點權爲正的點連邊,邊權爲點權,從所有點權爲負的點向T連邊,邊權爲點權的絕對值,原圖中的邊量爲inf。

然後我們花幾個圖可以發現 所有點權爲正的點權和-最小割=最大權,然後轉換爲最大流問題。

代碼:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<queue>
#include<vector>
#include<set>
#include<stack>
#include<map>
#include<ctime>
#include<bitset>
#define LL long  long
#define db double
#define EPS 1e-15
#define inf 1e10

using namespace std;

const int maxn = 100;
const int INF = 0x3f3f3f3f;
struct arc{
    int to,flow,next;
    arc(int x = 0,int y = 0,int z = -1){
        to = x;
        flow = y;
        next = z;
    }
}e[maxn*maxn];
int head[maxn],d[maxn],cur[maxn],tot,S,T;
void add(int u,int v,int flow){
    e[tot] = arc(v,flow,head[u]);
    head[u] = tot++;
    e[tot] = arc(u,0,head[v]);
    head[v] = tot++;
}
bool bfs(){
    queue<int>q;
    memset(d,-1,sizeof d);
    d[S] = 0;
    q.push(S);
    while(!q.empty()){
        int u = q.front();
        q.pop();
        for(int i = head[u]; ~i; i = e[i].next){
            if(e[i].flow && d[e[i].to] == -1){
                d[e[i].to] = d[u] + 1;
                q.push(e[i].to);
            }
        }
    }
    return d[T] > -1;
}
int dfs(int u,int low){
    if(u == T) return low;
    int tmp = 0,a;
    for(int &i = cur[u]; ~i; i = e[i].next){
        if(e[i].flow && d[e[i].to] == d[u]+1&&(a=dfs(e[i].to,min(e[i].flow,low)))){
            e[i].flow -= a;
            low -= a;
            e[i^1].flow += a;
            tmp += a;
            if(!low) break;
        }
    }
    if(!tmp) d[u] = -1;
    return tmp;
}
int dinic(){
    int ret = 0;
    while(bfs()){
        memcpy(cur,head,sizeof head);
        ret += dfs(S,INF);
    }
    return ret;
}
int main(){
    int Ts,n,m,u,v,w,k,ret,cs = 1;
    scanf("%d",&Ts);
    while(Ts--){
        memset(head,-1,sizeof head);
        scanf("%d %d",&n,&m);
        tot = ret = S = 0;
        T = n + m + 1;
        for(int i = 1; i <= n; ++i){
            scanf("%d",&w);
            add(S,i,w);
            ret += w;
        }
        for(int i = 1; i <= m; ++i){
            scanf("%d",&w);
            add(i+n,T,w);
        }
        for(int i = 1; i <= n; ++i){
            scanf("%d",&k);
            while(k--){
                scanf("%d",&u);
                add(i,u + n + 1,INF);
            }
        }
        for(int i = 1; i <= m; ++i)
        for(int j = 1; j <= m; ++j){
            scanf("%d",&w);
            if(w) add(i+n,j+n,INF);
        }
        printf("Case #%d: %d\n",cs++,ret - dinic());
    }
    return 0;
}

3、poj1087

題意:

一堆插頭,一堆用電器,一堆插座,一堆轉換器,問你最多能滿足幾個。

題解:

雖然是網絡流專題裏的,我用二分圖匹配做的。用電器一個集合,插座一個集合,轉換器負責連邊,因爲可能存在鬼畜的轉換器,所以floyd傳遞一下閉包,然後跑二分圖最大匹配。

網上的網絡流題解那個反向邊連法我沒看懂。

代碼:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
#include <set>
#include <stack>
#include <map>
#include <ctime>
#include <bitset>
#define LL long  long
#define db double
#define EPS 1e-15
#define inf 1e10

using namespace std;

const int MAXN=200+100;
int n,m,k,cnt,edge_cnt;
char str[25],s[5],s1[5];
int vis[2*MAXN],a[MAXN],head[2*MAXN],link[2*MAXN],d[2*MAXN][2*MAXN];
map<string,int>mp;
struct Edge{
    int v;
    int next;
}edge[MAXN*MAXN];
void init(){
    edge_cnt=0;
    mp.clear();
    memset(head,-1,sizeof(head));
    memset(link,-1,sizeof(link));
    memset(vis,0,sizeof(vis));
    memset(d,0,sizeof(d));
}
void floyd(){
    for(int k=1;k<=m+cnt;k++)
        for(int i=1;i<=m+cnt;i++)
            for(int j=1;j<=m+cnt;j++)
                if(d[i][k] && d[k][j] && !d[i][j])
                    d[i][j]=1;
}
void addedge(int u,int v){
    edge[edge_cnt].v=v;
    edge[edge_cnt].next=head[u];
    head[u]=edge_cnt++;
}
int path(int u){
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
        if(vis[v])
            continue;
        vis[v]=1;
        if(link[v]==-1 || path(link[v])){
            link[v]=u;
            return 1;
        }
    }
    return 0;
}
int main(){
    while(~scanf("%d",&n)){
        init();
        for(int i=1;i<=n;i++){
            scanf("%s",s);
            mp[s]=i;
        }
        cnt=n;
        scanf("%d",&m);
        for(int i=1;i<=m;i++){
            scanf("%s%s",str,s);
            if(!mp[s])
                mp[s]=++cnt;
           d[i][m+mp[s]]=1;
        }
        scanf("%d",&k);
        for(int i=0;i<k;i++){
            scanf("%s%s",s,s1);
            if(!mp[s])
                mp[s]=++cnt;
            if(!mp[s1])
                mp[s1]=++cnt;
            d[m+mp[s]][m+mp[s1]]=1;
        }
        floyd();
        for(int i=1;i<=m;i++)
            for(int j=m+1;j<=m+n;j++)
                if(d[i][j])
                    addedge(i,j);
        int res=0;
        for(int i=1;i<=m;i++){
            memset(vis,0,sizeof(vis));
            res+=path(i);
        }
        printf("%d\n",m-res);
    }
    return 0;
}

4、zoj2314

題意:

給n個點,及m根pipe,每根pipe用來流躺液體的,單向的,每時每刻每根pipe流進來的物質要等於流出去的物質,要使得m條pipe組成一個循環體,裏面流淌物質。並且滿足每根pipe一定的流量限制,範圍爲[Li,Ri].即要滿足每時刻流進來的不能超過Ri(最大流問題),同時最小不能低於Li。

題解:

令每一個點流進來的流=流出去的流,對於每一個點i,令Mi= sum(i點所有流進來的下界流)– sum(i點所有流出去的下界流)

如果Mi大於0,代表此點必須還要流出去Mi的自由流,那麼我們從源點連一條Mi的邊到該點。

如果Mi小於0,代表此點必須還要流進來Mi的自由流,那麼我們從該點連一條Mi的邊到匯點。

如果求S->T的最大流,看是否滿流(S的相鄰邊都流滿)。滿流則有解,否則無解

代碼:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
#include <set>
#include <stack>
#include <map>
#include <ctime>
#include <bitset>
#define LL long  long
#define db double
#define EPS 1e-15
#define inf 1e10

using namespace std;

int cnt;
struct edge{
	int to,w,nxt;
}e[100005];
int head[205],cur[205],in[205],lv[205];
int q[205],n,m,low[100005];
const int T=201;
bool bfs(){
	for (int i=1;i<=T;i++) lv[i]=-1;
	int st=0,ed=1;
	q[0]=0,lv[0]=0;
	while (st!=ed){
		int now=q[st];st++;
		for (int i=head[now];i!=-1;i=e[i].nxt)
			if (e[i].w && lv[e[i].to]==-1){
				lv[e[i].to]=lv[now]+1;
				q[ed++]=e[i].to;
			}
	}
	if (lv[T]==-1) return 0;
	return 1;
}
int dfs(int x,int flow){
	if (x==T) return flow;
	int used=0,w;
	for (int i=cur[x];i!=-1;i=e[i].nxt)
		if (lv[e[i].to]==lv[x]+1){
			w=flow-used;
			w=dfs(e[i].to,min(e[i].w,w));
			e[i].w-=w; e[i^1].w+=w;
			if (e[i].w) cur[x]=i;
			used+=w;
			if (used==flow) return flow;
		}
	if (!used) lv[x]=1;
	return used;
}
void dinic(){
	while (bfs()){
		for (int i=0;i<=T;i++)
			cur[i]=head[i];
		dfs(0,inf);
	}
}
void insert(int u,int v,int w){
	e[++cnt].to=v, e[cnt].w=w;
	e[cnt].nxt=head[u], head[u]=cnt;

	e[++cnt].to=u, e[cnt].w=0;
	e[cnt].nxt=head[v], head[v]=cnt;
}
void build(){
	for (int i=1;i<=n;i++)
		if (in[i]>0) insert(0,i,in[i]);
		else insert(i,T,-in[i]);
}
bool judge(){
	for (int i=head[0];i!=-1;i=e[i].nxt)
		if (e[i].w) return 0;
	return 1;
}
int main(){
	while (scanf("%d%d",&n,&m)!=EOF){
		cnt=1;
		memset(head,-1,sizeof(head));
		memset(in,0,sizeof(in));
		for (int i=1;i<=m;i++){
			int u,v,w;
			scanf("%d%d%d%d",&u,&v,&low[i],&w);
			in[u]-=low[i],in[v]+=low[i];
			insert(u,v,w-low[i]);
		}
		build();
		dinic();
		if (!judge()) printf("NO\n");
		else {
			printf("YES\n");
			for (int i=1;i<=m;i++)
				printf("%d\n",e[(i<<1)^1].w+low[i]);
		}
	}
	return 0;
}

5、nefu500

題意:中文題。

題解:

題目問你最大值最小,顯然是二分,二分邊權最大值,如果小於就連邊,跑最大流,如果慢流就說明可以。

代碼:

瑪德re是以爲你cnt每次二分忘記初始化了,WA是因沒有保留那個最小的cnt1。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<queue>
#include<vector>
#include<set>
#include<stack>
#include<map>
#include<ctime>
#include<bitset>
#define LL long  long
#define db double
#define EPS 1e-15
#define inf 1e9

using namespace std;

struct edges{
    int u,to,w,nxt;
}e[200005];
int cnt=0,T,S,n,m;
int head[200005],lv[200005],cur[200005];
void init(int nn){
    S=1,T=nn+1;
    memset(head,-1,sizeof(head));
}
void add(int u,int v,int w){
    e[cnt].to=v, e[cnt].w=w;
    e[cnt].nxt=head[u], head[u]=cnt++;

    e[cnt].to=u, e[cnt].w=0;
    e[cnt].nxt=head[v], head[v]=cnt++;
}
int q[200005];
bool bfs(){
    for (int i=0;i<=T;i++) lv[i]=-1;
    int st=0,ed=0;
    lv[q[ed++]=1]=0;
    while (st!=ed){
        int now=q[st];st++;
        for (int i=head[now];i!=-1;i=e[i].nxt)
            if (e[i].w && lv[e[i].to]==-1){
                lv[e[i].to]=lv[now]+1;
                q[ed++]=e[i].to;
                if (e[i].to==T) return 1;
            }
    }
    return 0;
}

int dfs(int x,int flow){
    if (x==T) return flow;
    int used=0,w;
    for (int &i=cur[x],w;i!=-1;i=e[i].nxt)
        if(e[i].w && lv[e[i].to]==lv[x]+1 && (used=dfs(e[i].to,min(flow,e[i].w)))>0){
            e[i].w-=used;
            e[i^1].w+=used;
            return used;
        }
    return 0;

}
int dinic(){
    int ret=0,delta;
    while (bfs()){
        for (int i=0;i<=T;i++)
            cur[i]=head[i];
        while(delta=dfs(1,inf)) ret+=delta;
    }
    return ret;
}
int a[200005],sum;
struct de{
    int u,v,w,lim;
}node[200005];
int main(){
    while (scanf("%d%d",&n,&m)!=EOF){
        int sum=0;
        for (int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            sum+=a[i];
        }
        int maxx=0;
        for (int i=1;i<=m;i++){
            scanf("%d%d%d%d",&node[i].u,&node[i].v,&node[i].w,&node[i].lim);
            maxx=max(maxx,node[i].w);
        }
        int l=0,r=maxx+1,flag=0;
        int cnt1=inf;
        while (l<=r) {
            //puts("yes");
            cnt=0;
            int mid=(l+r)/2;
            init(n);
            for (int i=1;i<=m;i++){
                if (node[i].w<=mid)
                    add(node[i].u,node[i].v,node[i].lim);
            }
            for (int i=1;i<=n;i++){
                if (a[i]==0) continue;
                else add(i,T,a[i]);
            }
            int ans=dinic();
            if (ans==sum){
                flag=1;
                cnt1=min(mid,cnt1);
                r=mid-1;
            }
            else l=mid+1;
        }
        if (flag) printf("%d\n",cnt1);
        else printf("-1\n");
    }
    return 0;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章