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; }
题意:
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; }
题意:
一堆插头,一堆用电器,一堆插座,一堆转换器,问你最多能满足几个。
题解:
虽然是网络流专题里的,我用二分图匹配做的。用电器一个集合,插座一个集合,转换器负责连边,因为可能存在鬼畜的转换器,所以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; }
题意:
给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; }