来讲点大家想听的:P17834

CSP-S 2026 研发基地 / hub

CSP 好好玩啊!啊!坑坏了!

思维难度中等。

代码难度超高。

简述题意

从一颗树里选择 \(k\) 个点,这 \(k\) 个点的连通块不超过两个。

使得剩下没选的 \(n-k\) 个点到这 \(k\) 个点的距离的最大值最小。

算法分析

我们发现"最大值最小",一眼就是二分的条件啊!

我们直接考虑二分。

对啥二分呢?

对题目里的通勤半径 二分也就是"\(n-k\) 个点到这 \(k\) 个点的距离的最大值"。

我们看能不能在这里面找出 \(k\) 个点使得满足条件。

于是开始考虑当二分到 \(rad\) 时可不可以。

check

首先我们去掉绝对可以不用管的东西,最外面的 \(rad\) 层叶子。

因为无论如何这个都可以满足。

这里要引进一个概念"关键点"。

"关键点":就是必须要选的点。

所以去掉的点都是非关键点,易证明剥叶子后留下剩下的点一定满足条件。

设:剩下的点为 \(cnt\)。

如果 \(cnt\le k\) 好办,直接从其他地方补足即可,随便几个都可以,反正满足。

但反之就不好做了,要删掉多余的,咋办?

不能删掉剩下的那个图的叶子,也不能删除多叉节点,因为删掉叶子就会不满足条件,删除多叉节点就会不止 \(2\) 个连通块了,至少有 \(3\) 个。

所以我们只能删除每一条连续二度链。

而且长度要大于等于 要删除点的个数,因为要不删不了,要不不止 \(2\) 个连通块。

于是我就转化成了链上操作。

进链

对于每一条链,我们可以使用滑动窗口然后依次判断是否可行。

但是依次判断会超时的,最坏情况会有 \(O(n^2)\)。

所以我们需要 \(O(1)\) 判断每个窗口是否可行。

我们可以把整个窗口划分成两个部分,一个部分离左边没删掉的点更近,另一个部分离右边没删掉的点更近,而且以正中间为分割线,于是我们可以把它拆成两个窗口。

于是只要判断左窗口的最右边和右窗口的最左边即可。

于是我们做完了。

呼。汗。

code

你们喜欢的 code 环节:

cpp 复制代码
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

const ll N=1e6+1,INF=0x541254484852;

int c,tc,t,n,k,u,v,deg[N],DEG[N],q[N],in_core[N],h[N];
basic_string<int>E[N];
bool vis[N];

void peel(int dep){
    int l=1,r=0;
	for(int i=1;i<=n;i++)
        DEG[i]=E[i].size(),in_core[i]=h[i]=0;
    for(int i=1;i<=n;i++)
        if(DEG[i]<=1)
            in_core[i]=1,q[++r]=i;
    while(l<=r){
        int u=q[l++];
        for(auto v:E[u]){
            if(in_core[v])
                continue;
            h[v]=max(h[v],in_core[u]),DEG[v]--;
            if(DEG[v]==1&&in_core[u]<dep)
                in_core[v]=in_core[u]+1,q[++r]=v;
        }
    }
}

bool solve_on_the_chain(vector<int>chain,int del,int rad){
	int len=chain.size();
	if(len<del)
		return false;
	int m=len-del+1;
	vector<int>diff(m+1,0);
	for(int i=0;i<len;i++){
		int rem_d=rad-h[chain[i]];
		int lo=max(0,i+rem_d-del+1);
		int hi=min(m-1,i-rem_d);
		if(lo<=hi)
			diff[lo]++,diff[hi+1]--;
	}
	int cur=0;
	for(int l=0;l<m;l++){
		cur+=diff[l];
		if(cur==0)
			return true;
	}
	return false;
}

bool check(int rad){
    peel(rad);
    int cnt=0;
    for(int i=1;i<=n;i++)
        if(!in_core[i])
            cnt++;
    if(cnt<=k)
        return true;
    int rem=cnt-k;
    fill(vis+1,vis+n+1,false);
    for(int i=1;i<=n;i++){
        if(in_core[i]||DEG[i]!=2||vis[i])
            continue;
        int pre=0,start=i;
        vector<int>chain;
        while(true){
            int nxt=-1;
            for(int v:E[start])
                if(!in_core[v]&&DEG[v]==2&&v!=pre){
                    nxt=v;
                    break;
                }
            if(nxt==-1)
                break;
            pre=start,start=nxt;
        }
		pre=0;
		int cur=start;
        while(cur!=0){
            vis[cur]=true,chain.push_back(cur);
            int nxt=0;
            for(int v:E[cur])
                if(!in_core[v]&&DEG[v]==2&&v!=pre){
                    nxt=v;
                    break;
                }
            pre=cur,cur=nxt;
        }
        if(solve_on_the_chain(chain,rem,rad))
        	return true;
    }
    return false;
}

void solve(){
	for(int i=1;i<=n;i++)
		E[i].clear();
	cin>>n>>k;
	for(int i=1;i<n;i++)
		cin>>u>>v,E[u].push_back(v),E[v].push_back(u);
	if(n==k){
		cout<<0<<endl;
		return;
	}
	int l=1,r=n,ans=n;
	while(l<=r){
		int mid=(l+r)>>1;
		if(check(mid))
			r=mid-1,ans=mid;
		else
			l=mid+1;
	}
	cout<<ans<<endl;
}

int main(){
    ios::sync_with_stdio(false);
	cin>>c>>tc;
	while(tc--)
		solve();
}

后记

中途 TLE 了。

但是把 vector 换成 basic_string 后跑的飞快。

这是一个教训。

我调了 37 发呀!

算不算滥用测评啊?

可以顶一下吗?我写了两星期qwq。

不要直接抄啊!我讨厌验证码qwq。

相关推荐
汉克老师1 个月前
CSP-S 2026 初赛试题解析(第一部分:选择题(8-15))精讲
c++·csp-s·小学生·学c++编程
王老师青少年编程5 个月前
信奥赛C++提高组csp-s之搜索进阶(记忆化搜索案例实践3)
c++·记忆化搜索·方格取数·csp·信奥赛·csp-s·提高组
王老师青少年编程5 个月前
信奥赛C++提高组csp-s之搜索进阶(迭代加深IDDFS)
c++·csp·信奥赛·csp-s·提高组·iddfs·埃及分数
王老师青少年编程5 个月前
信奥赛C++提高组csp-s之搜索进阶(记忆化搜索案例实践1)
c++·记忆化搜索·搜索·信奥赛·csp-s·提高组·滑雪
王老师青少年编程5 个月前
信奥赛C++提高组csp-s之搜索进阶(记忆化搜索核心思想)
c++·记忆化搜索·csp·信奥赛·csp-s·提高组·搜索进阶
王老师青少年编程5 个月前
信奥赛C++提高组csp-s之搜索进阶(搜索剪枝案例实践2)
c++·信奥赛·csp-s·提高组·搜索剪枝·生日蛋糕·最优性剪枝
王老师青少年编程5 个月前
信奥赛C++提高组csp-s之FHQ Treap
c++·csp·平衡树·信奥赛·csp-s·提高组·fhq treap
王老师青少年编程5 个月前
信奥赛C++提高组csp-s之平衡树(Treap)
c++·平衡树·treap·信奥赛·csp-s·提高组
君义_noip7 个月前
信息学奥赛一本通 4150:【GESP2509七级】⾦币收集 | 洛谷 P14078 [GESP202509 七级] 金币收集
c++·算法·gesp·信息学奥赛·csp-s