倍增啦啦啦

不得不学习一手啦,上次比赛就因为这个少了一堆分呜呜呜,先学树上的lca啦。呃呃呃,就,先预处理一下for(int i=1;i*2<=dep[x];i++) db[x][i]=db[db[x][i-1]][i-1];这一句,然后就可以直接计算,跳来跳去的啦。
但我们在跳的时候不能直接跳到它们的LCA,因为这可能会误判,比如4和8,在跳的时候,我们可能会认为1是它们的LCA,但1只是它们的祖先,它们的LCA其实是3。所以我们要跳到它们LCA的下面一层,比如4和8,我们就跳到4和5,然后输出它们的父节点,这样就不会误判了。(别人的图欸)在这里插入图片描述
诶呀太复杂,其实就是跳到最上面的那个,不是两个点的公共祖先的那个点,懂否?懂!奇妙抽风

//感觉倍增的是其长,本质上是到根之距,若有则有必有,若无则无
//重点乃从大到小枚举 
//可恶,1<<i的意思是2^i啊 nmd是将1左移i位 
//小知识:倍增英文是double
#include<bits/stdc++.h>
using namespace std;
int n,m,s;
int db[520000][21];//db[i][j]存的[i]向上走2的j次方那么长的路径,就是爹 
int dep[520000];//深度 
int last[520000]; 
int len=0;
struct pp
{
	int x,y,next;
};
pp p[1020000];
void ins(int x,int y)
{
	int now=++len;
	p[now]={x,y,last[x]};
	last[x]=now;
	return ; 
}
void dfs(int x,int fa)
{	
	dep[x]=dep[fa]+1;db[x][0]=fa;
	for(int i=1;(1<<i)<=dep[x];i++) db[x][i]=db[db[x][i-1]][i-1];
	//这一句就是典型的倍增啦,你看2^(i-1)*2^(i-1)==z^i对吧
	//所以其实就可以快速的得到它爹爹爹的爹那一堆的东西啦,构思一个图?
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;
		if(y==fa) continue;
		dfs(y,x);
	} 
	return ;
}
//找到某个位置i,在这个位置时,f[a][i]!=f[b][i],但再向上移动一步,a,b相同了 
//从log2n开始从大到小枚举i,如果超过了a,b的高度,则令i继续减小
//如果没有超过a,b的高度,那么就判断移动了后会不会让a==b,
//是,则i继续减小,否则,令此时的a=f[a][i],b=f[b][i]; 
int lca(int x,int y)
{
	if(dep[x]>dep[y]) swap(x,y);
	for(int i=20;i>=0;i--) 
	{
		if(dep[x]<=dep[y]-(1<<i)) y=db[y][i];//将其转到同一深度
		//其实 数个二的次方的和一定可以构造出任何的数,所以任何深度都是一定可以构造出来的
		//但i枚举的既是深度,也是转移的位置,当然是有大选大啦 
	}
	if(x==y) return x; 
	for(int i=20;i>=0;i--)
	{
		if(db[x][i]==db[y][i]) continue;
		x=db[x][i];y=db[y][i];
	}
	return db[x][0];
}
int main()
{
	memset(last,-1,sizeof(last));
	scanf("%d%d%d",&n,&m,&s);
//for(int i = 1; i <= n; ++i)
//		lg[i] = lg[i-1] + (1 << lg[i-1] == i)
	for(int i=1;i<=n-1;i++) 
	{
		int x,y;scanf("%d%d",&x,&y);
		ins(x,y);ins(y,x);
	}
	dep[0]=0;dfs(s,0);
	for(int i=1;i<=m;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		printf("%d\n",lca(x,y));
	 } 
	return 0;
}

