树上前缀和
原理
应用
P4427 求和
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const LL mod=998244353;
const int N=3e5+10,M=2*N;
int n,m;
int h[N],to[M],ne[M],tot;
int fa[N][22];//fa[u][i]表示从u向上跳2^i层的祖先结点
int dep[N]; //dep[v]表示v的深度
LL mi[60]; //mi[j]表示dep[v]的j次幂
LL s[N][60]; //s[v][j]表示从根到v的路径节点的深度的j次幂之和
void add(int a, int b){
to[++tot]=b,ne[tot]=h[a],h[a]=tot;
}
void dfs(int u, int f){ //预处理节点信息
for(int i=1; i<=20; i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(int i=h[u];i;i=ne[i]){
int v=to[i];
if(v==f) continue;
fa[v][0]=u; dep[v]=dep[u]+1;
for(int j=1;j<=50;j++) mi[j]=mi[j-1]*dep[v]%mod;
for(int j=1;j<=50;j++) s[v][j]=(mi[j]+s[u][j])%mod;
dfs(v, u);
}
}
int lca(int u, int v){ //倍增法求lca
if(dep[u]<dep[v])swap(u, v);
for(int i=20; ~i; i--)
if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
if(u==v) return v;
for(int i=20; ~i; i--)
if(fa[u][i]!=fa[v][i]) u=fa[u][i], v=fa[v][i];
return fa[u][0];
}
int main(){
scanf("%d",&n);
for(int i=1,a,b; i<n; i++){
scanf("%d%d",&a,&b);
add(a,b); add(b,a);
}
mi[0]=1; dfs(1, 0);
scanf("%d",&m);
for(int i=1,u,v,k;i<=m;i++){
scanf("%d%d%d",&u,&v,&k);
int l=lca(u,v);
LL ans=(s[u][k]+s[v][k]-s[l][k]-s[fa[l][0]][k]+2*mod)%mod;
printf("%lld\n",ans);
}
}
树上差分
原理
应用
P3128 Max Flow P
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e4+10;
int n,k,x,y;
vector<int> e[N];
int fa[N][20],dep[N],diff[N];
int ans=-1;
void dfs(int u,int f){
fa[u][0]=f,dep[u]=dep[f]+1;
for(int i=1;i<=19;i++){
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(auto v:e[u]){
if(v!=f){
dfs(v,u);
}
}
}
int lca(int u,int v){
if(dep[u]<dep[v]) swap(u,v);
for(int i=19;i>=0;i--){
if(dep[fa[u][i]]>=dep[v])
u=fa[u][i];
}
if(u==v) return u;
for(int i=19;i>=0;i--){
if(fa[u][i]!=fa[v][i])
u=fa[u][i],v=fa[v][i];
}
return fa[u][0];
}
void dfs2(int u,int f){
for(auto v:e[u]){
if(v!=f){
dfs2(v,u);
diff[u]+=diff[v];
}
}
ans=max(ans,diff[u]);
}
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
scanf("%d%d",&n,&k);//用scanf和printf比较好,
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1,0);
while(k--){
scanf("%d%d",&x,&y);
int l=lca(x,y);
diff[x]++,diff[y]++,diff[l]--,diff[fa[l][0]]--;
}
dfs2(1,0);
printf("%d",ans);
return 0;
}
砍树(蓝桥杯真题)
传送门
本题思路:将树上区间加操作,最后统计每条边总和,这马上想到差分,因为是树上,所以使用树上差分
#include <iostream>
#include <algorithm>
#include <vector>
#include<map>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int maxn = 1e5 + 50;
vector<int> e[maxn];
ll fa[maxn], dep[maxn], son[maxn], siz[maxn], top[maxn];
ll diff[maxn];
map<pii,ll> id;
void dfs1(ll u, ll f) {
fa[u] = f;
dep[u] = dep[f] + 1;
siz[u] = 1;
for (auto x: e[u]) {
ll v = x;
if (v == f) continue;
dfs1(v, u);
siz[u] += siz[v];
if (siz[v] > siz[son[u]]) son[u] = v;
}
}
void dfs2(ll u, ll t) {
top[u] = t;
if (!son[u]) return;
dfs2(son[u], t);
for (auto x: e[u]) {
ll v = x;
if (v != son[u] && v != fa[u]) dfs2(v, v);
}
}
ll lca(ll x, ll y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
x = fa[top[x]];
}
return dep[x] > dep[y] ? y : x;
}
void dfs(int x, int fx) {
for (auto y: e[x]) {
if (y == fx) continue;
dfs(y, x);
diff[x] += diff[y];
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n, m;
cin >> n >> m;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
e[u].emplace_back(v);
e[v].emplace_back(u);
id[{u,v}]=i;
id[{v,u}]=i;
}
dfs1(1, 1);
dfs2(1, 1);
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
diff[u]++, diff[v]++, diff[lca(u, v)] -= 2;
}
dfs(1, 0);
ll ans = -1;
for (int i = 0; i <= n; i++){
if (diff[i] >= m){
ans=max(ans,id[{i,fa[i]}]);
}
}
cout << ans << endl;
return 0;
}
注意:以两个点存储边号,标记。边差分原理是,将边差分转换为点差分,除根节点外,每个点都代表点上面的那条边。
U143800 暗之连锁
难点:
1.当边累积超过1次,则说明删除此边不能形成满足条件,所以不会影响答案。
2.当累积为1次时,答案加一,删除此边和构成环的附加边。
3.当累积为0次时,答案加m,说明删除此边就满足条件,然后随便删除一条附加边即可。
然后用set存有哪些点,最好答案还要减去m,因为根节点为0,但是其不表示边。注意set适合去重,存点,但是有顺序的时候得慎重考虑,因为set会自动排序。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int n,k,x,y;
vector<int> e[N];
set<int> st;
int fa[N][20],dep[N];
ll diff[N];
ll ans=0;
void dfs(int u,int f){
fa[u][0]=f,dep[u]=dep[f]+1;
for(int i=1;i<=19;i++){
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(auto v:e[u]){
if(v!=f){
dfs(v,u);
}
}
}
int lca(int u,int v){
if(dep[u]<dep[v]) swap(u,v);
for(int i=19;i>=0;i--){
if(dep[fa[u][i]]>=dep[v])
u=fa[u][i];
}
if(u==v) return u;
for(int i=19;i>=0;i--){
if(fa[u][i]!=fa[v][i])
u=fa[u][i],v=fa[v][i];
}
return fa[u][0];
}
void dfs2(int u,int f){
for(auto v:e[u]){
if(v!=f){
dfs2(v,u);
diff[u]+=diff[v];
}
}
}
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
scanf("%d%d",&n,&k);
int m=k;
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
e[x].push_back(y);
e[y].push_back(x);
st.insert(x);
st.insert(y);
}
dfs(1,0);
while(k--){
scanf("%d%d",&x,&y);
int l=lca(x,y);
diff[x]++,diff[y]++,diff[l]-=2;
}
dfs2(1,0);
for(auto x:st){
if(diff[x]==1) ans++;
else if(!diff[x]) ans+=m;
}
printf("%d",ans-m);
return 0;
}
P3258 松鼠的新家
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+10;
int n,k,x,y;
vector<int> e[N];
int fa[N][22],dep[N],a,b,t[N];
ll diff[N];
ll ans=0;
void dfs(int u,int f){
fa[u][0]=f,dep[u]=dep[f]+1;
for(int i=1;i<=21;i++){
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(auto v:e[u]){
if(v!=f){
dfs(v,u);
}
}
}
int lca(int u,int v){
if(dep[u]<dep[v]) swap(u,v);
for(int i=21;i>=0;i--){
if(dep[fa[u][i]]>=dep[v])
u=fa[u][i];
}
if(u==v) return u;
for(int i=21;i>=0;i--){
if(fa[u][i]!=fa[v][i])
u=fa[u][i],v=fa[v][i];
}
return fa[u][0];
}
void dfs2(int u,int f){
for(auto v:e[u]){
if(v!=f){
dfs2(v,u);
diff[u]+=diff[v];
}
}
}
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&t[i]);
}
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1,0);
for(int i=1;i<n;i++){
x=t[i-1],y=t[i];
int l=lca(x,y);
diff[x]++,diff[y]++,diff[l]--,diff[fa[l][0]]--;
}
dfs2(1,0);
for(int i=1;i<n;i++) diff[t[i]]--;//要先减,看清楚题意要求的输出
for(int i=1;i<=n;i++){
printf("%d\n",diff[i]);
}
return 0;
}
P2680 运输计划
题意:让一条边长度(时间)变为0,求所有运输计划所需最大时间最小。求最大值最小——二分答案。
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10,M=2*N;
int h[N],e[M],w[M],ne[M],idx;//链式向前星
int fa[N],son[N],sz[N],top[N],dep[N];//树剖求最近公共祖先
int n,m,R,L,cnt;//R表示最大航道飞行时间,L表示最大每条边的飞行时间
//diff树上差分数组,sum树上前缀和数组
int diff[N],sum[N],dnf[N],val[N];//dnf从祖宗节点重新映射每个树节点,val[i]存储点i上面的边长度。
void add(int a,int b,int c){
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
struct node{
int u,v,l,d;//存储运输计划的两个端点和最近公共祖先节点,所需耗时时间
}edge[N];
void dfs1(int u,int f,int len){//预处理除了top的数组,和前缀和数组
fa[u]=f,dep[u]=dep[f]+1,sz[u]=1,sum[u]=len,dnf[++cnt]=u;//dnf和val这个技巧要学会
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==f) continue;
dfs1(j,u,len+w[i]);
val[j]=w[i];
sz[u]+=sz[j];
if(sz[son[u]]<sz[j]) son[u]=j;
}
}
void dfs2(int u,int t){//预处理top数组,重链
top[u]=t;
if(!son[u]) return;
dfs2(son[u],t);
for(int i=h[u];~i;i=ne[i]){
int v=e[i];
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
}
}
int lca(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
u=fa[top[u]];
}
return dep[u]<dep[v]?u:v;
}
void dfs3(int u){//树上差分求和
for(int i=h[u];~i;i=ne[i]){
int v=e[i];
if(v==fa[u]) continue;
dfs3(v);
diff[u]+=diff[v];
}
}
bool check(int x){
memset(diff,0,sizeof diff);
int count=0;
for(int i=1;i<=m;i++){
if(edge[i].d>x){
count++;
int u=edge[i].u,v=edge[i].v,l=edge[i].l;
diff[u]++,diff[v]++,diff[l]-=2;
}
}
dfs3(1);
for(int i=1;i<=n;i++){
if(val[dnf[i]]>=R-x&&diff[dnf[i]]>=count)//有一条边长度大于等于缺少的部分,并且被覆盖次数大于count次
return true;
}
return false;
}
int main(){
memset(h,-1,sizeof h);
int a,b,c;
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
scanf("%d%d%d",&a,&b,&c);
add(a,b,c),add(b,a,c);
L=max(L,c);
}
dfs1(1,0,0),dfs2(1,1);
for(int i=1;i<=m;i++){
scanf("%d%d",&a,&b);
int l=lca(a,b);
int d=sum[a]+sum[b]-sum[l]*2;
edge[i]={a,b,l,d};
R=max(R,d);
}
int l=R-L-1,r=R+1;
while(l+1<r){
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid;
}
printf("%d\n",r);
}
开心,一遍敲过,想代码时间应该占完成题目总时间的七成,想好了,bug才很少。
经验:
1.用点表示点上面边的长度
2.二分答案技巧。