差分哈哈哈

差分的概念是b[1]=a[1],b[i]=a[i]-a[i-1]

差分数组有着一些令人着迷的性质
1.A数组的差分数组的前缀和数组就等于原数组,
2.Sum数组的差分数也等于原数组,即 A[i]=Sum[i]−Sum[i−1]
3.还有就是对差分的修改相当于单点修改啦只要改首尾两个点就好啦

第一题P5026 Lycanthropy,本来想的是有个线段树处理一下的不过直接看了题解,人家怎么说呢,人家直接用二次前缀和呀笑死我了,少了一堆代码可恶啊愚蠢的yuong man,呃呃呃这个差分,听听大佬的话吧:既然它递增/递减是均匀的,那么我们可以考虑先用差分维护某一点的水位与上一点的差值,很显然维护的差值也是一个差分。差分还原成原值需要求前缀和,所以第一次差分之后跑两遍前缀和即可。
哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈我是sb QWQ。

#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[2000001];
int sum[2200000];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		int x,v;
		scanf("%d%d",&v,&x);
		sum[x-3*v+1+1000000]++;
		sum[x-2*v+1+1000000]-=2;
		sum[x+1+1000000]+=2;	
		sum[x+2*v+1+1000000]-=2;
		sum[x+3*v+1+1000000]++;
	}
	for(int i=1;i<=2000001;i++) sum[i]+=sum[i-1],a[i]+=sum[i]+a[i-1];//超级前缀和!强强强
	for(int i=1000001;i<=m+1000000;i++) printf("%d ",a[i]);
	return 0;
}

下一个是P4231 三步必杀这一个啦,比上面那个难一点哦,维护的是等差数列,怎么办呢先看出来左右两个数的差值是相等的,然后想如何维护个差值数列,嘿嘿在df描述对嘛所以就可以维护改值那一点的右右两点,其实就相当于维护差分队列上的单点改制嘛简简单单啦。其实想了一会才想明白,若一个差分维护的是差值,则要让数组向后推一位才行。大概是这样吧(懒ing)
a 0 2 7 12 17 0 0 …
b 0 2 5 5 5 -17 0 …
c 0 2 3 0 0 -22 17 …
代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
int sum1[20000001],a[20000001],sum2[20000001];
signed main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int l,r,s,e,d;
		scanf("%lld%lld%lld%lld",&l,&r,&s,&e);d=(e-s)/(r-l);
		if(r==l) d=0;
		a[l]+=s;a[l+1]+=d-s;//维护第一个点初始值,第二个点的公差 
		a[r+1]-=(e+d);a[r+2]+=e;//最后一个点的差值与后面那一个的差值?考虑一下 
	}	
	int ans1=a[1],ans2=a[1];sum1[1]=sum2[1]=a[1];
	for(int i=2;i<=n;i++)
	{	
		sum1[i]=sum1[i-1]+a[i];sum2[i]=sum2[i-1]+sum1[i];
		ans1^=sum2[i],ans2=max(ans2,sum2[i]);
	}
	printf("%lld %lld",ans1,ans2);
	return 0;
}
}

感觉开始逐步掌握窍门了,主要是式子会比较难推一点,还有一些定义的问题没搞清。P3948 数据结构这个的话好像不是很难欸哈哈哈:蓝色给高了啦。

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m;
int T,mod,minn,maxx;
int df[8000001],ans[8000001];
int ask(int l,int r)
{
	int rt=0,now=0;
	for(int i=1;i<=r;i++)
	{
		now+=df[i];
		if(i>=l&&(now*i)%mod>=minn&&(now*i)%mod<=maxx) rt++;
	}
	return rt;
}
signed main()
{
	memset(ans,0,sizeof(ans));memset(df,0,sizeof(df));
	scanf("%lld%lld%lld%lld%lld",&n,&m,&mod,&minn,&maxx);
	while(m--)
	{
		char p[10];scanf("%s",p+1);
		if(p[1]=='A')
		{
			int l,r,x;scanf("%lld%lld%lld",&l,&r,&x);
			df[l]+=x;df[r+1]-=x;	
		}
		else 
		{
			int l,r;scanf("%lld%lld",&l,&r);
			printf("%lld\n",ask(l,r));
		}
	}
	for(int i=1;i<=n;i++)
	{
		df[i]+=df[i-1];
		if((df[i]*i)%mod>=minn&&(df[i]*i)%mod<=maxx) ans[i]=1;
		ans[i]+=ans[i-1];
	}
	scanf("%lld",&m);
	while(m--) 	
	{
		int l,r;scanf("%lld%lld",&l,&r);
		printf("%lld\n",ans[r]-ans[l-1]);
	}
	return 0;
}