这也差不多啦,难,The merchant 呃poj上的一道我不理解的题目欸,难难难,问题不大,1h ko它。
我们可以用倍增求lca的思路来写用一个Max[i][j]数组记录从 i 到 i 的第2j个祖先的这段位置的最大值
来写用一个Min[i][j] 数组记录从 i 到 i 的第2j个祖先的这段位置的最小值
当然我们也要记录他向上或者向下走的时候的能获得的最大利润
up[i][j]表示从 i 到 i 的第2j个祖先 的这段位置能获得的的最大值
down[i][j]表示从i 的第2j个祖先到 i 的这段位置能获得的的最大值
然后就可以维护其路径的值啦。
具体而言对于一个(u,v)的路径,一共会有三种情况,设其最近公共祖先是f,则可能:
1,最小值在u到f上,最小值在f到v上;
2,最小值与最大值都在u到f上;
3,最小值与最大值都在f到v上。
这三种,第一种明显好维护,第二三种懒得说。
然后这题对一些二进制的利用确实是妙极了。比如说

if(f&1<<i))这一句

就非常的有趣就是当有相同位置的1的时候才能返回true,其他都是false。相当于if(f-(1<<i)>=0) f-=(1<<i) 这一类的话。

#include<stdio.h>
#include<algorithm>
#include<vector>
#include<math.h>
#include<string.h>
#define int long long
using namespace std;
int n,m;
int len=0,last[200001];
int ans=0;
struct pp
{
	int x,y,next;
};
pp p[200001];
int db[200001][30],dep[200001];
int a[200001];
int maxx[200001][30],minn[200001][30],up[200001][30],down[200001][30];
void ins(int x,int y)
{
	int now=++len;
	p[now].x=x;
	p[now].y=y;
	p[now].next=last[x];
	last[x]=now;
	return ; 
}
void dfs(int x,int fa)
{
	dep[x]=dep[fa]+1;db[x][0]=fa;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;
		if(y==fa) continue;
		maxx[y][0]=max(a[x],a[y]);minn[y][0]=min(a[x],a[y]);//y到x的最大值与最小值 
		up[y][0]=max(0ll,a[x]-a[y]);//如果从i这个位置向上走到它的父亲这段距离能获得的最大利润 
		down[y][0]=max(0ll,a[y]-a[x]);//从他的父亲走到他能获得的最大利润 
		dfs(y,x);
	}
	return ; 
}
void update()
{
	for(int j=1;j<=20;j++)
	{
		for(int i=1;i<=n;i++)
		{
			db[i][j]=db[db[i][j-1]][j-1];
			int f=db[i][j-1];
			maxx[i][j]=max(maxx[i][j-1],maxx[f][j-1]);
			minn[i][j]=min(minn[i][j-1],minn[f][j-1]);
			//因为倍增求祖先节点的时候是跳跃式的求得
			//所以这里需要对这连段的数值进行分情况讨论 
			//就是之前两端已经分别处理好了对不对,就像cdq分治一样,我们所要做的只不过是合并两段区间呢qwq;明天就能回家啦哈哈哈 
			//如果是合并两段区间,那当然是后面那一段的最大值减去前面那一段的最小值啦 
			up[i][j]=max(maxx[f][j-1]-minn[i][j-1],max(up[i][j-1],up[f][j-1]));
			down[i][j]=max(maxx[i][j-1]-minn[f][j-1],max(down[i][j-1],down[f][j-1])); 
		}
	}
	return ;
}
int lca(int x,int y)
{
	if(dep[x]>dep[y]) swap(x,y);
	for(int i=20;i>=0;i--) 
	{
		if(dep[x]<=dep[y]-(1<<i)) y=db[y][i];
	}
	if(x==y) return x; 
	for(int i=20;i>=0;i--)
	{
		if(db[x][i]==db[y][i]) continue;
		x=db[x][i];y=db[y][i];
	}
	return db[x][0];
}
int ask(int y,int x,int op)
{
	int mi=a[x],ma=a[x];//最小值与最大值(路径上的) 
	int f=dep[x]-dep[y];//记录的是两点之间的距离 
	for(int i=0;(1<<i)<=f;i++)
	{
		if(f&(1<<i)) //这是一步看起来高端的操作,当然你也可以f-(1<<i)>=0这样做只不过这个看起来nb 
		{
			if(op==1)//为什么只改最小值呢,你想想得先拿那小的对吧,那肯定小的在大的前面,所以保存最小值就好啦 
			{
				ans=max(ans,max(up[x][i],maxx[x][i]-mi)); 
				mi=min(mi,minn[x][i]);
			} 
			else //和上面反着来呗 
			{
				ans=max(ans,max(down[x][i],ma-minn[x][i]));
				ma=max(ma,maxx[x][i]);
			}
			x=db[x][i];
		}
	}
	if(op==1) return mi;
	else return ma;	
}
signed main() 
{
	memset(last,-1,sizeof(last));
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=1;i<=n-1;i++)
	{
		int x,y;scanf("%lld%lld",&x,&y);
		ins(x,y),ins(y,x);
	}
	dfs(1,0);update();
	int q;scanf("%lld",&q);
	while(q--)
	{
		int x,y;scanf("%lld%lld",&x,&y);
		int f=lca(x,y);ans=0;
		ans=max(ans,ask(f,y,2)-ask(f,x,1));
		printf("%lld\n",ans);
	}
	return 0;
}

