[FZOJ190]网络流+二分答案

FZOJ190
题目描述

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N0cmFuZ2VERERG,size_16,color_FFFFFF,t_70

  • 神仙题,自闭场不要在意。了解了算法的思路,来写一下博客。(也就当是练一下板子233)
  • 做这道题大概是这样一个思路过程:
    • 是在1~n的所有路径上,选择不超过k个点,点的权值变成1,使得最短路最大,求最大值。
    • 一般情况下,使最小值最大或者使最大值最小这种问题都是先二分答案。
    • 我们二分一个mid作为答案,那么从1到n一共走了mid步。考虑怎么判断是否有可行解。
    • 首先应该考虑的是如何把点权转化为边的关系,然后想到拆点。从x0到x1连一条权值为1的边,代表直接经过了x这个点。拆点一般什么时候用啊?网络流(不要问我什么神仙想法,我也不知道啊)。所以应该建分层图跑最小割判断是否有可行解。
    • 为了使每个点的权值只能贡献一次,我们只把当前层的x0与下一层的x1连一条容量为inf的边。
    • 对于一条边x,y,把每个当前层的x1向下一层的y0连一条容量为inf的边。当然,每一层的n0也要向下一层的n0连一条容量inf的边。
    • 求第1层的s1到第mid-1层的n0的最小割。最小割意味着什么?画一下图,它代表的含义就是使用最少的点,让s1无法到达第mid-1层的n0(无法到达mid-1的n0代表着它一定到达了mid-1层之后的分层图,答案>=mid,符合条件)。
    • 哎,这题太神仙了。
Coding
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=1e5+10;
const int inf=1e6;
struct node{int x,y;}e[N];long long maxflow,flow;
int n,m,k,s,t,tot,ver[N*50],Next[N*50],lin[N*50],edge[N*50],d[N*50];
int cal(int i,int j,int temp){return (i-1)*2*n+temp*n+j;}
void add(int x,int y,int z){
    ver[++tot]=y;Next[tot]=lin[x];lin[x]=tot;edge[tot]=z;
    ver[++tot]=x;Next[tot]=lin[y];lin[y]=tot;edge[tot]=0;
}
bool bfs(){
    memset(d,0,sizeof(d));
    queue<int>q;
    q.push(s);d[s]=1;
    while(q.size()){
        int x=q.front();q.pop();
        for(int i=lin[x];i;i=Next[i]){
            if(edge[i]){
                int y=ver[i];
                if(!d[y]){
                    d[y]=d[x]+1;
                    q.push(y);
                    if(y==t) return 1;
                }
            }
        }
    }
    return 0;
}
int dinic(int x,int flow){
    if(x==t) return flow;
    int rest=flow,num;
    for(int i=lin[x];i&&rest;i=Next[i]){
        int y=ver[i];
        if(edge[i]&&d[y]==d[x]+1){
            num=dinic(y,min(edge[i],rest));
            if(!num) d[y]=0;
            rest-=num;edge[i]-=num;edge[i^1]+=num;
            if(!rest) return flow-rest;
        }
    }
    return flow-rest;
}
bool check(int mid){
    memset(lin,0,sizeof(lin));
    memset(Next,0,sizeof(Next));
    tot=1;
    for(int i=1;i<=mid+1;++i){
        for(int j=2;j<n;++j) 
            add(cal(i,j,0),cal(i,j,1),1);
        if(i!=mid+1){
            for(int j=2;j<n;++j) 
                add(cal(i,j,0),cal(i+1,j,1),inf);
            add(cal(i,n,0),cal(i+1,n,0),inf);
            for(int j=1;j<=m;++j){
                add(cal(i,e[j].x,1),cal(i+1,e[j].y,0),inf);
                add(cal(i,e[j].y,1),cal(i+1,e[j].x,0),inf);
            }
        }
    }maxflow=0;
    s=cal(1,1,1);t=cal(mid+1,n,0);
    while(bfs()){
        while(flow=dinic(s,inf)) 
            maxflow+=flow;
        if(maxflow>k) return 0;
    }
    return maxflow<=k;
}
int main(){
    freopen("min.in","r",stdin);
    freopen("min.out","w",stdout);
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=m;++i){
        scanf("%d%d",&e[i].x,&e[i].y);
        e[i].x++,e[i].y++;
    }
    int l=0,r=2*n;
    while(l+1<r){
        int mid=l+r>>1;
        if(check(mid)) l=mid;
        else r=mid;
    }
    if(check(l)) printf("%d\n",r);
    else printf("%d\n",l);
    return 0;
}
speech.gif posted on 2019-03-04 16:11 kgxpbqbyt 阅读( ...) 评论( ...) 编辑 收藏
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值