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