国旗计划这道奇怪的倍增题啦,本来没看出来的,不过还是题解强啊,我直接大彻大悟,其实就是一段首尾相连的区间,要求他们全部被覆盖且互相不相交(尤其是首尾要被同一段覆盖好像又不用了),嗯所以我们考虑断环为链,若要求i点开始,则i+m必定是结束,考虑建边,若l<=r,我们把它断成两个区间[l,r],[l+M,r+M],否则断成[l,r+M],[l+M,r+M+M] (断成[l+M,2M]也可以),算了这道题到现在还是没看懂其建边方式,难受。
贴个别人的码,我不懂啊~

#include<iostream>
#include<cstdio>
#include<cstring> 
#include<cmath>
#include<algorithm> 
#define INF 0x3f3f3f3f
#define maxn 2000000
#define maxlogn 25
using namespace std;
int n,len;
struct seg{
	int l;
	int r;
	int id;
	seg(){
		
	}
	seg(int _l,int _r,int _id){
		l=_l;
		r=_r;
		id=_id;
	}
	friend bool operator  < (seg p,seg q){
		if(p.l==q.l) return p.r<q.r;
		else return p.l<q.l;
	}
}a[maxn+5]; 
int sz;
int log2n; 
int ans[maxn+5];
int anc[maxn+5][maxlogn+5];
 
int query(int x){
	int ans=1;
	int r=a[x].l+len; //注意边界,比如3->5,5->1,1->3.必须要跳回原点3,所以是+len而不是+len-1 
	for(int i=log2n;i>=0;i--){
		if(anc[x][i]!=0&&a[anc[x][i]].r<=r){
			ans+=(1<<i); 
			x=anc[x][i];
		}
	}
	if(anc[x][0]&&a[x].r<r){
		ans++;
		x=anc[x][0];
	}
	return ans; 
}
int main(){
	int l,r;
	scanf("%d %d",&n,&len);
	log2n=log2(n*2);
	for(int i=1;i<=n;i++){
		scanf("%d %d",&l,&r);
		if(l<=r){
			a[++sz]=seg(l,r,i);
			a[++sz]=seg(l+len,r+len,i+n);
		}else{
			a[++sz]=seg(l,r+len,i);
			a[++sz]=seg(l+len,r+len+len,i+n);
		}
	}
	sort(a+1,a+1+sz);
	int ptr=1;
	for(int i=1;i<=sz;i++){
		while(ptr<sz&&a[ptr+1].l<=a[i].r) ptr++;
		if(ptr!=i) anc[i][0]=ptr;
	}
	for(int j=1;j<=log2n;j++){
		for(int i=1;i<=sz;i++){
			anc[i][j]=anc[anc[i][j-1]][j-1];
		}
	}
	for(int i=1;i<=sz;i++){
		if(a[i].id<=n) ans[a[i].id]=query(i);//注意要跳过(l+n,r+n),否则l+len会超过2*len导致答案错误 
	}
	for(int i=1;i<=n;i++) printf("%d ",ans[i]);
}
 

