【题解】CF1039D
题目描述
有一棵 \(n\) 个节点的树。
其中一个简单路径的集合被称为 \(k\) 合法当且仅当:
树的每个节点至多属于其中一条路径,且每条路径恰好包含 \(k\) 个点。
对于 \(k\in [1,n]\),求出 \(k\) 合法路径集合的最多路径数
即:设 \(k\) 合法路径集合为 \(S\),求最大的 \(|S|\)。
\(n \leq 10^5\)。
题解
其实观察到要求所有的东西而不能搞个什么扫描线跑跑什么的就可以观察答案的分布了。
于是可以考虑根号分治,长度小于\(\sqrt n\)的可以暴力跑,长度大于\(\sqrt n\)的可以观察到答案不超过\(\sqrt n\)种,可以二分出端点。
这题比较卡常,下面是几个卡常技巧:
- 前向星存图
- 只保留fa到son的边
- 快写垃圾的别写
- 记忆化
- dfs可以拍成dfs序后倒着跑
- 调整块长
#include<bits/stdc++.h>
using namespace std;
inline int rd(){
int f=1,j=0;
char w=getchar();
while(!isdigit(w)){
if(w=='-')f=-1;
w=getchar();
}
while(isdigit(w)){
j=j*10+w-'0';
w=getchar();
}
return f*j;
}
const int N=100010;
int head[N],to[N*2],fro[N*2],tail;
int ans[N];
int n,K,Ans,ma[N];
int Siz,sum[N][3];
int fa[N],dfn[N],cnt;
inline void adlin(int x,int y){
to[++tail]=y,fro[tail]=head[x],head[x]=tail;
return ;
}
vector<int>nex[N];
void init(int u){
dfn[++cnt]=u;
for(int v:nex[u]){
if(v==fa[u])continue;
fa[v]=u;
adlin(u,v);
init(v);
}
return ;
}
void dfs(){
for(int i=n;i>=1;i--){
int u=dfn[i];
for(int k=head[u];k;k=fro[k]){
int x=to[k];
sum[u][2]=ma[x];
if(sum[u][2]>=sum[u][1])swap(sum[u][2],sum[u][1]);
if(sum[u][1]>=sum[u][0])swap(sum[u][1],sum[u][0]);
}
if(sum[u][0]+sum[u][1]+1>=K)Ans++,ma[u]=0;
else ma[u]=sum[u][0]+1;
}
return ;
}
int getans(int k){
for(int i=1;i<=n;i++){
ma[i]=0;
for(int j=0;j<=2;j++)sum[i][j]=0;
}
K=k,Ans=0;
dfs();
return Ans;
}
signed main(){
n=rd();
Siz=min((int)sqrt(n)*3,n);
// cout<<"Siz:"<<Siz<<"\n";
for(int i=1;i<n;i++){
int x=rd(),y=rd();
nex[x].emplace_back(y);
nex[y].emplace_back(x);
}
init(1);
// for(int i=1;i<=n;i++)cout<<dfn[i]<<"\n";
// for(int i=1;i<=n;i++)ans[i]=getans(i);
int fro=0;
for(int i=1;i<=Siz;i++)ans[i]=getans(i),fro=max(fro,ans[i]);
for(int L=Siz+1,R;L<=n;L=R+1){
int l=L,r=n,mid;
if(!ans[L])ans[L]=getans(L);
while(l<=r){
mid=(l+r)/2;
if(!ans[mid])ans[mid]=getans(mid);
if(ans[mid]<ans[L])r=mid-1;
else l=mid+1;
}
R=r;
for(int i=L;i<=R;i++)ans[i]=ans[L];
}
ans[0]=INT_MAX;
for(int i=1;i<=n;i++)printf("%d\n",ans[i]);
return 0;
}

浙公网安备 33010602011771号