下一题P4552 [Poetize6] IncDec Sequence这题看出来怎么做了欸,我真棒,不过第二问还是没怎么懂,第一问的就是用到一个差分的性质,好的理解!怎么说呢,就是算了懒。因为这个我们的目的是要让差值归0即每一个数(差分数组中)都变成0,所以一共可以有三种操作:
1.单个数字加,那就只改变一个数字的大小;
2.单个数字减,那也是改变上面那东西;
3.还有可以:选取一个正数(X)和一个负数(Y),使正数减1,负数加1。
那就3是最优的嘛,然后选不到同时有正负数就只能选1和2了啦。
所以最少的次数当然是正数的数字和与负数的数字和中的较大值。
然后操作数最少时的结果,嗯,得到的数列有多少种,其实就是问的第一个数可以有多少种,我们上述所有操作是与b[1]无关的,因为我们的目标是让除了b[1]以外的项变0,所以我们上述的操作没有考虑到b[1],b[1]怎么变,与我们求出的最小步骤无关那么,我们怎么知道b[1]有几种呢?很简单,其实就是看看有几种一步步减或一步步加的操作数,因为我们一步步加的时候(假设我们现在的操作对象下标为i),可以这样操作,b[1]-1,b[i]+1一步步减的时候可以这样操作,b[1]+1,b[i]-1(注意,一个差分序列里一步步操作的时候只可能一步步加或一步步减,不可能一步步加和一步步减同时存在)所以说,有几步一步步的操作就有几种情况+1,为什么+1呢,因为这个b[1]本身就有一个值啊!就算你不对他进行任何操作,它自己也有一种情况。所以第二个问题的答案就是max(p,q)- min(p,q)+1=abs(p - q)+1;(https://www.luogu.com.cn/problem/solution/P4552)第一篇题解讲得好啊。
代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
int a[200000],df[200000];
int ans1=0,ans2;
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),df[i]=a[i]-a[i-1];
	int q=0,p=0;
	for(int i=2;i<=n;i++) 
	{	
		if(df[i]>=0) q+=df[i];
		else p+=df[i];
	}
	p=-p;ans1=max(p,q);//第一问咯
	ans2=abs(q-p)+1;//second
	printf("%lld\n%lld",ans1,ans2);
	return 0;	
}

有一道前缀和的前缀和,不过我拿差分加线段树处理的还好吧,看起来长了些,https://www.luogu.com.cn/problem/P4868,不多说:

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m;
int len=0;
int a[1000001],sum[1000001];
struct node
{
	int l,r,lc,rc,sum,lazy;
};
node e[4000001];
int bt(int x,int y)
{
	int now=++len;
	int l=x,r=y,lc=-1,rc=-1,maxx;
	if(x==y) maxx=sum[x];
	else 
	{
		int mid=(l+r)/2;
		lc=bt(l,mid);rc=bt(mid+1,r);
		maxx=e[lc].sum+e[rc].sum;
	}
	e[now]={l,r,lc,rc,maxx,0};
	return now;
}
void pushdown(int now)
{
	if(e[now].lazy==0) return ;
	int lc=e[now].lc,rc=e[now].rc,k=e[now].lazy;
	e[now].lazy=0,e[lc].lazy+=k,e[rc].lazy+=k;
	e[lc].sum+=(e[lc].r-e[lc].l+1)*k;e[rc].sum+=(e[rc].r-e[rc].l+1)*k;
	return ;
} 
void add(int now,int x,int y,int k)
{
	int l=e[now].l,r=e[now].r;
	if(x==l&&y==r) 
	{
		e[now].sum+=(r-l+1)*k;
		e[now].lazy+=k;
	}
	else
	{
		int lc=e[now].lc,rc=e[now].rc,mid=(l+r)/2;
		pushdown(now);
		if(x>=mid+1) add(rc,x,y,k);
		else if(y<=mid) add(lc,x,y,k);
		else add(lc,x,mid,k),add(rc,mid+1,y,k);
		e[now].sum=e[lc].sum+e[rc].sum;
	}
	return ;
}
int find(int now,int x,int y)
{
	int l=e[now].l,r=e[now].r;
	if(x==l&&y==r) return e[now].sum;
	else 
	{
		int lc=e[now].lc,rc=e[now].rc,mid=(l+r)/2;
		pushdown(now);
		if(y<=mid) return find(lc,x,y);
		else if(x>=mid+1) return find(rc,x,y);
		else return find(lc,x,mid)+find(rc,mid+1,y); 
	}
}
signed main()
{
	memset(sum,0,sizeof(sum));
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];
	int root=bt(1,n);
	for(int i=1;i<=m;i++)
	{
		char p[10];scanf("%s",p+1);
		if(p[1]=='Q') 
		{
			int x;scanf("%lld",&x);
			printf("%lld\n",find(root,1,x));
		}
		else 
		{
			int x,v;scanf("%lld%lld",&x,&v);
			add(root,x,n,v-a[x]);a[x]=v;
		}
	}
	return 0;
}