好吧后面还是去补了,受不了良心有点痛qwq,那代码就放下面?
首先那个断环为链,是这样断的,我一直都没搞懂它为什么要成r+m+m(上面那一位)欸它是没用的哈哈哈哈哈。呜呜呜,直接枚举的时间是无法接受的,所以我们采用建边倍增的方式:去找每个士兵能奔袭的最远的边防站,使得最少的士兵需要奔袭。从一名战士开始,一直到某一名战士的左端点大于这名战士的右端点,那么这名战士区间内奔袭过的最远的战士找到了,理解其实就是呃那个,我们设 f(i, j) 表示第 ii 个战士奔袭 2^j步到达的边防站,等下,是不是有个问题?好像也不是问题。其实不是步啊,是2 ^j个人,最终目的是到达自己呀。
在这里插入图片描述
注意看,这一段,好的代码,大佬的:

void pre()
{
	for(int i = 1, p = i; i <= 2 * n; i++) //枚举一下位置,
	{
		while(p <= 2 * n && s[p].l <= s[i].r)//如果当前的左端点能往前推
			p++;
		int pos = p - 1;//
		go[i][0] = pos;
	}
	for(int i = 1; i < 20; i++) {//这两个for循环顺序一定不能换!!!!
		for(int j = 1; j <= 2 * n; j++) {
			go[j][i] = go[go[j][i - 1]][i - 1];
		}
	}
}

我写的呢(>,<)

#include<bits/stdc++.h>
using namespace std;
int n,m;
int len=0;
int f[400001][21];
struct node 
{
	int l,r,id;
};
node s[400001];
int ans[400001];
bool cmp(const node &x,const node &y)
{
	return x.l<y.l;
}
void solve()
{
	//并且你看,l是有序的嘛,则其实r也是有序的哦,则在自己右端点内最右的左端点的那个人肯定是能跑最远的啦
	//其实就像那一篇题解说的,找从一名战士开始,(因为没有全覆盖) 
	//一直到某一名战士的左端点大于这名战士的右端点,那么这名战士区间内奔袭过的最远的战士找到了 
	//时间很优秀欸,而且2^i枚举的其实是跑过的战士啦 ,也不能这样说,可恶,自己想去吧
	for(int i=1,p=i;i<=n*2;i++)//枚举每一个点前面能接上来的咯 
	{
		while(p<=n*2&&s[p].l<=s[i].r) p++;//因为l是升序,所以可以这样搞
		//这里就是p是i的下一位兄台啦
		int pos=p-1;f[i][0]=pos;
	}
	for(int i=1;i<=19;i++) 
	{
		for(int j=1;j<=2*n;j++) f[j][i]=f[f[j][i-1]][i-1];
		//这一个继承,好好好!
	}
	return ;
}
void find(int x)
{
	int st=x,ed=s[x].l+m,sum=1; 
	for(int i=19;i>=0;i--)
	{
		if(f[st][i]!=0&&s[f[st][i]].r<ed) 
		{
			sum+=(1<<i);//接力了多少位兄台
			st=f[st][i]; 
		}
	}
	ans[s[x].id]=sum+1;
	return ;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) 
	{
		scanf("%d%d",&s[i].l,&s[i].r);
		if(s[i].r<s[i].l) s[i].r+=m;
		s[i].id=i;
	}
	sort(s+1,s+n+1,cmp);
	for(int i=1;i<=n;i++) s[i+n].id=s[i].id,s[i+n].l=s[i].l+m,s[i+n].r=s[i].r+m;
	solve();
	for(int i=1;i<=n;i++) find(i);
	for(int i=1;i<=n;i++) printf("%d ",ans[i]);
	return 0;
}

