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;
}



你可能感兴趣的:(2015-2016 下半学期 第五周 训练)