题解 P2494 【[SDOI2011]保密】

· · 题解

前言

我觉得这是一道好题,因为它考到了许多知识,将很多知识综合了起来

思路

拿到这道题之后,我们应该将这个问题拆成两个问题

第一,求n到每个出入口的时间和与安全系数和比值最小的路径

第二,求出探索所有空腔的最小代价

做法

第一个问题

假设我们去的是点x,那么我们就是要找到一条从nx的路径,使得\sum (Ti *xi)/\sum (Si*xi )最小,

(xi表示第i条边在不在该路径上,若在为1,不在即为0)

我们考虑二分答案,假设我们当前二分的值为ans,

那如果这个答案满足条件,就要满足 \sum (Ti *xi)/\sum (Si *xi)<=ans

移项可得\sum (Ti*xi) -\sum (Si*xi)*ans<=0

即为\sum((Ti-ans*Si)*xi)<=0

那我们只要使每条边权改为Ti-Si*xi,然后看nx的路径是否<=0即可

这就是一个简单的01分数规划,我相信看这道题的人应该都看得懂qwq

但是要求多个点到n的最小路径,所以考虑整体二分

但貌似听说不整体二分也过得去,如果你不会的话就一个一个二分吧233

最短路用什么方法呢?Spfa?Dijkstra?Floyed?

no no no!!!

首先,这道题有负边,所以Dijkstra首先淘汰,SPFA?,但是这道题m又有足足1e5,跑SPFA巨慢啊,Floyed对于n<=700来说根本不可能

那咋办啊??

等等,我们是不是漏了什么?

没有环啊!!!

