BZOJ 2521: [Shoi2010]最小生成树&&2229: [Zjoi2011]最小割

相当于重点练了一下最小割的知识

------------------------------------------------

2229: [Zjoi2011]最小割

首先有个结论是这些点的最小割最多有n-1个(并不会证明)

然后就可以分治+最小割解决了

分治过程运用递归,对于每一层选择头(s)尾(t)跑最大流得出最小割,然后根据割集分出S集和T集继续分治即可


#include<cstdio>
#include<vector>
#include<cstring>
#include<queue>
#include<algorithm>
#define N 155
#define M 100005
#define INF 1e9
using namespace std;
 
int read()
{
    int a=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}
    return a*f;
}
 
struct edge{int to,f;}e[M];
vector<int>G[N];
int n,m,s,t,cnt;
int a[N],d[N],cur[N],mark[N],pr[N],ans[N][N];
 
void add(int x,int y,int v)
{
    e[cnt].to=y,e[cnt].f=v;
    G[x].push_back(cnt++);
}
 
bool bfs()
{
    queue<int>Q;
    memset(d,-1,sizeof(d));
    d[s]=0,Q.push(s);
    while(!Q.empty())
    {
        int x=Q.front();Q.pop();
        if(x==t) return 1;
        for(int i=0;i<G[x].size();++i)
        {
            edge tmp=e[G[x][i]];
            if(d[tmp.to]==-1&&tmp.f)
            {
                Q.push(tmp.to);
                d[tmp.to]=d[x]+1;
            }
        }
    }
    return 0;
}
  
int dfs(int x,int a)
{
    if(x==t) return a;
    int flow=0,f;
    for(int i=0;i<G[x].size();++i)
    {
        int y=G[x][i];
        if(e[y].f&&d[e[y].to]==d[x]+1)
        {
            f=dfs(e[y].to,min(a,e[y].f));
            e[y].f-=f;
            e[y^1].f+=f;
            flow+=f;
            a-=f;
            if(a==0) break;
        }
    }
    if(!flow) d[x]=-1;
    return flow;
}
 
void search(int x)
{
    mark[x]=1;
    for(int i=0;i<G[x].size();++i)
        if(e[G[x][i]].f&&!mark[e[G[x][i]].to]) search(e[G[x][i]].to);
}
 
void solve(int l,int r)
{
    if(l==r) return;
    for(int i=0;i<cnt;i+=2) e[i].f=e[i^1].f=(e[i].f+e[i^1].f)/2;
    s=a[l],t=a[r];
    int cs=0;
    while(bfs()){cs+=dfs(s,INF);}
    memset(mark,0,sizeof(mark));
    search(s);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n&&mark[i];++j)
            if(!mark[j]) ans[i][j]=ans[j][i]=min(cs,ans[i][j]);
    int L=l,R=r;
    for(int i=l;i<=r;++i)
        if(mark[a[i]]) pr[L++]=a[i];
        else pr[R--]=a[i];
    for(int i=l;i<=r;++i) a[i]=pr[i];
    solve(l,L-1),solve(R+1,r);
}
 
int main(void)
{
    int test=read();
    while(test--)
    {
        n=read(),m=read();
        memset(ans,127,sizeof(ans));
        memset(e,0,sizeof(e));
        cnt=0;
        for(int i=1;i<=n;++i) a[i]=i,G[i].clear();
        for(int i=1,x,y,z;i<=m;++i)
        {
            x=read(),y=read(),z=read();
            add(x,y,z);
            add(y,x,z);
        }
        solve(1,n);
        int que=read(),res;
        while(que--)
        {
            res=0;
            int x=read();
            for(int i=1;i<=n;++i)
                for(int j=i+1;j<=n;++j) if(ans[i][j]<=x) ++res;
            printf("%d\n",res);
        }
        printf("\n");
    }
    return 0;
}



------------------------------------------------

2521: [Shoi2010]最小生成树

题目中的操作其实等价于将选择一条边并将它的边权加上1

根据克鲁斯卡尔算法求解最小生成树的过程与思想,想到如果将比lab边权小的边全部用放在一个并查集里,如果此时lab边两个端点如果不连通才一定会把lab边加入最小生成树

所以一种可行的做法是把lab边两端分别作为源点和汇点

将边按权值排序后比lab边小的加入图:连接容量为a[lab].v-a[i].v+1的边(记得连两边,即两边分别连或连双向边)

跑最大流得出最小割

#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#define INF 1e9
#define N 501
#define M 805
#define RG register
using namespace std;
 
struct Edge{int x,y,z;}a[M];
struct edge{int next,to,v;}e[M*4];
int n,m,id,cnt,S,T;
int head[N],d[N];
 
int read()
{
    int a=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}
    return a*f;
}
 
void insert(int x,int y,int v)
{
    e[++cnt].next=head[x],e[cnt].to=y,e[cnt].v=v;
    head[x]=cnt;
}
 
int bfs()
{
    memset(d,-1,sizeof(d));
    queue<int>q;
    q.push(S),d[S]=0;
    while(!q.empty())
    {
        int x=q.front();q.pop();
        if(x==T) return 1;
        for(RG int i=head[x];i;i=e[i].next)
            if(e[i].v&&d[e[i].to]==-1)
            {
                d[e[i].to]=d[x]+1;
                q.push(e[i].to);
            }
    }
    return 0;
}
 
int dfs(int x,int a)
{
    if(x==T) return a;
    int flow=0,f;
    for(RG int i=head[x];i;i=e[i].next)
        if(e[i].v&&d[e[i].to]==d[x]+1)
        {
            f=dfs(e[i].to,min(a,e[i].v));
            e[i].v-=f;
            e[i^1].v+=f;
            flow+=f;
            a-=f;
            if(!a) break;
        }
    if(!flow) d[x]=-1;
    return flow;
}
 
int dinic()
{
    int res=0;
    while(bfs())
        res+=dfs(S,INF);
    return res;
}
 
int main(void)
{
    n=read(),m=read(),id=read();
    for(RG int i=1,x,y,z;i<=m;++i)
    {
        a[i].x=read(),a[i].y=read(),a[i].z=read();
        if(i==id) S=a[i].x,T=a[i].y;
    }
    cnt=1;
    for(RG int i=1;i<=m;++i)
        if(i!=id&&a[i].z<=a[id].z)
        {
            insert(a[i].x,a[i].y,a[id].z-a[i].z+1);
            insert(a[i].y,a[i].x,a[id].z-a[i].z+1);
        }
    printf("%d",dinic());
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值