最后一题啦,为数不多我看的懂还做得懂的紫题呢,就是P4514 上帝造题的七分钟这个捏,呃呃呃其实就是二维树状数组啦,要怎么样做呢,毕竟我们树状数组通常只能区间和与区间加取其一,哈,这时候就可以用到差分,与前缀和的思想,对于一个差分数组(长度为n)a[ ] 来说,若求其正常状态下的前缀和即b [ ] 的前缀和,怎么办呢那就每一位都要加上咯,则第 i 位到第 n 位之间,a [ i ] 的值会出现(n-i+1)次 ,于是我们可以让其的差分乘上*sth,然后就可以维护其前缀和的值,呃就好啦。可能讲的不是很明白,看看题解吧(https://www.luogu.com.cn/problem/solution/P4514)第一篇写的不错。
在这里插入图片描述
其实就是这个,别人写的好啊~慢慢想吧

#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
using namespace std;
int n,m;
int f[5][2248][2248];
void add(int x,int y,int k)
{	 
	if(x<1||x>n||y<1||y>m) return ;
	for(int i=x;i<=n;i+=lowbit(i))
	{
		for(int j=y;j<=m;j+=lowbit(j))
		{
			f[0][i][j]+=k;
			f[1][i][j]+=k*y;
			f[2][i][j]+=k*x;
			f[3][i][j]+=k*x*y;
		}
	}
	return ;
}
int ask(int x,int y)
{
	int rt=0;
	for(int i=x;i>=1;i-=lowbit(i))
	{
		for(int j=y;j>=1;j-=lowbit(j))
		{
			rt+=(x+1)*(y+1)*f[0][i][j]-(x+1)*f[1][i][j]-(y+1)*f[2][i][j]+f[3][i][j];
		}
	}
	return rt;
}
int main()
{
	char p[10];
	scanf("%s%d%d",p+1,&n,&m);
	while(scanf("%s",p+1)!=EOF)
	{
		if(p[1]=='L')
		{
			int x1,x2,y1,y2,k;scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&k);	
			add(x1,y1,k);add(x2+1,y1,-k);add(x1,y2+1,-k);add(x2+1,y2+1,k);
		}
		else 
		{
			int x1,x2,y1,y2;scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
			printf("%d\n",ask(x2,y2)-ask(x1-1,y2)-ask(x2,y1-1)+ask(x1-1,y1-1));
		}
	}
	return 0;
} 

其他还有好多题,不过那些算法我还没来得及复习,就以后再写啦。
现在补补补P3128 [USACO15DEC]Max Flow P一道树上差分,我的评价是水水水,直接冲冲冲。啊哈,居然还有lca。呃,得讲一下树上差分,据说是一共会有两种的差分,一个是后缀行的,具体而言就从下往上,前缀的就是从上往下。我个人比较喜欢的是前缀,但网上没有怎么讲啊,不过你看那前缀需要维护的值过多了因为要维护儿子除非你拆点,还不如用后缀呢,后缀的话一般就说让改值得两个点加x,其lac减去,不过又分两种,一个是改点权,那就和这个代码中的一样,让lca–,lca它爹–,如果是边权的话则相对简单些,自己想想。