那就简单了,跑个拓扑序就完事了啊(我也不知道我前面扯那么多干嘛

第二个问题

现在我们得到了n到所有出入口的最小路径,那如何求探索所有空腔的最小代价呢?

首先,我觉得题目都已经在疯狂暗示告诉我们这是个一个二分图了

-----每个空腔有且只有两个出入口,并且这两个出入口不会在同一排。为了方便起见,两排出入口分别编号为1,3,5…2,4,6…并且最大的编号为n1

两排,每个出口一排一个,这不是二分图是什么??

其实这个网络流模型也并不难建立,

从源点往每个奇数点建边,流量为从n到该点的最小路径

对于每个空腔的两个出入口u,v,假设u是奇数,则从u向v建一条流量为inf的边

从每个偶数点往汇点建边,流量为从n到改点的最小路径

问题是为什么那么建图呢?

其实我们在这里利用的是最小割,因为对于每一个空腔的出入口u,v,我们必须选择一个,让uv建inf边的意义就是我们为了让uv分开,我们就必须在u

然后由最大流最小割定理,跑最小割即可 ## 代码 ```cpp #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #include<vector> #define inf 0x7fffffff/2 #define eps 1e-3 #define N 100010 using namespace std; typedef long long ll; typedef unsigned long long ull; inline ll read() { char ch=getchar(); ll s=0,w=1; while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();} while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();} return s*w; } struct Edge { int next,to; double s,t; }E[N<<1]; int Head[N],ecnt; int n,U[N],V[N],m,n1,m1; int pre[N],minn[N]; double dist[N],D[N]; int depth[N],Index[N]; struct edge { int next,to; double fl; }e[N*10]; int head[N],cnt=1; int q[N],ql[N],qr[N]; queue<int>qu; int vis[N]; int pos[N],nowpos; int ss,tt,maxflow; int Stack[N],top; inline void Add_edge(int from,int to,double s,double t) { E[++ecnt].to=to;E[ecnt].next=Head[from];E[ecnt].s=s;E[ecnt].t=t;Head[from]=ecnt;Index[to]++; }//对原图进行建边 inline void Topu(double x) { for(register int i=1;i<n;i++)dist[i]=inf;dist[n]=0; for(register int i=1;i<=n;i++) { int now=pos[i]; for(register int j=Head[now];j;j=E[j].next) { dist[E[j].to]=min(dist[E[j].to],dist[now]+E[j].t-E[j].s*x); } } }//求最短路 void Q(double L,double R,int l,int r) { if(l>r)return ; if(fabs(R-L)<=eps){for(register int i=l;i<=r;i++)D[q[i]]=L;return ;} double mid=(L+R)/2; Topu(mid); int nowl=0,nowr=0; for(register int i=l;i<=r;i++)if(dist[q[i]]<=0.0){ql[++nowl]=q[i];}else qr[++nowr]=q[i]; for(register int i=l;i<=l+nowl-1;i++)q[i]=ql[i-l+1]; for(register int i=l+nowl;i<=r;i++)q[i]=qr[i-nowl-l+1]; Q(L,mid,l,l+nowl-1);Q(mid+eps,R,l+nowl,r); }//整体二分 inline void add_edge(int from,int to,double fl){e[++cnt].to=to;e[cnt].next=head[from];e[cnt].fl=fl;head[from]=cnt;} //对网络流建边 inline int bfs() { memset(depth,0,sizeof(depth));while(!qu.empty())qu.pop(); qu.push(ss);depth[ss]=1; while(!qu.empty()) { int x=qu.front();qu.pop(); for(register int i=head[x];i;i=e[i].next) { if(fabs(e[i].fl-0)>=eps&&!depth[e[i].to])depth[e[i].to]=depth[x]+1,qu.push(e[i].to); } } return depth[tt]; } double dfs(int now,double flow) { if(now==tt)return flow; double ret=0; for(register int i=head[now];i;i=e[i].next) { if(fabs(ret-flow)<=eps)return flow; if(fabs(e[i].fl-0)>=eps&&depth[e[i].to]==depth[now]+1) { double fl=dfs(e[i].to,min(e[i].fl,flow-ret)); if(fabs(fl-0)>=eps) { ret+=fl; e[i].fl-=fl; e[i^1].fl+=fl; } } } if(fabs(ret-0)<=eps)depth[now]=0; return ret; } inline double Dinic() { double sum=0,x=0; while(bfs()){x=1;while(fabs(x-0)>=eps){x=dfs(ss,inf);sum+=x;}} return sum; }//最大流,即为最小割 inline void Prepare() { nowpos=0;top=0; for(register int i=1;i<=n;i++)if(!Index[i])Stack[++top]=i; while(top) { int x=Stack[top--];pos[++nowpos]=x; for(register int i=Head[x];i;i=E[i].next) { Index[E[i].to]--;if(!Index[E[i].to])Stack[++top]=E[i].to; } } }//求拓扑排序 int main() { scanf("%d%d",&n,&m); for(register int i=1;i<=n;i++)q[i]=i; for(register int i=1;i<=m;i++) { int from,to;double s,t; scanf("%d%d%lf%lf",&from,&to,&t,&s); Add_edge(from,to,s,t); } Prepare(); Q(0,inf,1,n-1);D[n]=0; m1=read(),n1=read(); for(register int i=1;i<=m1;i++)U[i]=read(),V[i]=read(); tt=n1+m1+1; for(register int i=1;i<=n1;i++) { if(i&1)add_edge(ss,i,D[i]),add_edge(i,ss,0); else add_edge(i,tt,D[i]),add_edge(tt,i,0); } for(register int i=1;i<=m1;i++) { if(V[i]&1)swap(U[i],V[i]); add_edge(U[i],V[i],inf),add_edge(V[i],U[i],0); } double ans=Dinic(); if(ans>1e8){printf("-1\n");} else printf("%.1lf\n",ans); return 0; } ``` **如果认为我这篇题解对你有帮助的可以给我点一下赞qwq。如果有任何疑问,或者认为我的题解有什么问题的话,请务必私信我,感激不尽!我会努力把我的题解写得最好的!**