2020牛客多校第一场H题Minimum-cost Flow(费用最小流)
题意: 给一个n个点m条边的有向带权图,q次询问,每次规定每条边的容量为u/v,你需要输出总流量为1时,从点1到点n的最小费用(分数表示),若到达的流量不足为1,则输出NaN;
题解:标准费用流模板,但是q的范围是1e5,所以我们要先跑一次费用流,把有用的信息记录下来,即把每一条增广路径的费用记录下来。
考虑放缩,同时乘以v,则总流量为v,每条边的容量为u,这是算出来的总费用除以v即为答案。我们可以在询问之前,预处理得到所有增广路的费用,每次进行一次SPFA算法后,就能得到一条增广路的费用,将其记录于path数组中(按增广顺序能保证每条增广路费用是升序的),并求其前缀和,方便后续询问的处理。
查询之前,先预处理得到:
(1)path[a],下标从0开始,表示单位容量(流量跑满,流量=容量)时第a+1条增广路费用。
(2)sum[a],下标从1开始,表示单位容量(流量跑满,流量=容量)时前a条增广路总费用。
接下来处理每次询问。
假设v=a*u+b(b<u),即前a条增广路流量都为u,第a+1条增广路流量达不到u,流量为b。
由于sum和path是之前预处理得到的单位容量情况下的费用,所以sum[a]要乘以u得到流量为u时前a条增广路总费用,path[a]要乘以b得到流量为b时第a+1条增广路费用,则ans=(sum[a]*u+path[a]*b)/v。
卡int。
题目链接 https://ac.nowcoder.com/acm/contest/5666/H
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int INF=INT_MAX; const int maxn=1000+5,maxm=10000+5; int n,m,s,t,q,cnt=0; int head[maxn]; int dis[maxn]; ll maxflow=0; ll minfee=0; bool vis[maxn]; vector<ll> path; ll sum[maxn]; struct node { int e; int nxt; int w;//指权值 int f;//指流量 }edge[maxm<<1]; inline void add(int u,int v,int flow,int cost) { edge[cnt].e=v; edge[cnt].w=cost; edge[cnt].f=flow; edge[cnt].nxt=head[u]; head[u]=cnt++; } inline int read() { int ans=0; char r=getchar(); while(r<'0'||r>'9')r=getchar(); while(r>='0'&&r<='9') { ans=ans*10+r-'0'; r=getchar(); } return ans; } bool spfa(int u,int v) { for(int i=0;i<maxn;i++)dis[i]=INF;//spfa基本操作 memset(vis,false,sizeof(vis));//spfa的队列排重优化 dis[u]=0; deque<int>d;//spfa的slf优化 d.push_back(u); vis[u]=true; while(!d.empty()) { int ui=d.front();d.pop_front();vis[ui]=false;//ui是当前松弛的边的起点 for(int i=head[ui];i!=-1;i=edge[i].nxt) { int vi=edge[i].e;int w=edge[i].w;int f=edge[i].f;//vi是当前松弛的边的终点 if(f&&dis[vi]>dis[ui]+w)//如果当前边有残量且是最短路上的边就对其进行松弛 { dis[vi]=dis[ui]+w; if(!vis[vi]) { if(!d.empty()&&dis[vi]<dis[d.front()])d.push_front(vi); else d.push_back(vi); } } } } return dis[t]!=INF; } int dfs(int u,int flow) { if(u==t)return flow; int detla=flow; vis[u]=true; for(int i=head[u];i!=-1;i=edge[i].nxt) { int v=edge[i].e;int w=edge[i].w;int f=edge[i].f; if(f&&!vis[v]&&dis[v]==dis[u]+w)//注意,一个点只能访问一次,这个可以用vis来维护(vis在spfa模块和dfs模块中都被用到,注意初始化) { int d=dfs(v,min(f,detla)); detla-=d; edge[i].f-=d;edge[i^1].f+=d; minfee+=d*w;//更新费用(单价x流量) if(detla==0)break; } } vis[u]=false; return flow-detla; } ll dinic() { maxflow=0; path.clear(); while(spfa(s,t)){ path.push_back(dis[t]); //记录增广路径的费用 memset(vis,false,sizeof(vis)); maxflow+=dfs(s,INF); } return maxflow; printf("%d %d",maxflow,minfee); } int main() { while(scanf("%d%d",&n,&m)!=EOF){ memset(head,-1,sizeof(head)); sum[0]=0; cnt=0; s=1,t=n; int ui,vi,wi=1,fi; for(int i=0;i<m;i++){ ui=read();vi=read();fi=read();//fi是单位流量费用,wi是最大流量 add(ui,vi,wi,fi);//正向边的权值为fi,流量为wi add(vi,ui,0,-fi);//反向边的权值为-fi,流量为0 } dinic(); ll pnum=path.size(),u,v; // for(int i=0;i<pnum;i++){ // cout<<path[i]<<" "; // } // cout<<endl; for(ll i=0;i<pnum;i++){ sum[i+1]=sum[i]+path[i]; } scanf("%d",&q); while(q--){ scanf("%lld%lld",&u,&v); if(u*pnum<v) printf("NaN\n"); else{ ll a=v/u; ll b=v%u; ll ans=sum[a]*u+path[a]*b; ll k=__gcd(ans,v); ans/=k; v/=k; printf("%lld/%lld\n",ans,v); } } } return 0; }