[POI2004] SZN
Description
给定\(N(N\leq 10000)\)个点的树,要求用最少的路径覆盖树边。路径之间可以有交点,不能有交边。问最少需要几条路径以及在第一问的基础上最长的路径最短是多少?
Solution
第一问对于每个点考虑它与它的孩子之间的边
如果能两两消掉是最好的,贡献就是\(\frac{deg[i]-1}2\),否则就要额外多出一条边,贡献是\(\frac{deg[i]-1}2+1\)。
第二问就是二分答案了
判断\(\leq mid\) 是否可行
可以一遍 \(dfs\) 求出 \(f[i]\) 表示第 \(i\) 个点最短向下延伸多长
如何求这个 \(f[i]\) ?
需要对点的孩子个数分奇偶性讨论
如果它有偶数个孩子,那么最理想的情况就是让他们两两匹配,这个点的 \(f\) 值为 \(0\)。
如果事与愿违(我写个博客怎么都这么多骚话),就可以尝试拿出两个孩子,让一个孩子的链断在这个点,这个点接上另一个孩子的链,其他孩子两两匹配。
如果有奇数个孩子,那就是拿出一个孩子,让这个点接上这个孩子的链,其他孩子两两匹配。
判断两两匹配可以再套个二分。
Code
#include<set>
#include<map>
#include<cmath>
#include<queue>
#include<cctype>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using std::min;
using std::max;
using std::swap;
using std::vector;
const int N=10005;
typedef double db;
typedef long long ll;
#define pb(A) push_back(A)
#define pii std::pair<int,int>
#define mp(A,B) std::make_pair(A,B)
int head[N],f[N];
int n,cnt,g[N],deg[N];
struct Edge{
int to,nxt;
}edge[N<<1];
void add(int x,int y){
edge[++cnt].to=y;
edge[cnt].nxt=head[x];
head[x]=cnt;
}
int getint(){
int X=0,w=0;char ch=0;
while(!isdigit(ch))w|=ch=='-',ch=getchar();
while( isdigit(ch))X=X*10+ch-48,ch=getchar();
if(w) return -X;return X;
}
bool check(int l,int r,int x,int mid){
while(l<r){
if(l==x) l++;
if(r==x) r--;
if(g[l]+g[r]>mid) return 0;
l++,r--;
} return 1;
}
bool dfs(int now,int fa,int x){
int cnts=0;
for(int i=head[now];i;i=edge[i].nxt){
int to=edge[i].to;cnts++;
if(to!=fa) if(!dfs(to,now,x)) return 0;
if(f[to]+1>x) return 0;
} if(cnts==1 and now!=1) return f[now]=0,1;
int pos=0;
for(int i=head[now];i;i=edge[i].nxt){
int to=edge[i].to;
if(to==fa) continue;
g[++pos]=f[to]+1;
} std::sort(g+1,g+1+pos);
if(pos%2==0){ //偶数
int flag=0;
for(int i=1;i<=pos/2;i++)
if(g[i]+g[pos-i+1]>x)
flag=1;
if(!flag) return f[now]=0,1;
if(now==1) return 0;
int l=1,r=pos-1,ans=0;
while(l<=r){
int mid=l+r>>1;
if(g[mid]>x) {l=mid+1;continue;}
if(check(1,pos-1,mid,x)) ans=mid,r=mid-1;
else l=mid+1;
} if(!ans) return 0;
f[now]=g[ans];return 1;
} else{
if(pos==1) return g[1]<=x?f[now]=g[1],1:0;
int l=1,r=pos,ans=0;
while(l<=r){
int mid=l+r>>1;
if(g[mid]>x) {l=mid+1;continue;}
if(check(1,pos,mid,x)) ans=mid,r=mid-1;
else l=mid+1;
} if(!ans) return 0;
f[now]=g[ans];return 1;
}
}
signed main(){
// system("fc 11.out out.txt");
// freopen("11.in","r",stdin);//freopen("out.txt","w",stdout);
n=getint();
for(int i=1;i<n;i++){
int x=getint(),y=getint();
add(x,y);add(y,x);deg[x]++;deg[y]++;
} int ans=1;
for(int i=1;i<=n;i++) ans+=(deg[i]-1)/2;
printf("%d ",ans);
int l=0,r=n-1;ans=0;
while(l<=r){
int mid=l+r>>1;
if(dfs(1,0,mid)){
if(f[1]<=mid) ans=mid,r=mid-1;
else l=mid+1;
}
else l=mid+1;
} printf("%d\n",ans);
return 0;
}//youngneal
当你走进这欢乐场