Logo __vector__ 的博客

博客

P2619 题解+带权二分记录

...
__vector__
2025-12-01 12:55:48

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-08-03 19:11:50

做法

首先忽略 $need$ 条白边的限制。
设 $f(x)$ 表示有 $i$ 条白边时的最小边权和。

由题解可知,这个函数是个凸函数。

对于这个函数,难以直接求出其中某个点的值,但是可以轻松求出它的最小值点。

所以,可以试着让 $need$ 点成为值最小的点。

现在已知值最小的点 $m$,尝试让它向 $need$ 点靠近。

如果 $m$ 点在 $need$ 点左边,那么将所有白边的权值加上 $w(w \ge 0)$,使得 $m$ 点向右移动(因为白边权值增加了,求最小生成树时,使用的白边数量减少,从而使得最小值点向左移动)。

反之亦然。

现在要做的,就是二分 $w$,使得 $need$ 点与 $m$ 点重合($need$ 点成为值最小的点)

二分出 $w$ 之后,还要把求得的最小边权和减去 $w \cdot need$,消去影响。

Code

\/*
Sto pt orz
Sto zgc orz
Sto lhx orz
Sto tyy orz
*\/
#include <bits\/stdc++.h>
using namespace std;
namespace Main
{
    typedef pair<int,int> PII;
    const int maxv=5e4+5;
    const int maxe=1e5+5;
    struct EDGE
    {
        int u,to,c,col;
        bool operator <(const EDGE& b)
        {
            return (c!=b.c)?(c < b.c):(col<b.col);
        }
    }edge[maxe];
    int cnt;
    inline void add(int s,int t,int c,int col)
    {
        edge[++cnt].to=t;
        edge[cnt].u=s;
        edge[cnt].c=c;
        edge[cnt].col=col;
    }
    int fa[maxv];
    int find(int x)
    {
        return x==fa[x]?x:fa[x]=find(fa[x]);
    }
    int V,E,need;
    PII kruskal()
    {
        sort(edge+1,edge+E+1);
        int ans=0;
        int ecnt,whicnt;
        ecnt=whicnt=0;
        for(int i=1;i<=E;i++)
        {
            int a=edge[i].u,b=edge[i].to;
            if(find(a)!=find(b))
            {
                fa[find(a)]=find(b);
                ans+=edge[i].c;
                ecnt++;
                if(edge[i].col==0)whicnt++;
            }
            if(ecnt==V-1)break;
        }
        return make_pair(ans,whicnt);
    }
    void main()
    {
        scanf("%d%d%d",&V,&E,&need);
        int si,ti,ci,coli;
        for(int i=1;i<=E;i++)
        {
            scanf("%d%d%d%d",&si,&ti,&ci,&coli);
            add(si+1,ti+1,ci,coli);
        }
        int l=-111,r=111;
        int ans=0;
        while(l<=r)
        {
            int mid=l+r>>1;
            for(int i=1;i<=V;i++)fa[i]=i;
            for(int i=1;i<=E;i++)
            {
                if(edge[i].col==0)edge[i].c+=mid;
            }
            PII sto_pt_orz=kruskal();
            if(sto_pt_orz.second>=need)
            {
                ans=sto_pt_orz.first-need*mid;
                l=mid+1;
            }
            else
            {
                r=mid-1;
            }
            for(int i=1;i<=E;i++)
            {
                if(edge[i].col==0)edge[i].c-=mid;
            }
        }
        printf("%d",ans);
    }
}
int main()
{
    Main::main();
    return 0;
}

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。