趁热嘛,P5465 [PKUSC2018]星际穿越,哦对这些题lougu上都有的,这题看起来蛮有趣的,可以看一下第一篇大佬的题解(https://www.luogu.com.cn/problem/solution/P5465),具体思路晚点说。

晚点了,我完蛋了根本看不懂哦好逊,呃考虑维护一个前缀和,具体而言,是sum[i][j] ,指的是从i开始,到 i - j 的最小步数的和,则答案即 1/(r - l + 1)* (sum[l][i]-sum[r+1][i] )对吧,现在就可以维护前缀和,那怎么搞呢,考虑一个点x,(第一次)最小能跳到 l [ x ], 最大呢因为是双向边,所以应该是满足 l[y]<=x&&y>x 这一个式子的max(y),然后第二次跳跃,可以到的点会多,最小的点则是min(l [ x ] )(l [ i ] <=x<= y)对吧,所以还是看看别人的吧,那你就可以直接得出这一个个柿子啦。然后我们需要谈一下最后一段的那两个式子,f明显表示的是从前位置 i 跳 2^j 步所能到达的最小距离,所以转移就直接转移,然后我们用g来表示从 f [ i ] [ j ] 到 i 这一个所能到达的点的最小步数和,假设每个点都从第二步走起(也就是第一步可能选择最大值或者最小值),我们可以强行处理完第一步的向前走(令他取l [ x ] ),然后再谈第二步的转移。也许可以这样理解?

在这里插入图片描述
现在有一个问题为什么if(x>to) ans+=tot*(x-to)+x-to;有这一句?
其实是因为可能所有的f [ x ][ i ]都超了所以要算一遍最后的一步,且可以一步到位嘛
上代码:

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int l[300001],f[300001][30],sum[300001][30]; 
int n,m;
int getsum(int x,int to)
{
	if(l[x]<=to) return x-to;//如果一步搞定,就返回,所以下面的操作数明显会大于1 
	int ans=x-l[x],tot=1;//强行走了一步呀 
	x=l[x];//强制其转移至前面那一个点捏 
	for(int i=20;i>=0;i--)
	{
		if(f[x][i]>=to)
		{
			ans+=tot*(x-f[x][i])+sum[x][i];//算的是向前转移i步后还有tot才能跳到起点
			//感性想想,在(x-f[x][i])这些点,是不是都可以通过tot步从起点转移过去,所以值就是这样
			//sum的话不多解释,自己想想
			tot+=(1<<i);x=f[x][i]; 
		}
	} 
	if(x>to) ans+=tot*(x-to)+x-to;
	return ans;
}
//现在有一个问题为什么 if(x>to) ans+=tot*(x-to)+x-to; 要加上去?
//明明已经可以走到终点了 
//噢噢噢我懂了,其实是它所有的最小点都超出了to,所以其实最后一步也可以1步转移 
signed main()
{
	scanf("%lld",&n);
	for(int i=2;i<=n;i++) scanf("%lld",&l[i]);
	f[n][0]=l[n],sum[n][0]=n-l[n];
	for(int i=n-1;i>=2;i--)
	{
		//可能上面那个点走的更远,这个时候就用到了那个公式,就那个a与b那玩意 
		f[i][0]=min(f[i+1][0],l[i]);
		sum[i][0]=i-f[i][0];//每一点都可以一步到达呀 
	}
	for(int j=1;j<=20;j++)
	{
		for(int i=(1<<j);i<=n;i++)
		{
			f[i][j]=f[f[i][j-1]][j-1];//转移最远能走到的点 
			sum[i][j]=sum[i][j-1]+sum[f[i][j-1]][j-1]+(1<<j-1)*(f[i][j-1]-f[i][j]);
			//这样转移是为什么捏,首先两边的前缀和是分开算的,得加在一起
			//后面那一句是,有这么长的一段 (f[i][j-1]-f[i][j]) 然后向前转要转移 (1<<j-1)次 才能到 f[i][j]
			//大概吧 
		}	
	}
	int q;scanf("%lld",&q);
	while(q--)
	{
		int L,R,X;scanf("%lld%lld%lld",&L,&R,&X);
		int ans=getsum(X,L)-getsum(X,R+1),cs=R-L+1,gg=__gcd(ans,cs);
		printf("%lld/%lld\n",ans/gg,cs/gg);
	}
	return 0;
}

一道小绿题,感觉不是很难:P7167 [eJOI2020 Day1] Fountain。首先读懂题目,然后采用图的方式来搞,利用单调栈+建边将其转化为树,明显的,构造成树后的维护可以用倍增,不然你学这东西干嘛,怎么维护呢,你看完前面两题发现这个不用说都会,那就不用说吧,开打。哦注意一下建的树应该是从下往上咯。具体方式看代码:

//吐槽:别人的代码打法很奇怪,我很裂开,评价是不如自己打 
#include<bits/stdc++.h>
using namespace std;
int q[400001];
int n,m,Q;
int d[100001],c[100001],f[100001][30],db[100001][30];
int dep[100001];
int len=0,last[200001];
struct pp
{
	int x,y,next;
};pp p[400001];
void ins(int x,int y)
{
	int now=++len;
	p[now]={x,y,last[x]};
	last[x]=now;
	return ;
}
void dfs(int x,int fa)
{	
	dep[x]=dep[fa]+1;db[x][0]=fa;f[x][0]=c[fa];
	for(int i=1;(1<<i)<=dep[x];i++)
	{
		db[x][i]=db[db[x][i-1]][i-1];
		f[x][i]=f[x][i-1]+f[db[x][i-1]][i-1];
	}
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;
		if(y!=fa) dfs(y,x);
	}
 	return ;
}
int find(int x,int v)
{
	if(c[x]>=v) return x;
	int ans=0;v-=c[x];
	for(int i=20;i>=0;i--)
	{
		if(f[x][i]<v&&dep[x]>=(1<<i))
		{
			v-=f[x][i];
			x=db[x][i];
		}
	}
	if(ans==0) ans=db[x][0];
	if(ans==n+1) return 0;
	else return ans;//?sb吧 
}
int main()
{
	memset(last,-1,sizeof(last));
	scanf("%d%d",&n,&Q);
	for(int i=1;i<=n;i++) scanf("%d%d",&d[i],&c[i]);
	d[n+1]=1e9+1,c[n+1]=1e9+1;
	int tot=0;q[++tot]=n+1;
	for(int i=n;i>=1;i--)//好的单调栈 
	{
		while(tot>0&&d[i]>=d[q[tot]]) tot--;
		ins(q[tot],i),ins(i,q[tot]);
		q[++tot]=i;
	}
	dfs(n+1,0);
	while(Q--)
	{
		int x,v;scanf("%d%d",&x,&v);
		printf("%d\n",find(x,v));
	}
	return 0;
}

