算法:
题目:(多按一下F5)
【】
关于输出路径,因为即使有反向弧经过左侧点也一定会改变左侧点的去向,若没连向右侧就会被更新到0,所以不用在意。
mark记录有入度的右侧点,然后从没入度的右侧点开始把整条路径输出来即可。
#include#include #include using namespace std;const int maxn=100000,inf=0x3f3f3f3f;int n,m,S,T,d[maxn],q[10010],first[maxn],tot=1,nex[maxn];bool mark[maxn];struct edge{ int from,v,flow;}e[maxn];void insert(int u,int v,int w){tot++;e[tot].v=v;e[tot].flow=w;e[tot].from=first[u];first[u]=tot; tot++;e[tot].v=u;e[tot].flow=0;e[tot].from=first[v];first[v]=tot;}bool bfs(){ memset(d,-1,4*(2*n+3)); int head=0,tail=1;q[0]=S;d[S]=0; while(head 10000)head=0; for(int i=first[x];i;i=e[i].from) if(e[i].flow&&d[e[i].v]==-1) { int y=e[i].v; d[y]=d[x]+1; q[tail++]=y;if(tail>10000)tail=0; } } return d[T]!=-1;}int dinic(int x,int a){// printf("x=%d a=%d\n",x,a); if(x==T||a==0)return a; int flow=0,f; for(int i=first[x];i;i=e[i].from) if(e[i].flow&&d[e[i].v]==d[x]+1&&(f=dinic(e[i].v,min(a,e[i].flow)))>0) { if(f>0) { nex[x]=e[i].v; if(e[i].v-n>0)mark[e[i].v-n]=1; } e[i].flow-=f; e[i^1].flow+=f; a-=f; flow+=f; if(a==0)break; } return flow;}int main(){ scanf("%d%d",&n,&m); S=0,T=2*n+1; for(int i=1;i<=m;i++) { int u,v; scanf("%d%d",&u,&v); insert(u,v+n,1); } for(int i=1;i<=n;i++)insert(S,i,1); for(int i=n+1;i<=2*n;i++)insert(i,T,1); int ans=n; while(bfs())ans-=dinic(S,inf); for(int i=1;i<=n;i++)if(!mark[i]) { printf("%d ",i); int k=i; while(nex[k]) { printf("%d ",nex[k]-n); k=nex[k]-n; } printf("\n"); } printf("%d",ans); return 0;}
【】
本题模型为最小路径覆盖问题。
(i+j)为完全平方数即连边,图显然是DAG。
题目变为添加尽量多的点使最小路径覆盖≤n(一条简单路径表示一根柱子)
题目的关键在于答案不可知(即二分图点数不确定),所以从1开始枚举答案,每次两端加点,
判断1..i-1是否有和i连边的(注意别连反了),然后在前一次的残余网络上建边再跑增广即可。
枚举答案理论上应用二分,但二分无法利用上次的残余网络,反而慢。
打印路径很烦,没AC……
#include#include #include #include using namespace std;const int maxn=100000,inf=0x3f3f3f3f;int n,m,S,T,d[maxn],q[10010],first[maxn],tot=1,nex[maxn],nexnow[maxn];bool mark[maxn];struct edge{ int from,v,flow;}e[maxn];void insert(int u,int v,int w){tot++;e[tot].v=v;e[tot].flow=w;e[tot].from=first[u];first[u]=tot; tot++;e[tot].v=u;e[tot].flow=0;e[tot].from=first[v];first[v]=tot;}bool bfs(){ memset(d,-1,4*(T+1)); int head=0,tail=1;q[0]=S;d[S]=0; while(head 10000)head=0; for(int i=first[x];i;i=e[i].from) if(e[i].flow&&d[e[i].v]==-1) { int y=e[i].v; d[y]=d[x]+1; q[tail++]=y;if(tail>10000)tail=0; } } return d[T]!=-1;}int dinic(int x,int a){ if(x==T||a==0)return a; int flow=0,f; for(int i=first[x];i;i=e[i].from) if(e[i].flow&&d[e[i].v]==d[x]+1&&(f=dinic(e[i].v,min(a,e[i].flow)))>0) { if(f>0) { nexnow[x]=e[i].v-1000; //nex[x]=e[i].v-1000; } e[i].flow-=f; e[i^1].flow+=f; a-=f; flow+=f; if(a==0)break; } return flow;}int main(){ scanf("%d",&n); S=0,T=2000;int ans=0,i; for(i=1;;i++) { for(int j=1;j n)break; } printf("%d\n",--i);// for(int j=1;j<=i;j++)// {// for(int k=first[j];k;k=e[k].from)// if(!e[k].flow)// {// nex[j]=e[k].v-1000;// break;// }// printf("nex[%d]=%d\n",j,nex[j]);// }// printf("nex=%d\n",nex[12]);// for(int j=1;j<=i+1;j++)printf("nex[%d]=%d\n",j,nex[j]); for(int j=1;j<=i;j++) if(!mark[j]) { int k=j;printf("%d ",j); while(nex[k]!=-1000&&nex[k]!=0) { printf("%d ",nex[k]); mark[nex[k]]=1; k=nex[k]; } printf("\n"); } return 0;}
【】
S向xi连边容量为单位人数
yi向T连边容量为餐桌人数
xi每个节点向yi每个节点连边容量为1(限制每单位至多一个人在桌)
【】
最大权闭合子图
S向计划连边,仪器向T连边,依赖关系连边正无穷。
ans=正权和-最小割
【】
这个问题的主要约束条件是每天的餐巾够用,而餐巾的来源可能是最新购买,也可能是前几天送洗的餐巾。每天用完的餐巾可以选择送到快洗部或慢洗部,或者留到下一天再处理。
所以我们可以把每天要用的和用完的分离开处理,建模成二分图(每天用完的和需要的之间用ri联系)。
左侧xi表示第i天用完的餐巾,其数量为ri,所以从S向Xi连接容量为ri的边作为限制。
右侧Yi是第i天需要的餐巾,数量为ri,与T连接的边容量作为限制。
每天用完的餐巾可以选择留到下一天(Xi->Xi+1),不需要花费;
或送到快洗部(Xi->Yi+m),费用为f,送到慢洗部(Xi->Yi+n),费用为s。
每天需要的餐巾除了刚刚洗好的餐巾,还可能是新购买的(S->Yi),费用为p。
最小费用最大流。