[NOIP2018]賽道修建(二分+multiset)


考場上打了一個 \(vector\) 解法,因為我當時不會 \(multiset\)

好吧,我來講一講今年的 \(tgD1T3\)

首先,這題 \(55\) 分是不難想的

1、 \(b_i=a_i+1\) 的情況(一條鏈)

解法:把所有邊權記錄下來,這種情況等價於將序列分割成 \(m\) 段,使 \(m\) 段區間和的最小值最大

那么二分 \(m\) 段區間和的最小值,然后 \(O(n)\) 貪心掃一遍,時間復雜度 \(O(nlogn)\)

namespace subtask1{
    int a[maxn];
    void dfs(int x,int fa){
        for(int i=head[x],y;i;i=e[i].next){
            y=e[i].to;
            if(y==fa) continue;
            dfs(y,x);
            a[x]=e[i].val;
        }
    }
    int check(int k){
        int t=0,now=0;
        for(int i=1;i<n;i++){
            if(now+a[i]>=k){
                now=0;
                t++;
            }
            else now+=a[i];
        }
        return t>=m;
    }
    void solve(){
        dfs(1,0);
        int l=1,r=sum,mid;
        while(l<r){
            mid=l+r+1>>1;
            if(check(mid)) l=mid;
            else r=mid-1;
        }
        printf("%d\n",l);
        return ;
    }
}

2、 \(m=1\) 的情況(樹的直徑)

解法:取一條最長鏈,即為樹的直徑問題,記錄一下最大值和次大值,每次把最大
值傳到它的父親,時間復雜度 \(O(n)\)

namespace subtask2{
    int dfs(int x,int fa){
        int sum1=0,sum2=0;
        for(int i=head[x],y;i;i=e[i].next){
            y=e[i].to;
            if(y==fa) continue;
            sum2=max(sum2,dfs(y,x)+e[i].val);
            if(sum2>sum1) swap(sum1,sum2); 
        }
        ans=max(ans,sum1+sum2);
        return sum1;
    }
    void solve(){
        dfs(1,0);
        printf("%d\n",ans);
        return ;
    }
}

3、\(a_i=1\)的情況(菊花圖)

解法:把所有邊權記錄下來,從大到小排序。設邊權為 \(w\),答案即為 \(w_1+w_{2m-1},w_2+w_{2m-2},...,w_m+w_{m+1}\) 的最小值,時間復雜度 \(O(nlogn)\)

namespace subtask3{
	int a[maxn];
	bool cmp(int a,int b){
		return a>b;
	}
	void solve(){
		for(int i=head[1],y;i;i=e[i].next){
			y=e[i].to;
			a[y-1]=e[i].val;
		}
		sort(a+1,a+n,cmp);
		int ans=inf;
		for(int i=1;i<=m;i++)
			ans=min(ans,a[i]+a[2*m-i+1]);
		printf("%d\n",ans);
	}
}

分支不超過 \(3\) 的話其實就是正解的弱化版

看到題意描述第一反應就是先二分那個修建的\(m\)條賽道中長度最小的賽道的長度 \(k\) ,然后 \(O(n)\)\(O(nlogn)\) 判斷

那么怎么判斷呢?

對於每個結點,把所有傳上來的值 \(val\) 放進一個 \(multiset\) ,其實這些值對答案有貢獻就兩種情況:

  • \(val\geq k\)
  • \(val_a+val_b\geq k\)

那么第一種情況可以不用放進 \(multiset\),直接答案 \(+1\) 就好了。第二種情況就可以對於每一個最小的元素,在 \(multiset\) 中找到第一個 \(\geq k\)的數,將兩個數同時刪去,最后把剩下最大的值傳到那個結點的父親

我出考場后想為什么這種解法是正確的,有沒有可能對於有些情況直接傳最大的數會使答案更大?

當然不會。這個數即使很大也只能對答案貢獻加 \(1\),在其沒傳上去的時候可以跟原來結點的值配對,也只能對答案貢獻加 \(1\)

\(multiset\) 版:

