摧毁图状树 - 线段树 - 倍增

8 篇文章 0 订阅
5 篇文章 0 订阅

题目大意:
给你一棵无权树,对于每个k=1…n求:
每次你可以给一个点x到其k级祖先的路径上的所有点打上删除标记。问最少多少次可以把所有点打上删除标记。 n ≤ 1 0 5 n\le10^5 n105
题解:
考虑k怎么做,显然先把叶子涂黑,然后每次涂黑一个点,就把其k级组先拿出来,放进堆里。每次选择堆中深度最大的一个点,若其未被涂黑,则涂黑,并重复上述操作。
于是可以注意到答案是O(x+(n-x)/k)级别的,其中x是叶子个数,原因是,若删除所有贪心过程中选择的点,那么森林的每个联通块的大小不小于k-1。
显然对于每个k都可以做到O(答案),后半部分可以调和级数,前半部分解决办法是只把到叶子最小距离恰好是k的点初始化那个堆即可。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define Rep(i,v) rep(i,0,(int)v.size()-1)
#define lint long long
#define ull unsigned lint
#define db long double
#define pb push_back
#define mp make_pair
#define fir first
#define sec second
#define gc getchar()
#define debug(x) cerr<<#x<<"="<<x
#define sp <<" "
#define ln <<endl
using namespace std;
typedef pair<int,int> pii;
typedef set<int>::iterator sit;
inline int inn()
{
	int x,ch;while((ch=gc)<'0'||ch>'9');
	x=ch^'0';while((ch=gc)>='0'&&ch<='9')
		x=(x<<1)+(x<<3)+(ch^'0');return x;
}
const int N=100010,LOG=20,INF=(INT_MAX/10-10);
struct edges{
	int to,pre;
}e[N<<1];vector<int> v[N];
int h[N],etop,lfcnt,tlf[N],in[N],out[N],d[N],up[N][LOG],Log[N],calc[N],dfc;
inline int add_edge(int u,int v) { return e[++etop].to=v,e[etop].pre=h[u],h[u]=etop; }
int dfs(int x,int fa=0)
{
	tlf[x]=INF,up[x][0]=fa,d[x]=d[fa]+1,in[x]=++dfc;
	rep(i,1,Log[d[x]]) up[x][i]=up[up[x][i-1]][i-1];
	for(int i=h[x],y;i;i=e[i].pre)
		if((y=e[i].to)^fa) tlf[x]=min(tlf[x],dfs(y,x)+1);
	if(tlf[x]==INF) tlf[x]=0,lfcnt++;else v[tlf[x]].pb(x);
	return out[x]=dfc,tlf[x];
}
int kthanc(int x,int k) { for(int i=0;k;k>>=1,i++) if(k&1) x=up[x][i];return x; }
struct segment{
	int l,r,v,ct;
	segment *ch[2];
}*rt;
inline int push_up(segment* &rt) { return rt->v=min(rt->ch[0]->v,rt->ch[1]->v); }
inline int Clr(segment* &rt) { return rt->ct=1,rt->v=INF; }
inline int push_down(segment* &rt) { return Clr(rt->ch[0]),Clr(rt->ch[1]),rt->ct=0; }
int build(segment* &rt,int l,int r)
{
	rt=new segment,rt->l=l,rt->r=r,rt->v=INF,rt->ct=0;
	if(l==r) return 0;int mid=(l+r)>>1;
	return build(rt->ch[0],l,mid),build(rt->ch[1],mid+1,r);
}
int update(segment* &rt,int p,int v)
{
	int l=rt->l,r=rt->r,mid=(l+r)>>1;
	if(l==r) return rt->v=v;
	if(rt->ct) push_down(rt);
	return update(rt->ch[p>mid],p,v),push_up(rt);
}
int query(segment* &rt,int s,int t)
{
	int l=rt->l,r=rt->r,mid=(l+r)>>1;
	if(s<=l&&r<=t) return rt->v;int ans=INF;
	if(rt->ct) push_down(rt);
	if(s<=mid) ans=min(ans,query(rt->ch[0],s,t));
	if(mid<t) ans=min(ans,query(rt->ch[1],s,t));
	return ans;
}
priority_queue<pii> q;
int solve(int k)
{
	while(!q.empty()) q.pop();Clr(rt);int ans=lfcnt;
	Rep(i,v[k]) q.push(mp(d[v[k][i]],v[k][i]));
	while(!q.empty())
	{
		int x=q.top().sec,y;q.pop();
		if(query(rt,in[x],out[x])<d[x]+k||tlf[x]<k) continue;
		update(rt,in[x],d[x]),ans++;
		if(k<d[x]) y=kthanc(x,k),q.push(mp(d[y],y));
	}
	return ans;
}
int main()
{
	int n=inn(),u,v;rep(i,2,n) Log[i]=Log[i>>1]+1;
	rep(i,1,n-1) u=inn(),v=inn(),add_edge(u,v),add_edge(v,u);
	dfs(1),build(rt,1,n),calc[1]=n;int mxd=1;rep(i,1,n) mxd=max(mxd,d[i]);
	for(int q=inn(),k;q;q--)
		if(calc[k=min(inn(),mxd)]) printf("%d\n",calc[k]);
		else printf("%d\n",calc[k]=solve(k));
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值