#include<bits/stdc++.h>//灵性小知识,倍增英文是duoble 
//大部分人的树上差分用的其实都是后缀形,具体说就是统计儿子的值,不过我觉得前缀形也不错啊 
//两种方式实现一下 这是第一种......
//啊我突然懂啦,如果用前缀的话,也就是要改x,y每一个儿子的值,太麻烦
//后缀用了每个点只有一个父亲的性质,只用改几个点的值就行 
using namespace std;
int n,m,ans=0;
int f[200001],db[200001][30],dep[400001];
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 getdb(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];
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;
		if(y!=fa) getdb(y,x);
	} 
	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;
		else x=db[x][i],y=db[y][i];
	}
	return db[x][0];
}
void dfs(int x,int fa)
{
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;
		if(y==fa) continue ;
		dfs(y,x);f[x]+=f[y];
	}
	ans=max(ans,f[x]);
	return ;
}
int main()
{
	memset(last,-1,sizeof(last));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n-1;i++) 
	{
		int x,y;scanf("%d%d",&x,&y);
		ins(x,y);ins(y,x);	
	}
	getdb(1,0);
	while(m--)//后缀的维护
	{
		int x,y,fa,fafa;scanf("%d%d",&x,&y);
		fa=lca(x,y);fafa=db[fa][0];
		f[fa]--,f[fafa]--;f[x]++,f[y]++;
	}
	dfs(1,0);
	printf("%d",ans);
	return 0;
} 

下一个是!P3258 [JLOI2014]松鼠的新家这一道问题!有点偷袭还行,不难有小细节,不管了:人家又不理我了呜呜呜。

#include<bits/stdc++.h>
using namespace std;
int n,m;
int len=0,last[400000];
struct pp
{
	int x,y,next;	
};pp p[1200001];
int db[400000][30],dep[400000];
int f[400000],a[400000];
void ins(int x,int y)
{
	int now=++len;
	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;
	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) getdb(y,x);
	}
	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 ;
		else x=db[x][i],y=db[y][i];
	}
	return db[x][0];
}
void dfs(int x,int fa)
{
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;
		if(y==fa) continue;
		dfs(y,x);f[x]+=f[y];
	}
	return ;
}
int main()
{
	memset(last,-1,sizeof(last));
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n-1;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		ins(x,y);ins(y,x);
	}
	getdb(1,0);
	for(int i=2;i<=n;i++) 
	{
		int fa=lca(a[i],a[i-1]);
		f[db[a[i]][0]]++,f[a[i-1]]++;f[fa]--,f[db[fa][0]]--;	
	}
	dfs(1,0);
	for(int i=1;i<=n;i++) printf("%d\n",f[i]);
	return 0;
}

来道别的:P3943 星空,嘿嘿嘿看起来很难,不过我得说:

命运偷走如果只留下结果, 时间偷走初衷只留下了苦衷。
你来过,然后你走后,只留下星空。

写的好啊兄弟!这一题据说是背包,我细看,看不得,真不懂。太离谱了题解。

下一题吧P1600 [NOIP2016 提高组] 天天爱跑步这个是一个奇怪的树上差分,题目简单,实现的话,可以采用线段树来做,但,我拒绝,我选择思维难度较高的桶与差分,呃这个lca就离谱。说实话,难,爽,开打。巧妙的保存方式欸采用了链式前向星。感觉考场上a这一题的都是神仙,我单是打都打了一个小时。