int dfs(int x,int fa,int k){
    s[x].clear();
    int val;
    for(int i=head[x],y;i;i=e[i].next){
        y=e[i].to;
        if(y==fa) continue;
        val=dfs(y,x,k)+e[i].val;
        if(val>=k) ans++;
        //直接處理第一種情況
        else {
            s[x].insert(val);
        }
    }
    int Max=0;
    while(!s[x].empty()){
        if(s[x].size()==1){
            return max(Max,*s[x].begin());
        }
        //把最大的給傳上去
        it=s[x].lower_bound(k-*s[x].begin());
        //二分到那個值
        if(it==s[x].begin()&&s[x].count(*it)==1) it++;
        //若找到的就是它自己且當前值的count==1,迭代器++
        if(it==s[x].end()){
            Max=max(Max,*s[x].begin());
            s[x].erase(s[x].find(*s[x].begin()));
        }
        //若沒有找到比k-*s[x].begin()大的,就取個最大值,把*s[x].begin()刪掉
        else {
            ans++;
            s[x].erase(s[x].find(*it));
			s[x].erase(s[x].find(*s[x].begin()));
        }
        //處理第二種情況
    }
    return Max;
    //把最大值傳上去
}

\(vector\) 版:

while(!s[x].empty()){
    if(s[x].size()==1){
        return max(Max,*s[x].begin());
    }
    it=lower_bound(s[x].begin(),s[x].end(),k-*s[x].begin());
    if(it==s[x].begin()) it++;
    if(it==s[x].end()){
        Max=max(Max,*s[x].begin());
        s[x].erase(s[x].begin());
    }
    else {
        ans++;
        s[x].erase(it);
        s[x].erase(s[x].begin());
    }
}
return Max;

\(multiset\) 版:時間復雜度 \(O(nlog^2n)\)

\(vector\) 版:時間復雜度 \(O(n^2logn)\)

備注:如果數據是隨機的,\(vector\) 的寫法會很快,但菊花圖可以把它卡掉

然后 \(tgD1T3\) 就被我們解決了

還有就是那個二分上界可以換成樹的直徑

\(Code\ Below:\)

#include <bits/stdc++.h>
using namespace std;
const int maxn=50000+10;
int n,m,head[maxn],tot,ans,up;

struct node{
    int to,next,val;
}e[maxn<<1];

multiset<int> s[maxn];
multiset<int>::iterator it;

inline int read(){
    register int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return (f==1)?x:-x;
}

inline void add(int x,int y,int w){
    e[++tot].to=y;
    e[tot].val=w;
    e[tot].next=head[x];
    head[x]=tot;
}

int dfs(int x,int fa,int k){
    s[x].clear();
    int val;
    for(int i=head[x],y;i;i=e[i].next){
        y=e[i].to;
        if(y==fa) continue;
        val=dfs(y,x,k)+e[i].val;
        if(val>=k) ans++;
        else {
            s[x].insert(val);
        }
    }
    int Max=0;
    while(!s[x].empty()){
        if(s[x].size()==1){
            return max(Max,*s[x].begin());
        }
        it=s[x].lower_bound(k-*s[x].begin());
        if(it==s[x].begin()&&s[x].count(*it)==1) it++;
        if(it==s[x].end()){
            Max=max(Max,*s[x].begin());
            s[x].erase(s[x].find(*s[x].begin()));
        }
        else {
            ans++;
            s[x].erase(s[x].find(*it));
            s[x].erase(s[x].find(*s[x].begin()));
        }
    }
    return Max;
}

int check(int k){
    ans=0;
    dfs(1,0,k);
    if(ans>=m) return 1;
    return 0;
}

int dfs1(int x,int fa){
    int sum1=0,sum2=0;
    for(int i=head[x],y;i;i=e[i].next){
        y=e[i].to;
        if(y==fa) continue;
        sum2=max(sum2,dfs1(y,x)+e[i].val);
        if(sum1<sum2) swap(sum1,sum2);
    }
    up=max(up,sum1+sum2);
    return sum1;
}


int main()
{
    n=read(),m=read();
    int x,y,w;
    for(int i=1;i<n;i++){
        x=read(),y=read(),w=read();
        add(x,y,w);add(y,x,w);
    }
    dfs1(1,0);
    int l=1,r=up,mid;
    while(l<r){
        mid=l+r+1>>1;
        if(check(mid)) l=mid;
        else r=mid-1;
    }
    printf("%d\n",l);
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM