01分数规划
bzoj1486 最小圈
题目大意:求一个图内的某个环,使得sigma ai[i]/k(环上点数)最小。
思路:二分答案,如果sigma ai[i]-k*mid>0说明mid可以更大,每次判断的时候给所有边-mid,就成了判断负环的问题。这里用spfa普通的判法会tle,所以要用深搜版的spfa来判断。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 3005 #define maxe 20005 #define eps 1e-10 using namespace std; int n,point[maxm]={0},next[maxe]={0},en[maxe]={0},tot=0; double va[maxe],vv[maxe]={0},dis[maxm]; bool visit[maxm],flag; int cmp(double x,double y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0; } void add(int u,int v,double w){ next[++tot]=point[u];point[u]=tot;en[tot]=v;vv[tot]=w; } void spfa(int u){ int i,j,v;visit[u]=true; for (i=point[u];i;i=next[i]){ if (dis[v=en[i]]>dis[u]+va[i]){ if (visit[v]){flag=true;break;} else{ dis[v]=dis[u]+va[i];spfa(v); } } }visit[u]=false; } bool judge(double x){ int i,j;flag=false; memset(dis,0,sizeof(dis)); memset(visit,false,sizeof(visit)); for (i=1;i<=tot;++i) va[i]=vv[i]-x; for (i=1;i<=n;++i){ spfa(i);if (flag) return true; }return false; } int main(){ int i,j,m,u,v;double w,l=0.0,r=0.0,mid; scanf("%d%d",&n,&m); for (i=1;i<=m;++i){ scanf("%d%d%lf",&u,&v,&w); add(u,v,w*1.0);r=max(r,w*1.0);l=min(l,w*1.0); }while(cmp(l,r)<0){ mid=(l+r)/2; if (judge(mid))r=mid; else l=mid; }printf("%.8f\n",l); }
bzoj3232 圈地游戏
题目大意:给定一个网格,格内和边上都有权值,求一个圈,使得格内的权值和v/边上的权值和c最大。
思路:二分答案k,判断能否存在v-ck>0,就是求所有格内权值-不选的格的权值-边界上选的格在边上的权值-选的两个格间的边权,然后就是总的格内的价值-最小割,S表示选,T表示不选,那么从S向边界的点连边界的边权,相邻两点连中间边权,每个点向T连格内边权。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 5000 #define maxe 100000 #define maxx 55 #define inf 2100000000.0 #define eps 1e-9 using namespace std; struct use{ int st,en;double va; }edge[maxe]; double ai[maxx][maxx],he[maxx][maxx],li[maxx][maxx],sum=0; int point[maxm],next[maxe],dis[maxm],gap[maxm],pre[maxm],bi[maxx][maxx],tot,n,m,st,en, dx[4]={0,1,0,-1},dy[4]={1,0,-1,0},cur[maxm]; void add(int u,int v,double vv){ next[++tot]=point[u];point[u]=tot;edge[tot]=(use){u,v,vv}; next[++tot]=point[v];point[v]=tot;edge[tot]=(use){v,u,0.0}; } double isap(){ int i,j,u,v;bool f;double ans=0,minn; memset(dis,0,sizeof(dis)); memset(gap,0,sizeof(gap)); for (i=st;i<=en;++i) cur[i]=point[i]; gap[0]=en-st+1;u=st; while(dis[st]<en-st+1){ f=false; for (i=cur[u];i;i=next[i]) if (edge[i].va-0>eps&&dis[edge[i].en]+1==dis[u]){ cur[u]=i;f=true;break; } if (f){ pre[u=edge[i].en]=i; if (u==en){ minn=inf; for (i=en;i!=st;i=edge[pre[i]].st) minn=min(minn,edge[pre[i]].va); ans+=minn; for (i=en;i!=st;i=edge[pre[i]].st){ edge[pre[i]].va-=minn; edge[pre[i]^1].va+=minn; }u=st; } }else{ --gap[dis[u]]; if (!gap[dis[u]]) return ans; j=en-st+1; for (i=point[u];i;i=next[i]) if (edge[i].va-0>eps) j=min(j,dis[edge[i].en]); ++gap[dis[u]=j+1];cur[u]=point[u]; if (u!=st) u=edge[pre[u]].st; } }return ans; } bool judge(double x){ int i,j,k,xi,yi;tot=1; memset(point,0,sizeof(point)); memset(next,0,sizeof(next)); for (i=1;i<=m;++i){ add(st,bi[1][i],x*he[1][i]); add(st,bi[n][i],x*he[n+1][i]); }for (i=1;i<=n;++i){ add(st,bi[i][1],x*li[i][1]); add(st,bi[i][m],x*li[i][m+1]); }for (i=1;i<=n;++i) for (j=1;j<=m;++j) add(bi[i][j],en,ai[i][j]); for (i=1;i<=n;++i) for (j=1;j<=m;++j) for (k=0;k<4;++k){ xi=i+dx[k];yi=j+dy[k]; if (xi<1||xi>n||yi<1||yi>m) continue; if (k==0) add(bi[i][j],bi[xi][yi],x*li[i][j+1]); if (k==1) add(bi[i][j],bi[xi][yi],x*he[i+1][j]); if (k==2) add(bi[i][j],bi[xi][yi],x*li[i][j]); if (k==3) add(bi[i][j],bi[xi][yi],x*he[i][j]); }return (sum-isap()>eps); } int main(){ int i,j;double l,r,mid;scanf("%d%d",&n,&m); st=tot=1;en=n*m+2; for (i=1;i<=n;++i) for (j=1;j<=m;++j){scanf("%lf",&ai[i][j]);bi[i][j]=++tot;sum+=ai[i][j];} for (i=1;i<=n+1;++i) for (j=1;j<=m;++j) scanf("%lf",&he[i][j]); for (i=1;i<=n;++i) for (j=1;j<=m+1;++j) scanf("%lf",&li[i][j]); l=0.0;r=250000.0; while(r-l>eps){ mid=(l+r)/2.0; if (judge(mid)) l=mid; else r=mid; }printf("%.3f\n",l); }
bzoj2285 保密
题目大意:给定n个点和m条有向边(有时间ti和安全系数si),点是两排点,前n1个点间有一些连接两排点的空腔,一条路径的安全度是sigma(ti)/sigma(si),如果一只军队能从n到一个点,就能访问过所有这个点连出去的空腔,求访问过所有空腔的最小的安全度和。
思路:分数规划+网络流。对于每个有空腔连接的点,分数规划求出从n到每一个点的最小安全度(二分答案mid,最短路,sigma(ti)-midsigma(si)<0,可以用map存一下每一种二分的mid,算过就不用算了,这样可以节省计算量)。然后就是从两排点中选出一些点覆盖所有的空腔且权值和最小,这是最小点权覆盖的模型,从s向第一排点连边权值,从第二排向终点连边权值,将空腔所连接的两个点连边inf,求出最小割,如果大于inf就是无解,否则就输出。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<map> #define maxm 705 #define maxe 1000005 #define eps 1e-5 #define LD double #define inf 2100000000. #define len 1000000 using namespace std; struct use{int st,en;double va,si;}edge[maxe]; int point[maxm]={0},next[maxe],tot,gap[maxm]={0},dis[maxm]={0},pre[maxm]={0},n,que[len+1], st,en,cur[maxm],tt=0; LD dq[maxm],di[10005][maxm]; bool visit[maxm]={false},vi[maxm]={false}; map<double,int>cnt; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0; } void add(int u,int v,LD vv,LD va){ next[++tot]=point[u];point[u]=tot;edge[tot]=(use){u,v,vv,va}; } void add2(int u,int v,LD vv){ next[++tot]=point[u];point[u]=tot;edge[tot]=(use){u,v,vv,0.}; next[++tot]=point[v];point[v]=tot;edge[tot]=(use){v,u,0.,0.}; } LD spfa(int y,LD x){ int i,j,u,v,head=0,tail; di[y][que[tail=1]=n]=0.;visit[n]=true; while(head!=tail){ visit[u=que[head=head%len+1]]=false; for (i=point[u];i;i=next[i]){ if (di[y][v=edge[i].en]>di[y][u]+edge[i].va-x*edge[i].si){ di[y][v]=di[y][u]+edge[i].va-x*edge[i].si; if (!visit[v]) visit[que[tail=tail%len+1]=v]=true; } } } } bool judge(int x,LD y){ int i=cnt[y]; if (!i) spfa(i=cnt[y]=++tt,y); return cmp(di[i][x],0.)<=0; } LD isap(){ int i,j,u,v,minn;LD mn,ans=0.;bool f; gap[0]=en-st+1;u=st; for (i=st;i<=en;++i) cur[i]=point[i]; while(dis[st]<=en-st+1){ f=false; for (i=cur[u];i;i=next[i]) if (edge[i].va>eps&&dis[edge[i].en]+1==dis[u]){ cur[u]=i;f=true;break; } if (f){ pre[u=edge[i].en]=i; if (u==en){ for (mn=inf,i=en;i!=st;i=edge[pre[i]].st) mn=min(mn,edge[pre[i]].va); ans+=mn; for (i=en;i!=st;i=edge[pre[i]].st){ edge[pre[i]].va-=mn; edge[pre[i]^1].va+=mn; }u=st; } }else{ if (!(--gap[dis[u]])) return ans; minn=en-st+1; for (cur[u]=i=point[u];i;i=next[i]) if (edge[i].va>eps) minn=min(minn,dis[edge[i].en]); ++gap[dis[u]=minn+1]; if (u!=st) u=edge[pre[u]].st; } }return ans; } int main(){ int m,i,j,n1,m1,u,v,vv;LD t,s,l,r,mid; scanf("%d%d",&n,&m); for (tot=0,i=1;i<=m;++i){ scanf("%d%d%lf%lf",&u,&v,&t,&s); add(u,v,t,s); }memset(di,127,sizeof(di)); spfa(cnt[++tt]=1,0.); tot=1;scanf("%d%d",&m1,&n1); for (i=1;i<=n1;++i) vi[i]=(cmp(di[1][i],di[0][0])<0); for (i=n1;i;--i){ if (!vi[i]){dq[i]=inf;continue;} l=0.;r=10.; while(r-l>eps){ mid=(l+r)/2.; if (judge(i,mid)) r=mid; else l=mid; }dq[i]=l; }memset(point,0,sizeof(point)); st=0;en=n1+1; for (i=1;i<=n1;i+=2) add2(st,i,dq[i]); for (i=2;i<=n1;i+=2) add2(i,en,dq[i]); for (i=1;i<=m1;++i){ scanf("%d%d",&u,&v);add2(u,v,inf); }t=isap(); if (t<inf) printf("%.1f\n",t); else printf("-1\n"); }
bzoj3597 方伯伯运椰子
题目大意;给出一个有向无环图,已知每条路的参数ai、bi、ci、di,表示压缩/扩容一流量的费用,当前流量和流过1流量的费用。求(X-Y)/k的最大值,其中X指改变前费用、Y是改变后的,k是改变次数,不能改变起点出发的边,要求改变前后的总流量不变,且每条边都满流。
思路:0/1分数规划+spfa判负环。考虑二分答案mid,如果(X-Y)-k*mid>0,就说明答案可以更大,变换一下就是(Y-X)+k*mid<0。对于给定的满流的网络,可以建图:u->v:bi+di+mid的边,表示扩容;如果c>0,说明可以压缩,v->u:ai-di+mid的边。从起点连出去的边不能改变,所以只建u->v,0的边就可以了。因为<0,所以找负环就可以了。
注意:1)不是找从起点到终点的负路径,因为要满足流量的要求,所以一定是一个环的某些边+1,某些边-1。
2)只对c>0的边建反向边,表示可以压缩。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 5005 #define M 6005 #define LD double #define eps 1e-9 #define epe 1e-4 #define inf 1e50 #define len 100000 using namespace std; int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} struct use{int u,v,ai,bi,ci,di;}ed[M]; int n,m,point[N],next[M],en[M],tot,que[len+1],vz[N]; LD va[M],dis[N]; bool vi[N]; void add(int i,LD x){ int u,v;u=ed[i].u;v=ed[i].v; next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=(LD)(ed[i].bi+ed[i].di)+x; if (!ed[i].ci) return; next[++tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=(LD)(ed[i].ai-ed[i].di)+x; } bool spfa(){ int i,u,v,head=0,tail; for (i=n+2;i;--i){dis[i]=inf;vi[i]=false;vz[i]=0;} vi[que[tail=1]=n+1]=true;++vz[n+1];dis[n+1]=0.; while(head!=tail){ vi[u=que[head=head%len+1]]=false; for (i=point[u];i;i=next[i]){ if (cmp(dis[v=en[i]],dis[u]+va[i])>0){ dis[v]=dis[u]+va[i]; if (!vi[v]){ vi[que[tail=tail%len+1]=v]=true; ++vz[v]; if (vz[v]>n) return true; } } } }return false; } bool judge(LD x){ int i;tot=0; memset(point,0,sizeof(point)); for (i=1;i<=m;++i){ if (ed[i].u==n+1){ next[++tot]=point[ed[i].u];point[ed[i].u]=tot;en[tot]=ed[i].v;va[tot]=0.; }else add(i,x); }return spfa(); } int main(){ int i;LD l,r,mid; n=in();m=in();l=r=0.; for (i=1;i<=m;++i){ ed[i]=(use){in(),in(),in(),in(),in(),in()}; r=(LD)ed[i].di*(LD)ed[i].ci; }while(r-l>epe){ mid=(l+r)/2.; if (judge(mid)) l=mid; else r=mid; }printf("%.2f\n",l); }