还是看看别的小绿题吧,P5648 Mivik的神力,呃啊坏的题面感觉起来很阴间,草,这个题目感觉见过不知道是不是打过可能会被卡长,怎么感觉这个cdsn这么卡wc,打字太慢了不知道为什么我重启过了啊,算了不管了,,可以先预处理出每一个数后面那一个大于它的数,这样其实可以拿倍增做,这玩意怎么还在卡,我们可以拿f记录答案,拿net记录位置,具体看代码,听说这样可能会被卡常,不过其实时间复杂度近乎正确。

#include<bits/stdc++.h>
#define int long long 
//首先没搞懂为什么要n+2 n+1不行是吧真的是 
//而且这个maxx的判断把我人搞裂开了 
using namespace std;
int n,m,ans=0,maxx;
int f[510000][30],net[510000][30];
int a[510000];
int q[510000],tot=0;
int find(int x,int y)
{
	int rt=0,pos=x;ans=0;
	for(int i=maxx;i>=0;i--)
	{
		if(net[pos][i]-1>y) continue ; 
		rt+=f[pos][i];
		pos=net[pos][i];	
	}
	rt+=a[pos]*(y-pos+1); 
	return rt;
}
signed main()
{
	scanf("%lld%lld",&n,&m);maxx=log2(n)+1;
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++)
	{
		while(tot&&a[q[tot]]<a[i]) net[q[tot--]][0]=i;
		q[++tot]=i;
	}
	while(tot) net[q[tot--]][0]=n+2;	
	net[n+1][0]=net[n+2][0]=n+2;
	for(int i=1;i<=n;i++) f[i][0]=a[i]*(net[i][0]-i);//这一步至关重要,记录一下一次的值 
	
	for(int j=1;j<=maxx;j++)
	{
		for(int i=1;i<=n+2;i++) net[i][j]=n+2;//赋值成n+2,不然n+1会有问题 
		for(int i=1;i+(1<<j)-1<=n;i++)	
		{
			net[i][j]=net[net[i][j-1]][j-1];
			f[i][j]=f[i][j-1]+f[net[i][j-1]][j-1];
		}
	}
	while(m--)
	{
		int u,v;scanf("%lld%lld",&u,&v);
		int l=1+(u^ans)%n,r=(v^(ans+1))%(n-l+1)+l;//printf("%d %d\n",l,r);
		ans=find(l,r);printf("%lld\n",ans);
	}
	return 0;
}