//我这一份代码实现用的是桶子 
//真正重要的只有main()与dfs2()
//其他的基本乱搞即可实现 
//p1是使用链式前向星的方法存储每个结点作为终点对应的路径集合
//p2是使用链式前向星的方法存储每个结点作为LCA对应的路径集合
//b1,b2是两组桶,分别用于上行阶段和下行阶段的贡献统计
//js[]用于统计以每个结点作为起点的路径条数
//dist[], s[], t[]用于统计m条路径对应的长度,起点和终点信息
//ans[]存储最后输出的答案,是每个结点观察员看到的人数
//具体的想法是转换思维,枚举观测点,则可以构成一颗以1为根的树
//注意一个重要性质,对自己有贡献的点都在自己子树中 
//再者,如何维护其值呢,作者说用了树上差分我是没看出来,我们分两种情况讨论:
//在上升的时候 dep[s[i]] == dep[p] + w[p] 的点是要被计算的
//在下降的时候dist[i] - dep[t[i]] == w[p] - dep[p] 的点是要被计算的
//尝试以深度为维度,毕竟每个点只能取同一深度点的和作为答案 
//那我们就考虑如何统计,引入一个js[],js[i]指的是以i为开头的点有多少个,然后我们每到一个点,我们就处理一下这个
//也就是这一句b1[dep[x]]+=js[x];将其放入上升桶中 
//然后再搞一个结尾的,那就是如下那一句来处理,因为每一个结尾其深度要一个个的搞 
//for(int i=last1[x];i!=-1;i=p1[i].next) b2[dist[p1[i].y]-dep[t[p1[i].y]]+300000]++;
//然后就删除统计 
#include<bits/stdc++.h>
using namespace std;
int n,m;
int dep[300001],db[300001][22];
int len=0,len1=0,len2=0,last[300001],last1[300001],last2[300001];
int b1[610001],b2[610001];
int js[300001],dist[300001];//用于统计以每个结点作为起点的路径条数与统计路径对应的长度
int s[300001],t[300001],w[300001];
int ans[300001];	
struct pp
{
	int x,y,next;
};pp p[600001],p1[600001],p2[600001];
void ins(int x,int y)
{
	int now=++len;
	p[now]={x,y,last[x]},last[x]=now;
	return ;
}
void ins1(int x,int y)
{
	int now=++len1;
	p1[now]={x,y,last1[x]};last1[x]=now;
	return ;
} 
void ins2(int x,int y)
{
	int now=++len2;
	p2[now]={x,y,last2[x]};last2[x]=now;
	return ;
}

void getdb(int x,int fa)
{
	dep[x]=dep[fa]+1;db[x][0]=fa;//printf("*%d %d% d\n",x,dep[x],db[x][0]);
	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) getdb(y,x);
	}	 
	return ;	
} 
int getlca(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]) x=db[x][i],y=db[y][i];
	}
	return db[x][0];
}
void dfs2(int x)
{
	int t1=b1[w[x]+dep[x]],t2=b2[w[x]-dep[x]+300000];//确保对自己有贡献的点都在自己子树中
	//构思一个图,二叉树的左右儿子,左儿子已处理完,但是有一部分的点不经过右儿子,直接经过根上去的,则其会对结果产生影响 
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;
		if(y!=db[x][0]) dfs2(y); 	
	} 
	b1[dep[x]]+=js[x];//加上由这个点出发的起点 即是 上行过程中,当前点作为路径起点产生贡献,入桶 
	for(int i=last1[x];i!=-1;i=p1[i].next) b2[dist[p1[i].y]-dep[t[p1[i].y]]+300000]++;//加上所有对其有贡献的终点即是 下行过程中,当前点作为路径起点产生贡献,入桶
	//尝试构思一个图,起点不在其子树上,但终点在且能造成贡献 
	ans[x]+=b1[w[x]+dep[x]]-t1+b2[w[x]-dep[x]+300000]-t2;//统计答案 
	for(int i=last2[x];i!=-1;i=p2[i].next)//如果以x为lca,则无论如何也无法对其他上方的点造成贡献的,所以要删除他们 
	{
		int y=p2[i].y;
		b1[dep[s[y]]]--;b2[dist[y]-dep[t[y]]+300000]--;//删除两部分 
	}
	return ;
}
int main()
{
	memset(last,-1,sizeof(last));memset(last1,-1,sizeof(last1));memset(last2,-1,sizeof(last2));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n-1;i++) 
	{
		int x,y;scanf("%d%d",&x,&y);
		ins(x,y),ins(y,x);
	}
	for(int i=1;i<=n;i++) scanf("%d",&w[i]);
	getdb(1,0);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&s[i],&t[i]);
		int lca=getlca(s[i],t[i]);
		dist[i]=dep[s[i]]+dep[t[i]]-2*dep[lca];//求其距离 
		js[s[i]]++;
		ins1(t[i],i);ins2(lca,i);//保存 
		if(dep[lca]+w[lca]==dep[s[i]]) ans[lca]--;//若是起点或终点直接为lca,则会重复计算两次(⊙﹏⊙) 
	}
	dfs2(1);
	for(int i=1;i<=n;i++)printf("%d ",ans[i]);
	return 0;
}

