差分的概念是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 三步必杀这一个啦,比上面那个难一点哦,维护的是等差数列,怎么办呢先看出来左右两个数的差值是相等的,然后想如何维护个差值数列,嘿嘿对嘛所以就可以维护改值那一点的右右两点,其实就相当于维护差分队列上的单点改制嘛简简单单啦。其实想了一会才想明白,若一个差分维护的是差值,则要让数组向后推一位才行。大概是这样吧(懒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;
}
下一题呃大概没有了有也不写了白!