------------------------------------------------------------------------------------
------------------------------------------------------------------------------------
开坑了...中考前看看能不能填完(总感觉会TJ...QwQ) TJ啦23333333
1.飞行员配对问题(二分图最大匹配
裸匈牙利。
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> using namespace std; struct poi{int too,pre;}e[20000]; int link[210],last[210],v[210],x,y,n,m,tot,t,ans; void read(int &k) { k=0;int f=1;char c=getchar(); while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar(); while(c<='9'&&c>='0')k=k*10+c-'0',c=getchar(); k*=f; } void add(int x,int y){e[++tot].too=y;e[tot].pre=last[x];last[x]=tot;} bool dfs(int x) { for(int i=last[x],too=e[i].too;i;i=e[i].pre,too=e[i].too) if(v[too]!=t) { v[too]=t; if((!link[too])||dfs(link[too])) { link[too]=x; return 1; } } return 0; } int main() { read(n);read(m); read(x);read(y); while(x!=-1&&y!=-1) { add(y,x); read(x);read(y); } for(int i=n+1;i<=n+m+1;i++) { t++; if(dfs(i))ans++; } printf("%d ",ans); for(int i=1;i<=n;i++) if(link[i])printf("%d %d ",i,link[i]); }
2.太空飞行计划问题(最大权闭合图△
题目大意:n个实验,m个仪器,给定实验需要的仪器(一个实验可能用多个仪器,不同实验使用的仪器可能有重复)和实验获利,仪器使用费用,求最大利润并输出方案。
有向图,点权有正有负,有向边u->v表示选了u必须选v,选一些点使权值最大,就是最大权闭合图辣。
建图:源点往所有实验连获利为权值的边,实验往所需仪器连inf的边,仪器往汇点连费用的边,都是正权,跑最大流,答案为实验总获利-最大流。怎么证明呢?
设实验为A集合,仪器为B集合,那我们的答案就是【A集合中所选的点权-B集合中所选的点权】可以转化成【A集合总点权-(A集合中没有选的点权+B集合中所选的点权)】,而其中的【A集合中没有选的点权+B集合中所选的点权】就是一个割,我们要让答案最大,所以就要让这个割最小,于是就把问题转化成了最小割,最小割集流量之和==最大流,于是答案就是A集合总点权-最大流即实验总获利-最大流。
求方案的话...为什么所有题解都是dinic,ISAP没人权啊?自己yy了一个枚举的做法,可是这题输入不友好,先留坑以后调完输出再交交看。
------------------------------------------------------------------------------------
填坑啦!
学习了一波别人的读入...(感觉这题的读入有省选难度(雾),为什么回车前还有空格啊淦!)
果然自己YY的对啦。首先最小割一定是简单割,也就是割集和S与T相连,因为实验和仪器连边为inf,显然不可能割这条,那么这题求方案就是求S集和T集。我们可以发现如果要选择一个仪器那么仪器到T的边必定满流,否则做这个实验一定会亏钱,就是仪器到T的边满流的就属于T集,那所需的所有仪器在T集里的实验就属于S集辣。求是否满流就把这条边删掉跑最大流看看【未删边的最大流-删边的最大流】是否等于这条边的边权,如果是的话就说明这条边满流。我一开始一直不知道为什么不能直接判断g[u][v]是否==0,写了个对拍才知道...因为g[u][v]==0并不代表这条边一定在最大流中,最大流可能有多个,而如果这条边不是一定在最大流中说明不是一定要选这个仪器或者说选了其实会亏钱,因为一个实验要用到多个仪器,必须这个实验的所有仪器都满流才能选这个实验和这些仪器,否则会亏钱,因为一个实验的获利可以高于它所需要的某一个仪器的费用,然后让这条边满流,如果直接判g[u][v]==0的话会错误的选上这个仪器。
ISAP:
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<vector> const int inf=2147483647; using namespace std; vector <int> req[120]; int g[120][120],G[120][120],dis[120],vd[120],s[120],t[120],cnt,cnt2,m,n,x,y,z,sum,ulen,num; long long ans,summ; bool v[1000],vv[1000]; char c[1000][10010]; int dfs(int x,int flow) { int ret=0; if(x==sum)return flow; for(int i=0;i<=sum;i++) if(g[x][i]&&dis[x]==dis[i]+1) { int t=dfs(i,min(flow-ret,g[x][i])); g[x][i]-=t;g[i][x]+=t;ret+=t; if(ret==flow)return ret; } if(dis[0]>sum)return ret; if(!--vd[dis[x]])dis[0]=sum+1; vd[++dis[x]]++; return ret; } int main() { scanf("%d%d",&m,&n); sum=n+m+1; for(int i=1;i<=m;i++) { scanf("%d",&z); summ+=z;g[0][i]=z; cin.getline(c[i],10000); ulen=0; while(sscanf(c[i]+ulen,"%d",&num)==1) { req[i].push_back(num); if(num==0)ulen++; else while(num) { num/=10; ulen++; } ulen++; } for(int j=0;j<req[i].size();j++) g[i][req[i][j]+m]=inf; } for(int i=1;i<=n;i++)scanf("%d",&z),g[i+m][sum]=z; memcpy(G,g,sizeof(g)); while(dis[0]<=sum)ans+=dfs(0,inf); for(int i=1;i<=n;i++) { int anss=0; memcpy(g,G,sizeof(G)); memset(dis,0,sizeof(dis)); memset(vd,0,sizeof(vd)); g[i+m][sum]=0; while(dis[0]<=sum)anss+=dfs(0,inf); if(ans-anss==G[i+m][sum])v[i]=1; } for(int i=1;i<=m;i++) { vv[i]=1; for(int j=0;j<req[i].size();j++) if(!v[req[i][j]]) { vv[i]=0;break; } } for(int i=1;i<=m;i++)if(vv[i])printf("%d ",i);printf(" "); for(int i=1;i<=n;i++)if(v[i])printf("%d ",i);printf(" "); printf("%d ",summ-ans); }
UPD:
Dinic(bzoj1497)(同样的题):
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> using namespace std; const int inf=1000000000,maxn=60010; struct poi{int too,pre,cf,c;}e[500010]; int n,m,tot,sum,sumz,ans,x,y,z,front,rear,cnt; int h[maxn],v[maxn],pre[maxn],last[maxn],dis[maxn],cur[maxn]; void read(int &k) { k=0;int f=1;char c=getchar(); while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar(); while(c<='9'&&c>='0')k=k*10+c-'0',c=getchar(); k*=f; } void add(int x,int y,int z) { e[++tot].too=y;e[tot].c=e[tot].cf=z;e[tot].pre=last[x];last[x]=tot; e[++tot].too=x;e[tot].pre=last[y];last[y]=tot; } bool bfs(int x) { for(int i=0;i<=sum;i++)pre[i]=-1,v[i]=0,dis[i]=-1; v[x]=1;h[1]=x;front=0;rear=1; while(front!=rear) { int now=h[++front]; if(front==maxn)front=-1; for(int i=last[now],too=e[i].too;i;i=e[i].pre,too=e[i].too) if(!v[too]&&e[i].cf) { v[too]=1;h[++rear]=too;pre[too]=i;dis[too]=dis[now]+1; if(too==sum)return 1; if(rear==maxn)rear=-1; } } return 0; } int dfs(int x,int f) { int tmp,flow=0; if(x==sum)return f; for(int &i=cur[x];i;i=e[i].pre) { int y=e[i].too; if(e[i].cf&&dis[y]==dis[x]+1) { tmp=dfs(y,min(f-flow,e[i].cf)); e[i].cf-=tmp;e[i^1].cf+=tmp;flow+=tmp; if(flow==f)return f; } } return flow; } void dinic() { while(bfs(0)) { for(int i=0;i<=sum;i++)cur[i]=last[i]; ans+=dfs(0,inf); } } int main() { tot=1; read(n);read(m);sum=n+m+1; for(int i=1;i<=n;i++)read(x),add(i+m,sum,x); for(int i=1;i<=m;i++) { read(x);read(y);read(z);sumz+=z; add(0,i,z);add(i,x+m,inf);add(i,y+m,inf); } dinic(); printf("%d ",sumz-ans); }
3.最小路径覆盖问题(最小路径覆盖△
题目大意:n个点的DAG图,求最少几条路径可以覆盖所有的点并输出路径。
对于有向边u->v把u拆成u和u',v拆成v和v',u向v'连边,跑二分图最大匹配,n-最大匹配数即为答案,易证。
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> using namespace std; struct poi{int pre,too;}e[20000]; int link[1000],v[1000],n,m,ans,ru[1000],tot,last[1000],t,x,y; void read(int &k) { k=0;int f=1;char c=getchar(); while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar(); while(c<='9'&&c>='0')k=k*10+c-'0',c=getchar(); k*=f; } void add(int x,int y){e[++tot].too=y;e[tot].pre=last[x];last[x]=tot;} bool dfs(int x) { for(int i=last[x];i;i=e[i].pre) if(v[e[i].too]!=t) { v[e[i].too]=t; if((!link[e[i].too])||dfs(link[e[i].too])) { link[e[i].too]=x; return 1; } } return 0; } void dfs2(int x) { printf("%d ",x); for(int i=last[x];i;i=e[i].pre) if(link[e[i].too]==x) { dfs2(e[i].too-n); break; } } int main() { read(n);read(m);ans=n; for(int i=1;i<=m;i++)read(x),read(y),add(x,y+n); for(int i=1;i<=n;i++) { t++; ans-=dfs(i); } for(int i=1;i<=n;i++) if(!link[i+n])dfs2(i),printf(" "); printf("%d ",ans); }
4.魔术球问题(最小路径覆盖△
题目大意:n个栈,要1,2,3,...依次往栈里塞数,一个栈里相邻的数之和必须是完全平方数,问最多塞几个数并输出方案。
这题其实是上一题的强化版...一个栈里一串数可以抽象成一条路径,那就可以把问题转换成求最小路径数不大于n的最多有几个数了。枚举答案(这个最大只有1600左右),枚举到哪一个数就再枚举前面的数,把和是完全平方数的两个数连边,然后更新最小路径覆盖的答案,显然只需要再dfs一次新加进来的数就行了,最后最小路径数大于n就break,break时的最小路径数-1即为答案,求方案的话和上题一样。
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<cmath> using namespace std; struct poi{int pre,too;}e[20000]; int link[3000],v[2000],n,m,ans,tot,last[2000],t,x,y,cnt; void read(int &k) { k=0;int f=1;char c=getchar(); while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar(); while(c<='9'&&c>='0')k=k*10+c-'0',c=getchar(); k*=f; } void add(int x,int y){e[++tot].too=y;e[tot].pre=last[x];last[x]=tot;} bool dfs(int x) { for(int i=last[x];i;i=e[i].pre) if(v[e[i].too]!=t&&e[i].too<=cnt) { v[e[i].too]=t; if((!link[e[i].too])||dfs(link[e[i].too])) { link[e[i].too]=x; return 1; } } return 0; } void dfs2(int x) { printf("%d ",x); for(int i=last[x];i;i=e[i].pre) if(link[e[i].too]==x&&cnt>=e[i].too) { dfs2(e[i].too); break; } } int main() { read(n); cnt=1; int ans=dfs(1); while(1) { cnt++; for(int i=1;i<cnt;i++) { int x=(int)sqrt(i+cnt); if(x*x==(i+cnt))add(cnt,i); } t++;ans+=dfs(cnt); if(cnt-ans>n)break; } printf("%d ",--cnt); ans=0; memset(link,0,sizeof(link)); for(int i=1;i<=cnt;i++)t++,ans+=dfs(i); for(int i=1;i<=cnt;i++) if(!link[i])dfs2(i),printf(" "); }
5.圆桌问题(最大流
题目大意:m个单位,每个单位有ri个人,要坐n张桌子,每张可以坐ci个人,每张桌子不能坐同个单位的人,输出方案。
一眼题,1A真酸爽...
S连m个单位容量ri,每个单位连每个桌子容量1,每个桌子连T容量ri,最大流。
方案就枚举单位和桌子看看这条边是否满流。
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> using namespace std; int dis[100010],vd[100010],g[1000][1000],sum,n,m,x,summ,ans; void read(int &k) { k=0;int f=1;char c=getchar(); while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar(); while(c<='9'&&c>='0')k=k*10+c-'0',c=getchar(); k*=f; } int dfs(int x,int flow) { int ret=0; if(x==sum)return flow; for(int i=0;i<=sum;i++) if(g[x][i]&&dis[x]==dis[i]+1) { int t=dfs(i,min(flow-ret,g[x][i])); g[x][i]-=t;g[i][x]+=t;ret+=t; if(ret==flow)return ret; } if(dis[0]>sum)return ret; if(!--vd[dis[x]])dis[0]=sum+1; vd[++dis[x]]++; return ret; } int main() { read(m);read(n);sum=n+m+1; for(int i=1;i<=m;i++)read(x),g[0][i]=x,summ+=x; for(int i=1;i<=n;i++)read(x),g[i+m][sum]=x; for(int i=1;i<=m;i++)for(int j=1;j<=n;j++)g[i][j+m]=1; while(dis[0]<=sum)ans+=dfs(0,2147483647); if(ans==summ) { printf("1 "); for(int i=1;i<=m;i++) { for(int j=1;j<=n;j++) if(!g[i][j+m])printf("%d ",j); printf(" "); } }else printf("0 "); }
6.最长递增子序列问题(最多不相交路径(最大流△
题目大意:①求一个序列的最长不下降子序列长度k,②求最多能取出(取出的不能再用)多少个长度为n的不下降子序列,③如果a1和an能取出多次求最多可以取出多少个。
这题PowerOJ上WA了但是其他OJ都过了,而且我把黄学长的程序交上去也WA了...干脆当自己过了。
第一问:DP求出每一位后面的最长不下降子序列f[i],懒得写nlogn了。
第二问:i拆成i和i',i连i'容量为1。若f[i]==k,S连i容量为1。若f[i]==1,i'连T容量为1。若i<j&&a[i]<=a[j]&&f[i]==f[j]+1,i'连j容量为1。最大流。
第三问:符合条件的1和n,把和它取出次数有关的边的容量改为inf,跑最大流。
注意:对于一个最长不下降子序列长度为1的序列,第三问答案是序列长度而不是inf。
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> using namespace std; const int inf=233333333,maxn=1010; struct poi{int c,f,cf;}g[maxn][maxn],G[maxn][maxn]; struct poii{int too,pre;}e[100010]; int n,a[maxn],f[maxn],last[maxn],h[10100],pre[maxn],ans,sum,anss,tot; bool v[maxn]; void read(int &k) { k=0;int f=1;char c=getchar(); while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar(); while(c<='9'&&c>='0')k=k*10+c-'0',c=getchar(); k*=f; } void add(int x,int y) {e[++tot].too=y;e[tot].pre=last[x];last[x]=tot;e[++tot].too=x;e[tot].pre=last[y];last[y]=tot;} void bfs(int s) { int front=0,rear=1; for(int i=0;i<=sum;i++)v[i]=0,pre[i]=-1; v[s]=1;h[1]=s; while(front!=rear) { if(front>1000)front=-1; int now=h[++front]; for(int i=last[now],too=e[i].too;i;i=e[i].pre,too=e[i].too) if(g[now][too].cf&&!v[too]) { pre[too]=now; v[too]=1;rear>1000&&(rear=-1);h[++rear]=too; } } } void dinic(int s,int t) { bfs(s); while(pre[t]!=-1) { int mincf=inf; for(int i=pre[t],j=t;i!=-1;j=i,i=pre[i]) mincf=min(mincf,g[i][j].cf); for(int i=pre[t],j=t;i!=-1;j=i,i=pre[i]) { g[i][j].f+=mincf;g[j][i].f=-g[i][j].f; g[i][j].cf-=mincf;g[j][i].cf+=mincf; } ans+=mincf; bfs(s); } } int main() { read(n); for(int i=1;i<=n;i++)read(a[i]); for(int i=n;i>=1;i--) { int mx=0; for(int j=i+1;j<=n;j++) if(a[i]<=a[j])mx=max(mx,f[j]); f[i]=mx+1; anss=max(anss,f[i]); } printf("%d ",anss); sum=n*2+1; for(int i=1;i<=n;i++) { add(i,i+n); g[i][i+n].c=g[i][i+n].cf=1; if(f[i]==anss)g[0][i].c=g[0][i].cf=1,add(0,i); if(f[i]==1)g[i+n][sum].c=g[i+n][sum].cf=1,add(i+n,sum); for(int j=i+1;j<=n;j++) if(a[i]<=a[j]&&f[i]==f[j]+1)g[i+n][j].c=g[i+n][j].cf=1,add(i+n,j); } memcpy(G,g,sizeof(g)); dinic(0,sum); printf("%d ",ans); if(anss==1)printf("%d ",n); else { memcpy(g,G,sizeof(G)); ans=0; if(f[1]==anss)g[0][1].c=g[0][1].cf=g[1][1+n].c=g[1][1+n].cf=inf,add(0,1),add(1,n+1); if(f[1]==1)g[1][1+n].c=g[1][1+n].cf=g[1+n][sum].c=g[1+n][sum].cf=inf,add(1,n+1),add(n+1,sum); if(f[n]==anss)g[0][n].c=g[0][n].cf=g[n][n+n].c=g[n][n+n].cf=inf,add(0,n),add(n,n*2); if(f[n]==1)g[n+n][sum].c=g[n+n][sum].cf=g[n][n+n].c=g[n][n+n].cf=inf,add(n*2,sum),add(n,n*2); dinic(0,sum); printf("%d ",ans); } }
7.试题库问题(最大流
题目大意:一张试卷需要k个类型的题,每个类型需要ai道题,题库里有n道题,一道题可以属于多个类型,输出一个出题的方案。
S连k个类型容量为ai,k个类型连可以属于这个类型的题目容量为1,每个题目连T容量为1,最大流,方案判满流。
这题的数据较大,我估计ISAP过不去,因为在二分图里dinic效率高很多,于是我学习了一波dinic... woc,这不就是费用流把spfa改成bfs么,于是我直接把自己的费用流改过来了,然后一看其他博客的代码,发现我改出来的dinic把他们的dfs都去掉了,在bfs时记录路径直接一路扫过来,这样也吼兹磁啊,而且如果记得dinic费用流就不会忘,记得费用流dinic就不会忘(所以忘了一个两个就都忘了?)。
UPD:mdzz原来费用流改过来的dinic慢的要死,直接被bzoj1497卡成傻逼。。。这玩意儿每次只会跑一条增广路,但是dinic建完图会把能跑的都跑了,所以费用流改过来的dinic建了无数次图,但dinic只建了几次。。。顺便学习了一波当前弧优化,就是比原来的程序多加了个&,但是快了好多2333。因为bzoj1497和费用流24题第2差不多,就把代码丢上面了。
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> const int maxn=2000,inf=2147483647; struct poi{int c,f,cf;}g[maxn][maxn]; int h[1010],pre[maxn],ans,n,m,x,y,z,k,sum,p; char ch[maxn][maxn]; bool v[maxn]; using namespace std; void read(int &k) { k=0;int f=1;char c=getchar(); while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar(); while(c<='9'&&c>='0')k=k*10+c-'0',c=getchar(); k*=f; } void bfs(int s) { int front=0,rear=1; for(int i=0;i<=sum;i++)v[i]=0,pre[i]=-1; v[s]=1;h[1]=s; while(front!=rear) { if(front>1000)front=-1; int now=h[++front]; for(int i=0;i<=sum;i++) if(g[now][i].cf&&!v[i]) { pre[i]=now; v[i]=1;rear>1000&&(rear=-1);h[++rear]=i; } } } void dinic(int s,int t) { bfs(s); while(pre[t]!=-1) { int mincf=inf; for(int i=pre[t],j=t;i!=-1;j=i,i=pre[i]) mincf=min(mincf,g[i][j].cf); for(int i=pre[t],j=t;i!=-1;j=i,i=pre[i]) { g[i][j].f+=mincf;g[j][i].f=-g[i][j].f; g[i][j].cf-=mincf;g[j][i].cf+=mincf; } ans+=mincf; bfs(s); } } int main() { read(k);read(n); sum=n+k+1; for(int i=1;i<=k;i++)read(x),g[0][i].c=g[0][i].cf=x,m+=x; for(int i=1;i<=n;i++) { g[i+k][sum].c=g[i+k][sum].cf=1; read(p); for(int j=1;j<=p;j++) read(x),g[x][i+k].c=g[x][i+k].cf=1; } dinic(0,sum); if(ans<m)printf("No Solution!"); else { for(int i=1;i<=k;i++) { printf("%d:",i); for(int j=1;j<=n;j++) if(!g[i][j+k].cf&&g[i][j+k].c)printf(" %d",j); printf(" "); } } }
8.机器人路径规划问题(不可做,先跳
9.方格取数问题(最大独立集△
题目大意:n*m的矩阵取出一些数使得两两不相邻且和最大。
最大独立集=总点权-最小点覆盖=总点权-最小割=总点权-最大流。
染个色可以发现是二分图,于是分成A集和B集,S连A容量为点权,A连B容量inf,B连T容量点权。
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> using namespace std; const int inf=233333333,maxn=100; struct poi{int c,cf,f;}g[maxn][maxn]; int n,m,x,h[1010],pre[maxn],sum,ans,summ; bool v[maxn]; void read(int &k) { k=0;int f=1;char c=getchar(); while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar(); while(c<='9'&&c>='0')k=k*10+c-'0',c=getchar(); k*=f; } void bfs(int s) { int front=0,rear=1; for(int i=0;i<=sum;i++)v[i]=0,pre[i]=-1; v[s]=1;h[1]=s; while(front!=rear) { if(front>1000)front=-1; int now=h[++front]; for(int i=0;i<=sum;i++) if(g[now][i].cf&&!v[i]) { pre[i]=now; v[i]=1;rear>1000&&(rear=-1);h[++rear]=i; } } } void dinic(int s,int t) { bfs(s); while(pre[t]!=-1) { int mincf=inf; for(int i=pre[t],j=t;i!=-1;j=i,i=pre[i]) mincf=min(mincf,g[i][j].cf); for(int i=pre[t],j=t;i!=-1;j=i,i=pre[i]) { g[i][j].f+=mincf;g[j][i].f=-g[i][j].f; g[i][j].cf-=mincf;g[j][i].cf+=mincf; } ans+=mincf; bfs(s); } } int num(int x,int y){return (x-1)*m+y;} int main() { read(n);read(m);sum=n*m+1; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { read(x);summ+=x; if((i+j)&1) { g[0][num(i,j)].c=g[0][num(i,j)].cf=x; if(j-1>0)g[num(i,j)][num(i,j-1)].c=g[num(i,j)][num(i,j-1)].cf=inf; if(i-1>0)g[num(i,j)][num(i-1,j)].c=g[num(i,j)][num(i-1,j)].cf=inf; if(i+1<=n)g[num(i,j)][num(i+1,j)].c=g[num(i,j)][num(i+1,j)].cf=inf; if(j+1<=m)g[num(i,j)][num(i,j+1)].c=g[num(i,j)][num(i,j+1)].cf=inf; } else g[num(i,j)][sum].c=g[num(i,j)][sum].cf=x; } dinic(0,sum); printf("%d ",summ-ans); }
10.餐巾计划问题(费用流△
题目大意:有N天,每天需要ai张餐巾,用完可以送去快洗店洗m天费用f或慢洗店洗n天费用s或者买新的一条费用p,洗完或者买新的才能继续用,问N天的最小费用。
把没洗和需要的分成两个集合A和B,S连Ai容量为ai费用0,Ai连A(i+1)容量为inf,费用为0表示留着餐巾,Ai连B(i+m)容量为inf,费用f表示送快洗店,Ai连B(i+n)费用s表示送慢洗店,S连Bi容量inf费用p表示买新的餐巾,Bi连T容量为ai费用0。跑费用流。
循环队列记得开大点QAQ...
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> const int maxn=2020,inf=1000000000; struct poi{int c,f,cf,v;}g[maxn][maxn]; struct poii{int too,pre;}e[100010]; int h[2010],dist[maxn],pre[maxn],last[maxn],sum,ans,n,m,tot,p,f,nn,s,x; bool v[maxn]; using namespace std; void add(int x,int y){e[++tot].too=y;e[tot].pre=last[x];last[x]=tot;} void read(int &k) { k=0;int f=1;char c=getchar(); while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar(); while(c<='9'&&c>='0')k=k*10+c-'0',c=getchar(); k*=f; } void spfa(int s) { int front=0,rear=1; for(int i=0;i<=sum;i++)dist[i]=inf,v[i]=0,pre[i]=-1; dist[s]=0;v[s]=1;h[1]=s; while(front!=rear) { if(front>2000)front=-1; int now=h[++front]; for(int i=last[now],too=e[i].too;i;i=e[i].pre,too=e[i].too) { if(g[now][too].cf==0)continue; if(g[now][too].v==inf)g[now][too].v=-g[too][now].v; if(dist[too]>dist[now]+g[now][too].v) { dist[too]=dist[now]+g[now][too].v;pre[too]=now; if(!v[too])v[too]=1,rear>2000&&(rear=-1),h[++rear]=too; } } v[now]=0; } } void ford(int s,int t) { spfa(s); while(pre[t]!=-1) { int mincf=inf; for(int i=pre[t],j=t;i!=-1;j=i,i=pre[i]) mincf=min(mincf,g[i][j].cf); ans+=dist[t]*mincf; for(int i=pre[t],j=t;i!=-1;j=i,i=pre[i]) { g[i][j].f+=mincf;g[j][i].f=-g[i][j].f; g[i][j].cf-=mincf;g[j][i].cf+=mincf; } spfa(s); } } void insert(int x,int y,int c,int v) { g[x][y].c=g[x][y].cf=c;g[x][y].v=v; add(x,y);add(y,x); } int main() { read(n);read(p);read(m);read(f);read(nn);read(s);sum=n*2+1; for(int i=0;i<=sum;i++)for(int j=0;j<=sum;j++)g[i][j].v=inf; for(int i=1;i<=n;i++) { read(x); if(i<n)insert(i,i+1,inf,0); if(i+m<=n)insert(i,i+m+n,inf,f); if(i+nn<=n)insert(i,i+nn+n,inf,s); insert(0,i,x,0);insert(0,i+n,inf,p);insert(i+n,sum,x,0); } ford(0,sum); printf("%d ",ans); }
11.航空路线问题(最长不相交路径(最大费用最大流△
题目大意:求两条a->b长度和最长的不相交路径
这题好坑啊...输出方案特别麻烦,写了我一天,最后还一直WA一个点,看题解说是a->b有边的话就可以算两条,改了还是WA一个点,先留坑。
拆点连边为1,对于边u->v,u'连v容量1。跑最大费用最大流。
方案跑一遍dfs,或者把spfa的条件改为满流再跑费用流记录路径也行,但是不能第一次费用流就记录,因为有反向弧会往回跑,记录出来的路径会GG。
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> const int maxn=510,inf=1000000000,mod=19961993; struct poii{int too,pre,c,f,cf,v;}e[100010]; int h[2010],dist[maxn],pre[maxn],last[maxn],ha[19961993],fir[maxn],sec[maxn]; int sum,ans,n,m,tot,anss1,anss2,x,anss,cnt1,cnt2,flag,fflag; bool v[maxn]; char s[maxn][maxn],s1[maxn],s2[maxn]; using namespace std; void add(int x,int y,int c,int v) { e[++tot].too=y;e[tot].pre=last[x];last[x]=tot; e[tot].c=e[tot].cf=c;e[tot].v=v; e[++tot].too=x;e[tot].pre=last[y];last[y]=tot;e[tot].v=-v; } void read(int &k) { k=0;int f=1;char c=getchar(); while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar(); while(c<='9'&&c>='0')k=k*10+c-'0',c=getchar(); k*=f; } void spfa(int s) { int front=0,rear=1; for(int i=0;i<=sum;i++)dist[i]=-inf,v[i]=0,pre[i]=-1; dist[s]=0;v[s]=1;h[1]=s; while(front!=rear) { if(front>2000)front=-1; int now=h[++front]; for(int i=last[now],too=e[i].too;i;i=e[i].pre,too=e[i].too) { if(e[i].cf) if(dist[too]<dist[now]+e[i].v) { dist[too]=dist[now]+e[i].v;pre[too]=i; if(!v[too])v[too]=1,rear>2000&&(rear=-1),h[++rear]=too; } } v[now]=0; } } void ford(int s,int t) { spfa(s); while(pre[t]!=-1) { int mincf=inf,cnt=0;; for(int i=pre[t];i!=-1;i=pre[e[i^1].too]) mincf=min(mincf,e[i].cf); ans+=mincf; anss+=dist[t]; for(int i=pre[t];i!=-1;i=pre[e[i^1].too]) { e[i].f+=mincf;e[i^1].f=-e[i].f; e[i].cf-=mincf;e[i^1].cf+=mincf; } spfa(s); } } int hashh(int x) { int len=strlen(s[x]),sum=0; for(int i=0;i<len;i++) sum=(sum*10+s[x][i])%mod; return sum; } bool dfs(int x,int fa,int t) { if(t==1&&x==n)return 1; if(t==2&&x==n+1)return 1; int num=0,xx=0; if(t==1)num=x+n,xx=x; else num=x-n,xx=x-n; if(xx!=1)v[xx]=v[xx+n]=1; for(int i=last[num];i;i=e[i].pre) { if(e[i].too==xx||e[i].too==xx+n)continue; int cf=t==1?e[i].cf:e[i^1].cf; if(cf==0&&(e[i].too!=fa)&&(!v[e[i].too])) { int next=e[i].too>n?e[i].too-n:e[i].too; if(t==1&&xx==1&&next==n) { fflag=1;continue; } //if(t==2)printf("%d %d ",e[i].too,cf); if(t==1&&next>xx)if(dfs(e[i].too,x,t)) { fir[++cnt1]=xx; return 1; } if(t==2&&next<xx)if(dfs(e[i].too,x,t)) { sec[++cnt2]=xx; return 1; } } } return 0; } int main() { tot=1; read(n);read(m);sum=n*2; for(int i=1;i<=n;i++) { scanf("%s",s[i]); ha[hashh(i)]=i; if(i!=1&&i!=n)add(i,i+n,1,0); else add(i,i+n,2,0); } for(int i=1;i<=m;i++) { scanf("%s%s",s[n+1],s[n+2]); int x=ha[hashh(n+1)],y=ha[hashh(n+2)]; if(x>y)swap(x,y); if(x==1&&y==n)flag=1; x=x+n; add(x,y,1,1); } ford(1,sum); memset(v,0,sizeof(v)); if(ans==2) { printf("%d ",anss); dfs(1,-1,1); dfs(n*2,-1,2); for(int i=cnt1;i>=1;i--)printf("%s ",s[fir[i]]); if(!cnt1&&fflag)printf("%s ",s[1]); for(int i=cnt2;i>=1;i--)printf("%s ",s[sec[i]]); printf("%s ",s[1]); } else if(flag)printf("2 %s %s %s",s[1],s[n],s[1]); else printf("No Solution!"); }
12.软件补丁问题(spfa
题目大意不好说...直接戳链接看题吧。
这为什么会在网络流24题里...
一开始以为是状压,结果发现有后向性不能dp,哇其实写个spfa就可以了?
学会新姿势,“~”是按位取反。这样从a中除去b就简单多啦!a&(~b)
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<queue> using namespace std; const int maxn=250,inf=1000000000,maxnum=2100000; struct poi{int pos,dis;}; priority_queue<poi>q; int n,m,dist[maxnum],f1[maxn],f2[maxn],b1[maxn],b2[maxn],t[maxn]; bool v[maxnum]; char s1[maxn],s2[maxn]; bool operator <(poi a,poi b){return a.dis>b.dis;}; void read(int &k) { k=0;int f=1;char c=getchar(); while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar(); while(c<='9'&&c>='0')k=k*10+c-'0',c=getchar(); k*=f; } void spfa(int x) { for(int i=0;i<=x;i++)dist[i]=inf; v[x]=1;dist[x]=0;q.push((poi){x,0}); while(!q.empty()) { int now=q.top().pos; q.pop(); for(int i=1;i<=m;i++) if(((now&b1[i])==b1[i])&&(!(now&b2[i]))) { int too=now&(~f1[i])|f2[i]; if(dist[too]>dist[now]+t[i]) { dist[too]=dist[now]+t[i]; if(!v[too])v[too]=1,q.push((poi){too,dist[too]}); } } v[now]=0; } } int main() { read(n);read(m); for(int i=1;i<=m;i++) { read(t[i]); scanf("%s%s",s1,s2); for(int j=0;j<n;j++) { if(s1[j]=='+')b1[i]|=(1<<j); if(s1[j]=='-')b2[i]|=(1<<j); } for(int j=0;j<n;j++) { if(s2[j]=='-')f1[i]|=(1<<j); if(s2[j]=='+')f2[i]|=(1<<j); } } spfa((1<<n)-1); if(dist[0]==inf)printf("0"); else printf("%d ",dist[0]); }
13.星际转移问题(分层图最大流△
感觉一个星期没写博客概括能力--,题目大意戳链接看吧。
这题的思路好妙啊,把题目分成d天来处理,把图分成d个层次。
首先0向1连容量为k的边,表示要送走k个人。枚举天数d(这里有个误区,不能二分,因为网络流是一直保持最大流状态,d+1后很快就能跑出新网络的最大流),把每个空间站拆成d个点分别表示第1~d天的空间站,把飞船d-1天停留的空间站(d-1,i)与d天停留的空间站(d,j)连边,容量为hi,每个空间站与自己前一天的空间站连边容量为inf,(d,-1)的最大流为k,就输出d。
写这题GG了好多次,忘记建双向边,下次要注意(其实是模板忘改了)。。。
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> const int maxn=1000,inf=1000000000; struct poii{int too,pre,c,f,cf,x;}e[100010]; int h[1010],pre[maxn],last[maxn],r[maxn][maxn],fa[maxn],hh[maxn],d,ans,n,m,x,y,z,tot,sum,k; char ch[maxn][maxn]; bool v[maxn]; using namespace std; void read(int &k) { k=0;int f=1;char c=getchar(); while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar(); while(c<='9'&&c>='0')k=k*10+c-'0',c=getchar(); k*=f; } void add(int x,int y,int z) { e[++tot].too=y;e[tot].c=e[tot].cf=z;e[tot].pre=last[x];last[x]=tot; e[++tot].too=x;e[tot].c=e[tot].cf=0;e[tot].pre=last[y];last[y]=tot; } void bfs(int s) { int front=0,rear=1; for(int i=0;i<=sum;i++)v[i]=0,pre[i]=-1; v[s]=1;h[1]=s; while(front!=rear) { if(front>1000)front=-1; int now=h[++front]; for(int i=last[now],too=e[i].too;i;i=e[i].pre,too=e[i].too) { if(e[i].cf&&!v[too]) { pre[too]=i; v[too]=1;rear>1000&&(rear=-1);h[++rear]=too; } } } } void dinic(int s,int t) { bfs(s); while(pre[t]!=-1) { int mincf=inf; for(int i=pre[t];i!=-1;i=pre[e[i^1].too]) mincf=min(mincf,e[i].cf); for(int i=pre[t];i!=-1;i=pre[e[i^1].too]) { e[i].f+=mincf;e[i^1].f=-e[i].f; e[i].cf-=mincf;e[i^1].cf+=mincf; } ans+=mincf; bfs(s); } } int gf(int v){return fa[v]==v?v:fa[v]=gf(fa[v]);} int num(int x,int y){return (x)*(n+2)+y;} int main() { tot=1; read(n);read(m);read(k); for(int i=1;i<=n+2;i++)fa[i]=i; for(int i=1;i<=m;i++) { read(hh[i]);read(r[i][0]); for(int j=1;j<=r[i][0];j++) { read(r[i][j]);r[i][j]+=2; if(j!=1)fa[gf(r[i][j])]=gf(r[i][j-1]); } } if(gf(1)!=gf(2)) { printf("0"); return 0; } add(0,2,k); d=-1; while(ans<k) { d++;sum+=n+2; if(d!=0) { for(int i=1;i<=n+2;i++) add(num(d-1,i),num(d,i),inf); for(int i=1;i<=m;i++) add(num(d-1,r[i][(d-1)%(r[i][0])+1]),num(d,r[i][d%(r[i][0])+1]),hh[i]); } dinic(0,1+d*(n+2)); } printf("%d ",d); }
14.孤岛营救问题(spfa+状态压缩
怎么我的网络流里又混进了奇怪的东西...
dist[i][state]表示走到位置i,钥匙的状态为state
总是忘记无解输出-1,下次要注意...
#include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<queue> using namespace std; const int maxn=1000,inf=1000000000; struct poi{int dis,pos,state;}; struct zs{int pre,too;}e[100010]; priority_queue<poi>q; bool operator <(poi a,poi b){return a.dis>b.dis;}; int n,m,p,k,J,mn,tot,x1,y1,x2,y2,z; int last[maxn],g[maxn][maxn],dist[maxn][2100],dxy[5]={0,0,0,-1,+1}; bool v[maxn][2100]; void read(int &k) { k=0;int f=1;char c=getchar(); while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar(); while(c<='9'&&c>='0')k=k*10+c-'0',c=getchar(); k*=f; } void add(int x,int y){e[++tot].too=y;e[tot].pre=last[x];last[x]=tot;} int num(int x,int y){return (x-1)*m+y;} void spfa() { for(int i=1;i<=n*m;i++) for(int j=0;j<=(1<<(J+1))-1;j++) dist[i][j]=inf; int st=0; for(int i=last[1];i;i=e[i].pre)st|=(1<<e[i].too); dist[1][st]=0;v[1][st]=1;q.push((poi){0,1,st}); while(!q.empty()) { poi now=q.top();q.pop(); for(int i=1;i<=4;i++) { int too=now.pos+dxy[i]; if(too>n*m||too<1||((now.pos%m==0)&&dxy[i]==1)||((now.pos%m==1)&&dxy[i]==-1)||g[now.pos][too]==-1)continue; st=now.state;for(int j=last[too];j;j=e[j].pre)st|=(1<<e[j].too); if(dist[too][st]>dist[now.pos][now.state]+1&&((now.state&g[now.pos][too])==g[now.pos][too])) { dist[too][st]=dist[now.pos][now.state]+1; if(!v[too][st]) { v[too][st]=1; q.push((poi){dist[too][now.state],too,st}); } } } v[now.pos][now.state]=0; } } int main() { read(n);read(m);read(p);read(k); dxy[1]=-m;dxy[2]=m; for(int i=1;i<=k;i++) { read(x1);read(y1);read(x2);read(y2);read(z); if(z==0)g[num(x1,y1)][num(x2,y2)]=g[num(x2,y2)][num(x1,y1)]=-1; else g[num(x1,y1)][num(x2,y2)]=g[num(x2,y2)][num(x1,y1)]=1<<z; } read(J); for(int i=1;i<=J;i++) { read(x1);read(y1);read(z); add(num(x1,y1),z); } spfa(); mn=inf; for(int i=0;i<=(1<<(J+1))-1;i++) mn=min(mn,dist[n*m][i]); if(mn==inf)printf("-1");else printf("%d ",mn); }
15.汽车加油行驶问题(spfa+状态
MDZZ干脆叫最短路24题好了...
dist[i][state]表示走到位置i,油量为state
凭什么走到加油站就必须加油...调了好久才发现自己看错题
#include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<queue> using namespace std; const int maxn=50010,inf=1000000000; struct poi{int dis,pos,state;}; priority_queue<poi>q; bool operator <(poi a,poi b){return a.dis>b.dis;}; int n,k,mn,a,b,c; int dist[maxn][20],g[maxn],dxy[5]={0,0,0,-1,+1}; bool v[maxn][20]; void read(int &k) { k=0;int f=1;char c=getchar(); while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar(); while(c<='9'&&c>='0')k=k*10+c-'0',c=getchar(); k*=f; } int num(int x,int y){return (x-1)*n+y;} void spfa() { for(int i=1;i<=n*n;i++) for(int j=0;j<=k;j++) dist[i][j]=inf; dist[1][k]=0;v[1][k]=1;q.push((poi){0,1,k}); while(!q.empty()) { poi now=q.top();q.pop(); for(int i=1;i<=4;i++) { int too=now.pos+dxy[i]; int st=now.state; if(too>n*n||too<1||((now.pos%n==0)&&dxy[i]==1)||((now.pos%n==1)&&dxy[i]==-1))continue; int cost=0; if(i==1||i==3)cost=b; if((!g[too])&&dist[too][st-1]>dist[now.pos][st]+cost&&st>0) { dist[too][st-1]=dist[now.pos][st]+cost; if(!v[too][st-1]) { v[too][st-1]=1; q.push((poi){dist[too][st-1],too,st-1}); } } if(g[too]&&dist[too][k]>dist[now.pos][st]+a+cost&&st>0) { dist[too][k]=dist[now.pos][st]+a+cost; if(!v[too][k]) { v[too][k]=1; q.push((poi){dist[too][k],too,k}); } } if((!g[too])&&dist[too][k]>dist[now.pos][st]+a+c+cost&&st>0) { dist[too][k]=dist[now.pos][st]+a+c+cost; if(!v[too][k]) { v[too][k]=1; q.push((poi){dist[too][k],too,k}); } } } v[now.pos][now.state]=0; } } int main() { read(n);read(k);read(a);read(b);read(c); dxy[1]=-n;dxy[2]=n; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) read(g[num(i,j)]); spfa(); mn=inf; for(int i=0;i<=k;i++) mn=min(mn,dist[n*n][i]); printf("%d ",mn); }
16.数字梯形问题(最长不相交路径(最大费用最大流△
题目大意:规则1:路径互不相交 规则2:路径只可以在点相交 规则3:路径可以在点和边相交。从梯形顶部到底部分别找三个规则的最长路径。
和第11题差不多,就是把一个点拆成俩来限制点的使用次数。
PS:这题规则2和3的顶部的点不能用多次...T_T
规则1:S连顶部容量1费用0,点i拆成i和i',i连i'容量1费用权值,i'连下一个点j容量1费用0,底部点连T容量1费用0。
规则2:S连顶部容量1费用0,点i拆成i和i',i连i'容量inf费用权值,i'连下一个点j容量1费用0,底部点连T容量inf费用0。
规则3:S连顶部容量1费用0,点i拆成i和i',i连i'容量inf费用权值,i'连下一个点j容量inf费用0,底部点连T容量inf费用0。
然后跑最大费用最大流就行辣!
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> const int maxn=1100,inf=1000000000; struct poii{int too,pre,c,f,cf,v;}e[100010]; int h[maxn],dist[maxn],pre[maxn],last[maxn],z[maxn][maxn]; int sum,sum1,ans,n,m,tot; bool v[maxn]; using namespace std; void add(int x,int y,int c,int v) { e[++tot].too=y;e[tot].pre=last[x];last[x]=tot; e[tot].c=e[tot].cf=c;e[tot].v=v; e[++tot].too=x;e[tot].pre=last[y];last[y]=tot;e[tot].v=-v; } void read(int &k) { k=0;int f=1;char c=getchar(); while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar(); while(c<='9'&&c>='0')k=k*10+c-'0',c=getchar(); k*=f; } void spfa(int s) { int front=0,rear=1; for(int i=0;i<=sum;i++)dist[i]=-inf,v[i]=0,pre[i]=-1; dist[s]=0;v[s]=1;h[1]=s; while(front!=rear) { if(front>maxn)front=-1; int now=h[++front]; for(int i=last[now],too=e[i].too;i;i=e[i].pre,too=e[i].too) if(e[i].cf) if(dist[too]<dist[now]+e[i].v) { dist[too]=dist[now]+e[i].v;pre[too]=i; if(!v[too])v[too]=1,rear>maxn&&(rear=-1),h[++rear]=too; } v[now]=0; } } void ford(int s,int t) { spfa(s); while(pre[t]!=-1) { int mincf=inf; for(int i=pre[t];i!=-1;i=pre[e[i^1].too]) mincf=min(mincf,e[i].cf); ans+=mincf*dist[t]; for(int i=pre[t];i!=-1;i=pre[e[i^1].too]) { e[i].f+=mincf;e[i^1].f=-e[i].f; e[i].cf-=mincf;e[i^1].cf+=mincf; } spfa(s); } } int num(int x,int y){return (2*m+x-2)*(x-1)/2+y;} int main() { tot=1; read(m);read(n);sum1=num(n,m+n-1);sum=sum1*2+1; for(int i=1;i<=m;i++)add(0,i,1,0); for(int i=1;i<=n;i++) for(int j=1;j<m+i;j++) { read(z[i][j]); add(num(i,j),num(i,j)+sum1,1,z[i][j]); if(i!=n) { add(num(i,j)+sum1,num(i+1,j),1,0); add(num(i,j)+sum1,num(i+1,j+1),1,0); } else add(num(i,j)+sum1,sum,1,0); } ford(0,sum); printf("%d ",ans);ans=0; memset(e,0,sizeof(e));memset(last,0,sizeof(last));tot=1; for(int i=1;i<=m;i++)add(0,i,1,0); for(int i=1;i<=n;i++) for(int j=1;j<m+i;j++) { add(num(i,j),num(i,j)+sum1,inf,z[i][j]); if(i!=n) { add(num(i,j)+sum1,num(i+1,j),1,0); add(num(i,j)+sum1,num(i+1,j+1),1,0); } else add(num(i,j)+sum1,sum,inf,0); } ford(0,sum); printf("%d ",ans);ans=0; memset(e,0,sizeof(e));memset(last,0,sizeof(last));tot=1; for(int i=1;i<=m;i++)add(0,i,1,0); for(int i=1;i<=n;i++) for(int j=1;j<m+i;j++) { add(num(i,j),num(i,j)+sum1,inf,z[i][j]); if(i!=n) { add(num(i,j)+sum1,num(i+1,j),inf,0); add(num(i,j)+sum1,num(i+1,j+1),inf,0); } else add(num(i,j)+sum1,sum,inf,0); } ford(0,sum); printf("%d ",ans); }
17.运输问题(最小/最大费用流(真·模板题
S连A容量a[i]费用0,A连B容量inf费用cij,B连T容量b[i]费用0,最小/最大费用最大流。
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<queue> using namespace std; const int inf=1000000000,maxn=2010; struct poi{int pre,too,v,c,f,cf;}e[200010],E[200010]; struct poii{int dis,pos;}; priority_queue<poii>q; bool operator <(poii a,poii b){return a.dis<b.dis;}; int n,m,z,sum,ans,tot,front,rear; int dist[maxn],h[maxn],pre[maxn],last[maxn],a[maxn],b[maxn]; bool v[maxn]; void read(int &k) { k=0;int f=1;char c=getchar(); while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar(); while(c<='9'&&c>='0')k=k*10+c-'0',c=getchar(); k*=f; } void add(int x,int y,int c,int v) { e[++tot].too=y;e[tot].pre=last[x];last[x]=tot; e[tot].cf=e[tot].c=c;e[tot].v=v; e[++tot].too=x;e[tot].pre=last[y];last[y]=tot; e[tot].v=-v; } void spfa(int s,int ty) { for(int i=0;i<=sum;i++) { if(ty)dist[i]=inf;else dist[i]=-inf; pre[i]=-1;v[i]=0; } v[s]=1;front=0;rear=1;h[1]=s;dist[s]=0; while(front!=rear) { int now=h[++front]; if(front==maxn)front=-1; for(int i=last[now];i;i=e[i].pre) if(e[i].cf) { if(ty) { if(dist[e[i].too]>dist[now]+e[i].v) { dist[e[i].too]=dist[now]+e[i].v; pre[e[i].too]=i; if(!v[e[i].too]) { v[e[i].too]=1;h[++rear]=e[i].too; if(rear==maxn)rear=-1; } } } else { if(dist[e[i].too]<dist[now]+e[i].v) { dist[e[i].too]=dist[now]+e[i].v; pre[e[i].too]=i; if(!v[e[i].too]) { v[e[i].too]=1;h[++rear]=e[i].too; if(rear==maxn)rear=-1; } } } } v[now]=0; } } void ford(int s,int t,int ty) { spfa(s,ty); while(pre[t]!=-1) { int mincf=inf; for(int i=pre[t];i!=-1;i=pre[e[i^1].too]) mincf=min(mincf,e[i].cf); ans+=dist[t]*mincf; for(int i=pre[t];i!=-1;i=pre[e[i^1].too]) { e[i].f+=mincf;e[i^1].f=-e[i].f; e[i].cf-=mincf;e[i^1].cf+=mincf; } spfa(s,ty); } } int main() { tot=1; read(m);read(n);sum=n+m+1; for(int i=1;i<=m;i++)read(a[i]),add(0,i,a[i],0); for(int i=1;i<=n;i++)read(b[i]),add(i+m,sum,b[i],0); for(int i=1;i<=m;i++) for(int j=1;j<=n;j++)read(z),add(i,j+m,inf,z); memcpy(E,e,sizeof(e)); ford(0,sum,1); printf("%d ",ans);ans=0; memcpy(e,E,sizeof(E)); ford(0,sum,0); printf("%d ",ans); }
18.分配问题
19.负载平衡问题
20.深海机器人问题
21.最长k可重区间集问题
22.最长k可重线段集问题
23.火星探险问题
24.骑士共存问题
总结(持续更新中...):
1.最大权闭合图:
有向图,点权有正有负,有向边u->v表示选了u必须选v,选一些点使权值最大。
S连正权点容量为权值,对于有向边u->v,u连v容量inf,负权点连T容量为权值相反数,答案为正权点总权值-最大流。
2.最小路径覆盖:
每个点只能在一条路径(不是边)上,求最少几条路径覆盖所有的点,一个点也可以算一条路径。
每个点u拆成u和u',对于有向边u->v,u'连v,答案为总点数减去二分图最大匹配。
无向图答案为总点数-(二分图最大匹配/2)。
3.最多不相交(或每个点最多为k条线段交点)路径:
求长度为k的不相交路径最多有几条。
每个点i拆成i和i‘相连容量为1,S连合法的起点容量为1,可以相连接的点相连容量为1,合法的终点连T容量为1,最大流。
若可以相交且某个点最多作为k条线段交点,就修改i和i'边的容量为k。
4.二分图最小点(权)覆盖:
找出最少的点使得这些点和所有边都有关联。
最小点覆盖直接最大匹配,最小点覆盖数=最大匹配数。
最小点权覆盖,A集和B集,S连A容量为点权,A连B容量inf,B连T容量点权。最大流即为答案。
5.二分图最大(点权)独立集
选一些点要求互不相连且权值最大。
最大独立集=总点数-最小点覆盖。
最大点权独立集=总点权-最小点权覆盖。
6.最大团:最大独立集的对立,先留坑。
选一些点要求两两都有边相连且权值最大。
7.线性规划网络优化:
将问题分成几个集合,把集合间的关系变成边跑费用流。
8.最长不相交(或每个点最多为k条线段交点)路径:
求k条不相交的路径使总长度最长。
拆点连边为k,对于边u->v,u'连v容量inf(或者k)。跑最大费用最大流。
若可以相交且某个点最多作为k条线段交点,就修改i和i'边的容量为k。
边可以相交就修改u'到v的边的容量。
9.分层图网络流
对于一个问题可以划分成几个相同的层次,比如每一天的每个图。
枚举层次,边枚举边建图跑网络流解决问题。
10.有条件最短路
其实和分层图最短路一个原理,给dist加一维记录状态,有时候要状态压缩。
适用于 需要某种条件才能到达某个点 或者 行走需要满足某些条件 的最短路题目。