20260606 - 部分分训练 2 总结

唉不是,部分分训练放蓝题,真不怕有人做出来吗??

好吧最后还是没人做出来。

好吧其实有人做出来,真的吓哭了,准备退役 .jpg


今天的总结没有部分分代码,只有正解代码,那咋办。

\(m=1\)(测试点 \(1,4,5,6\)

\(m=1\) 就是只要建一条赛道,那么也就是找出一条最长的路就行,发现是树的直径(只不过每条边有边权),两次 DFS 做完了。

时间复杂度:\(O(n)\)

\(b_i = a_i + 1\)(测试点 \(2,9,10,11\)

这个性质告诉我们给定的树是一条链,那么等价于给你一个长度为 \(n-1\) 的序列,划分为 \(m\) 段(按照顺序分区间),最大化每段的和的最小值。

这个东西显然具有单调性,考虑二分,判断的时候从前往后遍历,只要当前子段和 \(\ge mid\) 了就记作一段并新开下一段,最后统计总段数是否 \(\ge m\) 即可。

时间复杂度:\(O(n \log V)\)

\(a_i = 1\)(测试点 \(3,5,7,8\)

所有边都连向 \(1\),这说明树是一个菊花图,菊花图能取出的链长度要么是 \(1\) 要么是 \(2\),那么等价于给你一个长度为 \(n-1\) 的序列,分为 \(m\) 组(可以乱序)且每组数量为 \(1\)\(2\),最大化每组的和的最小值。

这个东西也具有单调性,依旧二分。先对这个序列升序排序,check 的时候先把序列末端所有 \(\ge mid\) 的都单独分组,剩下的部分双指针小对大做完了。

时间复杂度:\(O(n \log V)\)

也可以用 set 做,枚举每个位置找到第一个匹配上的,累加个数并删掉两值,时间复杂度 \(O(n \log n \log V)\)

注意在这里,双指针和 set 两种做法存在本质区别:虽然得到的数量是一样的,但 set 的最终剩余值中的最大值一定是所有可行分配方案中剩余值的最大值。

分支不超过 \(3\)(测试点 \(12,13,14,15,16\)

等价于题目给你一棵二叉树。

还是要二分。

check 时考虑树形 DP,对于一个节点有两种选择,一种是自己两个儿子弯一下得到一条链,还有一种是选择较长的儿子链向上扩充帮助祖先们拼凑满足条件的链。

显然当第一种情况合法时不会考虑第二种情况(一定不会更优),所以第二种情况当且仅当两条儿子链加起来不满足条件时才会向上扩充。维护 \(dp\)\(f\) 两个数组,一个表示以当前节点为根的子树能得到的最多链树,一个表示当前子树最多能向上扩充的长度。

对应转移即可。最终判断是否 \(dp_1 \ge m\)

时间复杂度:\(O(n \log V)\)

正解(测试点 \(17,18,19,20\)

其实就等价于把菊花图和二叉树两种情况拼起来。

还是二分,还是树形 DP,还是维护 \(dp\)\(f\),还是相同的定义,那么在一个点有多个儿子时,套用菊花图的做法,贪心匹配(此时需要用 set 做法)得到最大数量,并将没匹配上的最大值存进 \(f\)(这里呼应为什么要用 set 做法),最后还是判断是否 \(dp_1 \ge m\)

时间复杂度:\(O(n \log n \log V)\)

代码

只有正解代码,为什么呢,其实前面的部分分都有代码,但是二叉树的部分分我没写哦哦哦

所以懒得贴了,就放一个正解代码吧,前面的代码都是好写的 qwq。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 5e4+5;
int n,m,UP,l,r,Ans,dp[N],f[N];
vector<pii> g[N];
int read(){
    int su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
void DFS_sol(int u,int fa,int num){
    multiset<int> st;
    for(auto [v,w]:g[u])if(v^fa){
        DFS_sol(v,u,num);
        dp[u]+=dp[v];
        if(f[v]+w>=num)dp[u]++;
        else st.isr(f[v]+w);
    }
    for(auto it=st.begin();it!=st.end();){
        auto it2=st.lower_bound(num-*it);
        if(it2==it)it2++;
        if(it2!=st.end()){
            dp[u]++;
            st.erase(it2);
            it=st.erase(it);
        }else it++;
    }
    f[u]=(st.empty()?0:*st.rbegin());
    return;
}
bool check(int num){
    for(int i=1;i<=n;i++)dp[i]=0,f[i]=0;
    DFS_sol(1,0,num);return (dp[1]>=m);
}
int main(){
    n=read(),m=read();
    for(int i=1;i<n;i++){
        int x=read(),y=read(),l=read();
        g[x].pb({y,l}),g[y].pb({x,l});UP+=l;
    }
    l=1,r=UP,Ans=0;
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid))Ans=mid,l=mid+1;
        else r=mid-1;
    }cout<<Ans<<"\n";
    return 0;
}

总结

好题 >^<!

赛时没想到二叉树只有两种情况真是糖丸了。

感觉二叉树那里是对正解最好的提示吧可惜没做出来唉唉唉。二叉树那里如果做出来了很容易套上菊花图想到正解吧,对呀对呀。

期待部分分训练 3 /fendou

posted @ 2026-06-06 17:11  嘎嘎喵  阅读(28)  评论(0)    收藏  举报