洛谷P2619 [國家集訓隊2]Tree I(帶權二分,Kruscal,歸併排序)

洛谷題目傳送門html

給一個比較有逼格的名詞——WQS二分/帶權二分/DP凸優化(固然這題不是DP)。函數

用來解決一種特定類型的問題:優化

\(n\)個物品,選擇每個都會有相應的權值,須要求出強制選\(need\)個物品時的最大/最小權值和。spa

通常來講,咱們求不限制個數的最大/最小權值和很容易,但在限制個數的前提下再求最值會變得有點困難。比較低效的作法是對狀態再加設一個維度表示已選物品數量,而後經過DP等方法求出。code

應用前提:設\(g_x\)爲強制選\(x\)個物品的最大/最小權值和,若是全部的點對\((x,g_x)\)在平面上可以構成一個凸包,那麼能夠考慮使用WQS二分。htm

因此說用三個名詞合指它也不爲過(提出者WQS,二分的量是權值增量,使用前提是凸函數)blog

WQS的論文這裏能夠下載排序

建議食用Creeper_LKF大佬的blog,數形結合的分析過程已經很是完整了。ip

簡單的來講,咱們不能知道這個凸包長什麼樣子,但咱們能夠拿着一個斜率爲\(k\)的直線去切這個凸包,至關於給每一個物品附加了一個權值\(k\)。設直線的截距爲\(b\),那麼選\(x\)個物品後總權值就會等於\(b+kx\)。咱們經過\(O(n)\)的DP等方法找到最大的\(b\),同時也能夠求出選了的個數\(x\),經過\(x\)\(need\)的關係來調整直線斜率繼續二分。get

拿本題來講,選\(x\)條白邊,能夠寫個平方DP而後發現\(g_x\)是個下凸函數。而後咱們在\([-100,100]\)(顯然是斜率的上下界,由於更改一條邊帶來的權值和的更改不會超過\(100\))的範圍內二分\(k\),以後全部白邊的權值增長\(k\),跑一遍Kruscal統計選了多少條白邊。若是這個數量大於等於\(need\)就調大\(k\),不然調小。

最後斜率爲\(mid\)的直線與凸包的切點就是答案,注意從中減去\(k\)的影響(ans-=mid*x

邊界問題Creeper_LKF大佬也證實了只要在邊權相等的時候優先選白邊就沒問題了。

寫法上有一個優化:每次Kruscal的時候不用從新排序了。由於每次咱們只是給全部白邊總體加一個權值,因此若是咱們先把白邊和黑邊分開排序的話,加完之後也仍是有序的。每次Kruscal時只要用相似歸併排序的方法\(O(E)\)的掃一遍就能夠啦!複雜度從\(O(E\log E\log 200)\)降到了\(O(E\log E+E\log200)\),目前暫時rank1,歡迎超越。

#include<cstdio>
#include<algorithm>
#define RG register
#define R RG int
#define G if(++ip==iend&&fread(ip=buf,1,N,stdin))
using namespace std;
const int N=5e5+9,M=1e6+9;
char buf[N],*iend=buf+N,*ip=iend-1;
inline int in(){
    while(*ip<'-')G;
    R x=*ip&15;G;
    while(*ip>'-'){x=x*10+(*ip&15);G;}
    return x;
}
struct Edge{
    int u,v,w;bool c;
    inline bool operator<(RG Edge&x){
        return w<x.w;
    }
}e[M];
int f[N];
int getf(R x){
    return x==f[x]?x:f[x]=getf(f[x]);
}
inline bool add(R i){//嘗試加邊並返回是否成功
    if(getf(e[i].u)==getf(e[i].v))return 0;
    f[f[e[i].u]]=f[e[i].v];return 1;
}
int main(){
    R n=in(),mw=0,m=in(),need=in(),i,j;//mw爲白邊數量
    for(i=0;i<m;++i){//點和邊都從0開始存了
        e[i].u=in();e[i].v=in();e[i].w=in();
        if(!(e[i].c=in()))swap(e[mw++],e[i]);//將黑白邊分開
    }
    sort(e,e+mw);sort(e+mw,e+m);
    R l=-100,r=100,mid,ans;
    while(l<r){
        mid=(l+r+1)>>1;
        for(i=0;i<n;++i)f[i]=i;
        ans=i=0;j=mw;
        while(i<mw&&j<m)//類歸併排序
            e[i].w+mid<=e[j].w?ans+=add(i++):add(j++);
        while(i<mw)ans+=add(i++);//白邊數量統計完整
        ans<need?r=mid-1:l=mid;
    }
    mid=l;
    for(i=0;i<n;++i)f[i]=i;
    ans=i=0;j=mw;//最後算權值總和
    while(i<mw&&j<m)
        if(e[i].w+mid<=e[j].w)
            ans+=(e[i].w+mid)*add(i),++i;
        else ans+=e[j].w*add(j),++j;
    while(i<mw)ans+=(e[i].w+mid)*add(i),++i;
    while(j<m )ans+=e[j].w*add(j),++j;
    printf("%d\n",ans-need*mid);//減掉
    return 0;
}
相關文章
相關標籤/搜索