做一个大佬推荐的题:P8163 [JOI 2022 Final] 铁路旅行 2 (Railway Trip 2),先读一读题目看起来非常的棘手,呃具体而言,累了明天补,现在去做差分。
时隔多天,之前欠的题还没补就做新的了QWQSplit the Tree看上去挺难的,先yy一下,因为是查的是倍增题,所以决定倍增,再看,考虑从dp入手,可以先预处理出每一个点最多能向上走多远,既然这样了,不妨设f[i]是以i被覆盖时最多能向上走多少,ans[i] 指以i为根的话需要的覆盖边数。那么转移就可以直接从儿子节点转移上来了,f [ x ] =max ( f [ y ] - 1) ,那么ans也可以转移。这其实是一种贪心,每次贪心的向上选取就可以了。

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,L,S;
int f[200001],ans[200001],val[200001],dep[200001],sum[200001];
int tot=0,last[200001],db[200001][30],len[200001];
struct pp
{
	int x,y,next;
};pp p[4000001];
void ins(int x,int y)
{
	int now=++tot;
	p[now]={x,y,last[x]};last[x]=now; 
	return ;
}
void getdb(int x,int fa)
{
	dep[x]=dep[fa]+1;db[x][0]=fa;sum[x]=sum[fa]+val[x];
	for(int i=1;(1<<i)<=dep[x];i++) db[x][i]=db[db[x][i-1]][i-1];
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		getdb(y,x);
	}
	int now=x;
	for(int i=20;i>=0;i--)
	{
		if(dep[now]-(1<<i)>=1&&sum[x]-sum[db[db[now][i]][0]]<=S&&dep[x]-dep[db[db[now][i]][0]]<=L) now=db[now][i];
		//前缀和处理错了,难受~
	}
	len[x]=dep[x]-dep[now];
	return ;
}
void dfs(int x,int fa)
{
	f[x]=-1;ans[x]=0;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		dfs(y,x);ans[x]+=ans[y];f[x]=max(f[x],f[y]-1);
	}
	if(f[x]==-1) 
	{
		ans[x]++;f[x]=len[x];
	}
	return ;
}
signed main()
{
	memset(last,-1,sizeof(last));memset(dep,0,sizeof(dep));
	scanf("%lld%lld%lld",&n,&L,&S);
	if(L==0) 
	{
		printf("-1");
		return 0;
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&val[i]);
		if(val[i]>S) 
		{
			printf("-1");
			return 0;	
		}  
	}
	for(int i=2;i<=n;i++) 
	{
		int x;scanf("%lld",&x);
		ins(x,i);ins(i,x);
	}
	getdb(1,0);dfs(1,0);
	printf("%lld",ans[1]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值