熬夜写一道感觉不错的题目:P2680 [NOIP2015 提高组] 运输计划,放一篇题解:https://www.cnblogs.com/hanx16msgr/p/16774663.html,看到这个删边,发现贪心与dp都处理不了,考虑二分答案,而且有一个重要结论就是要清零的边一定是在所有大路(>=mid)上的,新知识!树上差分可以快速求出树上一点在几条给定路径上,这个理解我是没想到的。具体看代码:

//在这写一下这一篇的题解,说实话我看得出来是二分,但始终不知道如何处理,看来还是二分做少了
//首先,若用二分的话,则是通过判断一个结果是否成立来做,则我们需要枚举一个答案,使其成立
//那怎么样的答案会成立呢,首先计算每一条飞船飞行的长度即是,两个点到根的距离-lca到根的距离*2 
//那么低于枚举的答案的长度,是不用计算的,高于答案的值,把它计入树中,我们要在其中找一条最大的边,令其被全部树上的路径覆盖。
//于是考虑使用树上差分统计路径,若一条边的经过次数刚好是高于答案的值的数量,那么就将其与之前的maxx取maxx
//最后再判断一下,最大值删去这一条边后是否小于等于枚举的答案就行啦 
#include<bits/stdc++.h>
using namespace std;
int n,m,l=0,r=0,ans;
int len[3000001],val[3000001];
int sum[3000001];
int tot=0,last[3000001];
struct pp
{
	int x,y,c,next;
};pp p[6000001];
int db[310001][30],dep[3000001];
struct node
{
	int x,y,dis,lca;
};node e[310001];
void ins(int x,int y,int c)
{
	int now=++tot;
	p[now]={x,y,c,last[x]};last[x]=now;
	return ;
}
bool cmp(const node &x,const node &y)
{
	return x.dis>y.dis;
}
void getdb(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];
	for(int i=last[x];i!=-1;i=p[i].next) 
	{
		int y=p[i].y;
		if(y!=fa) 
		{
			len[y]=len[x]+p[i].c;val[y]=p[i].c; 
			getdb(y,x);
		}
	}
	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];
}
void dfs2(int x,int fa)
{
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;
		if(y!=fa) dfs2(y,x),sum[x]+=sum[y];
	}
	return ;
}
bool check(int lim)
{
	int cnt=0,maxx=0;
	memset(sum,0,sizeof(sum));
	for(int i=1;i<=m;i++)
	{
		if(e[i].dis<=lim) break;//可以不用考虑路径长度小于二分的长度的 
		sum[e[i].x]++;sum[e[i].y]++;
		sum[e[i].lca]-=2;cnt++;
	}
	dfs2(1,0);
	for(int i=1;i<=n;i++)
	{
		if(sum[i]==cnt)	maxx=max(maxx,val[i]);	
	}
	return e[1].dis-maxx<=lim; 
}
int main() 
{
	//l与r的界限有点奇怪 
	memset(last,-1,sizeof(last));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n-1;i++)
	{
		int x,y,c;scanf("%d%d%d",&x,&y,&c);
		ins(x,y,c);ins(y,x,c);l=max(l,c);
	}
	getdb(1,0);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&e[i].x,&e[i].y);e[i].lca=lca(e[i].x,e[i].y);	 
		e[i].dis=len[e[i].x]+len[e[i].y]-len[e[i].lca]*2;r=max(r,e[i].dis);
	}
	sort(e+1,e+m+1,cmp);//将其从大到小排序	 
	while(l<=r)//二分 
	{
		int mid=(l+r)/2;
		if(check(mid)) ans=mid,r=mid-1;
		else l=mid+1;
	}
	printf("%d\n",ans);
	return 0;
}

下一题呃大概没有了有也不写了白!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值