文章目录

  • 0x0E 雨天的尾巴 洛谷p4556(线段树合并+树上差分+树链lca)
  • 0x0F CF600E Lomsat gelral(线段树合并)
  • 0x10 天天爱跑步 NOIP2016 P1600 (树链LCA 和树上差分)
  • 0x11 异象石 Acwing (树链LCA + 时间戳)
  • 0x12 次小生成树 (倍增LCA + 路径上权值最大和次大的保存)
  • 0x13 疫情控制 (倍增LCA +思维 +根到叶子检查点 )

0x0E 雨天的尾巴 洛谷p4556(线段树合并+树上差分+树链lca)

雨天的尾巴
详细视频讲解
题意:一个由n座房屋形成的树状结构,然后救济粮分 m m m 次发放,每次选择两个房屋 ( x , y ) (x, y) (x,y),然后对于 x x x 到 y y y 的路径上(含 x x x 和 y y y)每座房子里发放一袋 z z z 类型的救济粮。
然后深绘里想知道,当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮。
输入:输入 n − 1 n-1 n−1条边,接下来m行,每行 x , y , z , x,y,z, x,y,z,代表一次救济粮的发放是从 x x x 到 y y y 路径上的每栋房子发放了一袋 z z z 类型的救济粮。。
输出:输出 n n n 行,每行一个整数,第 i i i 行的整数代表 ii 号房屋存放最多的救济粮的种类,如果有多种救济粮都是存放最多的,输出种类编号最小的一种。
如果某座房屋没有救济粮,则输出 0 0 0。
思路:使用树链剖分求lca。使用树上差分,在路径两边,公共祖先,及其公共祖先父节点添加标记。
此题求点上存放最多救济粮的编号,建立线段树的,区间表示种类编号。
修改过程,配合树上差分即是单点修改。合并时,儿子结点向上更新取最大的儿子结点给父节点。
ACcode(详解)

#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int maxn=1e5+10;
const ll P=1e9+7;
ll read(){ll s = 0, f = 1; char ch = getchar();while(!isdigit(ch)){if(ch == '-') f = -1;ch = getchar();}while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();return s * f;
}
using namespace std;
typedef pair<int,int> pii;
struct E{int v,next;
}Edge[maxn<<1];
int tot,head[maxn<<1];
void AddEdge(int u,int v){Edge[++tot] = (E){v,head[u]};head[u] = tot;
}
struct Node{int l,r;  //左右结点编号pii val;
}sgt[70*maxn];
int cnt;
/// deep深度    siz当前结点的子结点之和  fa:父节点  son:son[u] =  儿子结点子树最多的结点。 //树链时用到
int deep[maxn],siz[maxn],fa[maxn],son[maxn];
int root[maxn];    //存放结点的根编号
// dfs1  和 dfs2 求树链
void dfs1(int u,int f){deep[u] = deep[f] + 1;siz[u]  = 1;fa[u] = f;int maxsize = -1;for(int i=head[u];i;i=Edge[i].next){int v= Edge[i].v;if(v==f) continue;dfs1(v,u);siz[u] +=siz[v];if(siz[v] > maxsize){maxsize = siz[v];son[u] = v;}}
}
int top[maxn];     // 一条链的所有点标记一样
void dfs2(int u,int k){top[u] = k;if(son[u]) dfs2(son[u],k);for(int i=head[u];i;i=Edge[i].next){int v = Edge[i].v;if(v==fa[u] || v==son[u]) continue;dfs2(v,v);}
}
// 求lca
int lca(int x,int y){while(top[x]!= top[y]){if(deep[top[x]] < deep[top[y]]){swap(x,y);}x = fa[top[x]];}return deep[x]<deep[y]? x:y;
}
// 比较儿子结点val大小
pii& maxpii(pii& x,pii& y){  // 个数  和 类别if(x.first < y.first) return y;else if(x.first == y.first) return x.second<y.second? x:y;else return x;
}
// 向上更新
void pushup(int k){ sgt[k].val = maxpii(sgt[sgt[k].l].val , sgt[sgt[k].r].val); }
//单点修改 区间[l,r]  k--- root[u]的地址映射值,对k修改同时对root[u]修改,  p种类 x值
void modify(int l,int r,int& k,int p,int x){if(!k)  k = ++cnt;   // 结点null 创建一个空间if(l==r){   //叶子结点 ++sgt[k].val.first +=x;sgt[k].val.second = p;return ;}int  m = (l+r)>>1;//  线段树常见手法if(p<=m){modify(l,m,sgt[k].l,p,x);}else{modify(m+1,r,sgt[k].r,p,x);}// 向上更新pushup(k);
}
// y合并到x中. 1.结点空 2.非空合并 3.叶子结点
void merge(int &x,int y,int l=1,int r=maxn){if((!x) || (!y)) x|=y;else if(l==r){sgt[x].val.first +=sgt[y].val.first;}else{int m = (l+r)>>1;merge(sgt[x].l,sgt[y].l,l,m);merge(sgt[x].r,sgt[y].r,m+1,r);pushup(x);}
}
int ans[maxn];  // 存结果
// 对树自底向上回溯,合并。
void dfs(int u){for(int i=head[u];i;i=Edge[i].next){int v= Edge[i].v;if(v==fa[u]) continue;dfs(v);merge(root[u],root[v]);}if(sgt[root[u]].val.first){ans[u] = sgt[root[u]].val.second;}
}
void solve(){int n,m;cin>>n>>m;for(int i=1;i<n;i++){int u,v;cin>>u>>v;AddEdge(u,v);AddEdge(v,u);}dfs1(1,0);dfs2(1,1);// cout<<"I am Hero"<<endl;// for(int i=1;i<=n;i++){//     cout<<fa[i]<<" "<<i<<endl;// }for(int i=1;i<=m;i++){int x,y,z;cin>>x>>y>>z;// cout<<lca(x,y)<<endl;// 树上差分常见手法modify(1,maxn,root[x],z,1); // x结点加上z救济粮标记modify(1,maxn,root[y],z,1);// y结点加上z救济粮标记modify(1,maxn,root[lca(x,y)],z,-1);  // lca(x,y)结点加上-z救济粮标记modify(1,maxn,root[fa[lca(x,y)]],z,-1);// fa[lca(x,y)]结点加上-z救济粮标记} dfs(1);   //差分统计咯for(int i=1;i<=n;i++){cout<<ans[i]<<endl;}return ;
}
int main (){//freopen("in.txt","r",stdin);//freopen("out.txt","w",stdout);solve();getchar();getchar();return 0;
}

0x0F CF600E Lomsat gelral(线段树合并)

CF600E Lomsat gelral
题意:给你一棵有nn个点的树 ( n ≤ 1 0 5 ) (n≤10^5) (n≤105),树上每个节点都有一种颜色 c i ( c i ≤ n ) ci(ci≤n) ci(ci≤n),让你求每个点子树出现最多的颜色的编号的和.
思路:一样的只不过单点修改对每个点修改。线段树的区间依然表示的是颜色编号。
在pushup上 个数相等的儿子结点,编号相加。不等,取个数较大的赋给父节点。
tips: 记得开long long 。 (话说假如树退化成一条链,不会爆栈嘛,好像也让过了…)
ACcode:

#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=1e5+10;
const ll P=1e9+7;
ll read(){ll s = 0, f = 1; char ch = getchar();while(!isdigit(ch)){if(ch == '-') f = -1;ch = getchar();}while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();return s * f;
}
using namespace std;
typedef pair<ll,ll> pii;
struct Node{ll l,r;pii val;  // 个数 类型
}sgt[30*N];
ll cnt;
ll root[N];
struct E{ ll v,next;} Edge[N<<1];
ll tot,head[N];
void AddEdge(ll u,ll v){Edge[++tot] = (E){v,head[u]};head[u] = tot;
}
void pushup(ll k){ll l,r;l = sgt[k].l;r = sgt[k].r;if(sgt[l].val.first == sgt[r].val.first){sgt[k].val.first = sgt[l].val.first;sgt[k].val.second = sgt[l].val.second + sgt[r].val.second;}else if( sgt[l].val.first < sgt[r].val.first){sgt[k].val.first  =sgt[r].val.first;sgt[k].val.second = sgt[r].val.second;}else {sgt[k].val.first  =sgt[l].val.first;sgt[k].val.second = sgt[l].val.second;}
}
void modify(ll l,ll r,ll& k,ll p,ll x){if(!k) k = ++cnt;if(l==r){sgt[k].val.first+=x;sgt[k].val.second = p;return ;}ll m = (l+r)>>1;if(p<=m) modify(l,m,sgt[k].l,p,x);else modify(m+1,r,sgt[k].r,p,x);pushup(k);
}
void merge(ll& x,ll y,ll l=1,ll r=N){if((!x)|| (!y))  x|=y;else if(l==r){sgt[x].val.first +=sgt[y].val.first;}else{int m= (l+r)>>1;merge(sgt[x].l,sgt[y].l,l,m);merge(sgt[x].r,sgt[y].r,m+1,r);pushup(x);}
}
ll ans[N];  //存放结果
//  自底向上的递归合并
void dfs(int u,int f){for(int i=head[u];i;i=Edge[i].next){int v = Edge[i].v;if(v==f) continue;dfs(v,u);merge(root[u],root[v]);}ans[u] = sgt[root[u]].val.second;
}
void solve(){int n;cin>>n;for(int i=1;i<=n;i++){int p;cin>>p;modify(1,N,root[i],p,1);}//建树for(int i=1;i<n;i++){int u,v;cin>>u>>v;AddEdge(u,v);AddEdge(v,u);}dfs(1,0);for(int i=1;i<=n;i++){cout<<ans[i]<<" ";}
}
int main (){//   freopen("in.txt","r",stdin);
//   freopen("out.txt","w",stdout);solve();getchar();getchar();return 0;
}

0x10 天天爱跑步 NOIP2016 P1600 (树链LCA 和树上差分)

天天爱跑步
详解
题意:再一个树形结构的地图中,m(m<=3e5)个玩家,起点 S i S_i Si​,终点 T i T_i Ti​,所有玩家从第0秒同时出发。由于地图是树形结构,玩家从起点到终点路径唯一。在地图上每个结点都有观察员,在j结点的观察员选择在第 W j W_j Wj​秒观察玩家。一个玩家能被该观察员观察到当且仅当在第 W j W_j Wj​也正好到达结点j。求每个结点的观察员可以观察的玩家数量。
思路 : 对问题拆解,玩家起点s[i],终点t[i],起点和终点公共祖先lca.观察员j点第w[j]秒。dist:起点到终点的距离
玩家被观察员j观察到的条件:起点到lca:deep[j]+w[j] = deep[s[i]] 。 或者 lca到终点 dist - deep[t[i]] = w[j] - deep[j];
在式子中,我们就能将deep[s[i]], dist - deep[t[i]] 看成一个类型,添加上两个公共数组中。每当询问j点时,就在公共数组中询问类型为deep[j]+w[j],w[j] - deep[j]的个数。 结果就能得出了。
注意!求一个j点观察到的数量,在dfs遍历,有效的数量是子树的范围呢。在没到j点之前的值是无效的。有效是"遍历完成时的值"与“”遍历开始时的值”。由于我们是将一条路径拆成两部分:起点到lca,lca到终点。细心的你一定发现了lca被用了两次,假如lca有贡献,那么势必会贡献两次,所以我们在此先将结果值减一,if(deep[LCA]+w[LCA]==deep[s[i]]) ans[LCA]–;
w[j] - deep[j] 这个类型值可能为负数,所以在原有加上N,即是类型值是(w[j] - deep[j]+N);
ACcode详解

#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=3e5+10;
const ll P=1e9+7;
ll read(){ll s = 0, f = 1; char ch = getchar();while(!isdigit(ch)){if(ch == '-') f = -1;ch = getchar();}while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();return s * f;
}
using namespace std;
struct E{ int to,next;}Edge[N<<1];
int tot,head[N];
void AddEdge(int u,int v){Edge[++tot] = (E){v,head[u]};head[u] = tot;
}
int deep[N],fa[N],siz[N],son[N];  // 深度,fa[子]=父,siz[u]=u子树结点数量+1 son[u]= u的子结点中siz最大的节点
// dfs1,dfs2 使用树链的方式求LCA
void dfs1(int u,int f){fa[u] = f; deep[u] = deep[f]+1; siz[u] =1;int maxsize = -1;for(int i=head[u];i;i=Edge[i].next){int v = Edge[i].to;if(v==f) continue;dfs1(v,u);siz[u] +=siz[v];if(siz[v] > maxsize){maxsize  = siz[v];son[u] = v;}}
}
int top[N];
void dfs2(int u,int k){top[u] = k;if(son[u]) dfs2(son[u],k);for(int i=head[u];i;i=Edge[i].next){int v = Edge[i].to;if(v==fa[u] || v==son[u]) continue;dfs2(v,v);}
}
int lca(int x,int y){while(top[x]!=top[y]){if(deep[top[x]] < deep[top[y]]) swap(x,y);x = fa[top[x]];}return deep[x]<deep[y]? x:y;
}
int w[N],s[N],t[N];
vector<int> e1[N],e2[N];  // 终点 编号   lca 编号
int js[N];  // 起点  个数
int ans[N];  //存放结果
int b1[N],b2[N<<1]; // 起点开始  lca开始
int dist[N];  // 求出路径的距离
// dfs递归自叶向根
void dfs(int x){int t1 = b1[deep[x] + w[x]];int t2 = b2[w[x] - deep[x] + N];for(int i=head[x];i;i=Edge[i].next){int v = Edge[i].to;if(v==fa[x])  continue;dfs(v);}b1[deep[x]] +=js[x];for(int i=0;i<(int)e1[x].size();i++){int num = e1[x][i];  //第几条边b2[dist[num] - deep[t[num]] + N ]++;}// 计算结果ans[x] += b1[deep[x] + w[x] ] - t1 + b2[w[x] -deep[x]+N] -t2;// x是LCA时   路径已经走完。dfs回溯是向上for(int i = 0;i<(int)e2[x].size();i++){int num = e2[x][i];b1[deep[s[num]]] -- ;b2[dist[num] - deep[t[num]]+ N ]--;}
}
void solve(){int n,m;cin>>n>>m;for(int i=1;i<n;i++){int u,v;cin>>u>>v;AddEdge(u,v);AddEdge(v,u);}for(int i=1;i<=n;i++) cin>>w[i];dfs1(1,0);dfs2(1,1);for(int i=1;i<=m;i++){cin>>s[i]>>t[i];js[s[i]]++;  int LCA = lca(s[i],t[i]);dist[i] =  deep[s[i]]+ deep[t[i]] - 2*deep[LCA];// cout<<"LCA:"<<LCA<<endl;e1[t[i]].push_back(i);e2[LCA].push_back(i);if(deep[LCA]+w[LCA]==deep[s[i]]) ans[LCA]--;}dfs(1);for(int i=1;i<=n;i++){cout<<ans[i]<<" ";}
}
int main (){//freopen("in.txt","r",stdin);//freopen("out.txt","w",stdout);solve();getchar();getchar();return 0;
}

0x11 异象石 Acwing (树链LCA + 时间戳)

异象石

题意:在一颗树种,三种操作:+,-和?.

输出查询的结果。
所以的点连通的边集的总长度最小什么意思呢? 即是这些点形成的连通图的权值最小值。
使用时间戳,对时间相邻的点求得路径长度.
ACcode:

#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){ll s = 0, f = 1; char ch = getchar();while(!isdigit(ch)){if(ch == '-') f = -1;ch = getchar();}while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();return s * f;
}
using namespace std;
struct E{ ll to,next,val;} Edge[N<<1];
int tot=0,head[N];
void AddEdge(int u,int v,ll val){Edge[++tot] = (E){v,head[u],val};head[u] = tot;
}
ll dist[N],fa[N],siz[N],son[N],deep[N];
ll dfn[N],mp[N];
ll cnt=0;
void dfs1(int u,int f){fa[u] = f;siz[u] = 1;dfn[u] = ++cnt;  mp[cnt] = u;  // 编号映射到节点deep[u] = deep[f] +1;int maxsize = -1;for(int i=head[u];i;i=Edge[i].next){int v = Edge[i].to;ll val = Edge[i].val;if(v==f) continue;dist[v] =  dist[u] + val;dfs1(v,u);siz[u] +=siz[v];if(siz[v] > maxsize){son[u] = v;maxsize = siz[v];}}
}
int top[N];
void dfs2(int u,int k){top[u] = k;if(son[u]) dfs2(son[u],k);for(int i=head[u];i;i=Edge[i].next){int v = Edge[i].to;if(v==fa[u] || v==son[u]) continue;dfs2(v,v);}
}
int lca(int x,int y){while(top[x]!=top[y]){if(deep[top[x]] < deep[top[y]]) swap(x,y);x = fa[top[x]];}return deep[x]<deep[y]? x:y;
}
ll dis(int u,int v){ //编号u = mp[u];v = mp[v];// printf("u:%d v:%d lca:%d\n",u,v,lca(u,v));return dist[u] + dist[v] - 2*dist[lca(u,v)];
}
set<ll> s; //存放编号
ll ans = 0;
void add(int u){s.insert(dfn[u]);set<ll>:: iterator it = s.find(dfn[u]);set<ll>:: iterator l = it==s.begin() ? --s.end():--it;it = s.find(dfn[u]);set<ll>:: iterator r = it== (--s.end()) ? s.begin():++it;it = s.find(dfn[u]);ans -= dis(*l,*r);ans +=dis(*l,*it) + dis(*r,*it);
}
void del(int u){set<ll>:: iterator it = s.find(dfn[u]);set<ll>:: iterator l = it==s.begin() ? --s.end():--it;it = s.find(dfn[u]);set<ll>:: iterator r = it== (--s.end()) ? s.begin():++it;it = s.find(dfn[u]);ans += dis(*l,*r);ans -= dis(*l,*it) + dis(*it,*r);s.erase(dfn[u]);
}
void solve(){int n;cin>>n;for(int i=1;i<n;i++){int u,v;ll val;cin>>u>>v>>val;AddEdge(u,v,val);AddEdge(v,u,val);}dfs1(1,0);dfs2(1,1);char ch;int m;cin>>m;int u;for(int i=1;i<=m;i++){cin>>ch;if(ch=='+'){cin>>u;add(u);}else if(ch=='-'){cin>>u;del(u);}else if(ch=='?'){cout<<ans/2<<endl;}}
}
int main (){//freopen("in.txt","r",stdin);//freopen("out.txt","w",stdout);solve();getchar();getchar();return 0;
}

0x12 次小生成树 (倍增LCA + 路径上权值最大和次大的保存)

次小生成树
题意:给定一张 N 个点 M 条边的无向图,求无向图的严格次小生成树。设最小生成树的边权之和为 sum,严格次小生成树就是指边权之和大于 sum 的生成树中最小的一个。 问次大生成树的边集和是多少。
思路: 不用质疑,先kursal找出最小生成树,并建立树。这里要明白一点 未在最小生成树的边,权值一定大于等于树上x->y路径边的权值.
就开始替换咯,找出树x-y路径的权值最大(记为max1),次大记为(记为max2)。如果外边权值==max1,那么 s u m − m a x 2 + v a l sum-max2+val sum−max2+val。否则 s u m − m a x 2 + v a l sum-max2+val sum−max2+val.


G[y][0][1] = -INF; //重边或重边权值都一样 很重要的一条代码

// 在四个值中 筛选出最大给max1。严格次大给max2 巧妙构思
void qu(ll &max1,ll &max2,ll x,ll y){if(max1 == x){max2 = max(max2,y);}else if(max1 < x){max2 = max(max1,y);max1  = x;}else if(max1 > x){max2 = max(max2,x);}
}

ACcode详解

#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
const ll INF = 1e15;
ll read(){ll s = 0, f = 1; char ch = getchar();while(!isdigit(ch)){if(ch == '-') f = -1;ch = getchar();}while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();return s * f;
}
using namespace std;
struct Node{ ll u,v,vis,val;} a[N<<1];
struct E{ ll to,next,val;}  Edge[N];
int tot,head[N];
ll sum,ans;
void AddEdge(int u,int v,ll val){Edge[++tot] = (E) {v,head[u],val};head[u] = tot;
}
ll fa[N];
int find(int x){return x==fa[x]? x:fa[x] = find(fa[x]);
}
bool cmp(Node x,Node y){return x.val < y.val;
}
void krusal(int n,int m){// 权值排序sort(a+1,a+1+m,cmp);for(int i=1;i<=m;i++){int x = find(a[i].u);int y = find(a[i].v);if(x==y) continue;a[i].vis = 1;fa[x] = y;sum +=a[i].val;AddEdge(a[i].u,a[i].v,a[i].val);AddEdge(a[i].v,a[i].u,a[i].val);}
}
ll f[N][20],deep[N];
int t ;
ll G[N][20][2];  // 节点 k 大小
// bfs 求 fa和G
void bfs(){queue<int> q;q.push(1);deep[1] = 1;while(q.size()){int x = q.front();q.pop();for(int i=head[x];i;i=Edge[i].next){int y = Edge[i].to;if(deep[y]) continue;deep[y] = deep[x] + 1;f[y][0] = x;G[y][0][0] = Edge[i].val;G[y][0][1] = -INF;  //重边或重边权值都一样for(int i=1;i<=t;i++){f[y][i] = f[f[y][i-1]][i-1];int z = f[y][i-1];G[y][i][0] = max(G[y][i-1][0],G[z][i-1][0]);if(G[y][i-1][0] == G[z][i-1][0]){G[y][i][1] = max(G[y][i-1][1],G[z][i-1][1]);}else if(G[y][i-1][0] < G[z][i-1][0]){G[y][i][1] = max(G[y][i-1][0],G[z][i-1][1]);}else if(G[y][i-1][0] > G[z][i-1][0]){G[y][i][1] = max(G[y][i-1][1],G[z][i-1][0]);}}q.push(y);}}
}
// 在四个值中 筛选出最大给max1。严格次大给max2
void qu(ll &max1,ll &max2,ll x,ll y){if(max1 == x){max2 = max(max2,y);}else if(max1 < x){max2 = max(max1,y);max1  = x;}else if(max1 > x){max2 = max(max2,x);}
}
int lca(int x,int y,ll &max1,ll &max2){//  目标:找出x->y路径 最大和次大的值 if(deep[x] > deep[y] ) swap(x,y);for(int i=t;i>=0;i--){if(deep[f[y][i]] >= deep[x]) {qu(max1,max2,G[y][i][0],G[y][i][1]);y = f[y][i];}}if(x==y) return x;for(int i=t;i>=0;i--){if(f[x][i] != f[y][i]){//  一起跳qu(max1,max2,G[x][i][0],G[x][i][1]);qu(max1,max2,G[y][i][0],G[y][i][1]);//   更新x和 y;x = f[x][i];y = f[y][i];}}//  x和y需要再跳一步qu(max1,max2,G[x][0][0],G[x][0][1]);qu(max1,max2,G[y][0][0],G[y][0][1]);return f[x][0];
}
void solve(){int n,m;cin>>n>>m;// 初始化for(int i=1;i<=n;i++) fa[i] = i;for(int i=1;i<=m;i++){ll u,v,val;cin>>u>>v>>val;a[i] = (Node){u,v,0,val};}// 求tt = (int)(log(n)/log(2)) +1;krusal(n,m);bfs();ans = INF;for(int i=1;i<=m;i++){if(a[i].vis) continue;int x = a[i].u;int y = a[i].v;ll max1=-INF,max2=-INF;ll LCA = lca(x,y,max1,max2);// 这里我要明白一点 未在最小生成树的边,权值一定大于等于树上x->y路径边的权值.if(a[i].val==max1){ans = min(ans,sum - max2 + a[i].val);}else{ans = min (ans,sum - max1 + a[i].val);}}cout<<sum<<" "<<ans<<endl;// for(int i=1;i<=n;i++){//     for(int j=1;j<=5;j++){//         printf("(%lld,%lld) ",G[i][j][0],G[i][j][1]);//     }//     cout<<endl;// }
}
int main (){//freopen("in.txt","r",stdin);//freopen("out.txt","w",stdout);solve();getchar();getchar();return 0;
}

0x13 疫情控制 (倍增LCA +思维 +根到叶子检查点 )

疫情控制
题意:H国,n个城市,城市之间有n-1条双向道路互连构成树。1号城市是首都,也是根节点。
在树的叶子点是疫情扩散点(边境城市),现为了阻止扩散到首都,决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。注意首都不能建立检查点。
军队总数为 m 支。
1 一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。
2 一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。
请问:最少需要多少个小时才能控制疫情?如果无法控制疫情则输出 −1
n个城市,m支军队。
思路:在此题中,给时间越多军队选择的越多更有可能控制疫情。符合二分的思想。二分答案。给了时间只要得出能与不能控制疫情就行了。
军队可以分为两类:
1.第一类在mid小时内无法到达根节点,这些结点就尽量往根节点的方向移动。处理完第一类的军队。
记根节点的子节点集合为son(root),对每个 s ∈ s o n ( r o o t ) s\in son(root) s∈son(root),统计还有叶子节点尚未被管辖的点,记为集合H。
2.第二类mid内能到到根节点的军队

所以对任意 x ∈ H x\in H x∈H并且s上rest值最小的军队不足以移动到根再返回s的这支军队驻扎在s,即是管辖s为根的子树。
原理说完了,该这么实现?
H集合怎么找到呢? 用fg[结点] = 有无军队. 使用dfs自底向上回溯,fg[u]是1的前提示u的所有儿子节点都必须fg[v] =1;
然后下一步是删除部分的H集合的s(这一步是为了后续剩余的军队移至根节点,再跳往H集合的s)。
并将剩下的军队移步至根节点,对此时军队的rest排序和对根到s距离排序。使用双指针,只有H集合的s全部指完,疫情得到控制可行
,否则,不可行。
注意: 路径权值 w < = 1 e 9 w<=1e9 w<=1e9,开 long long
ACcode详解:

#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=1e5+10;
const ll P=1e9+7;
ll read(){ll s = 0, f = 1; char ch = getchar();while(!isdigit(ch)){if(ch == '-') f = -1;ch = getchar();}while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();return s * f;
}
using namespace std;
struct E{ ll to,next,val;} Edge[N];
int tot,head[N];
void AddEdge(int u,int v,int val){Edge[++tot] = (E){v,head[u],val};head[u] = tot;
}
ll w[N],deep[N],f[N][30];  // w:根到i的权值
int t = 20;
int n,m;
void bfs(){queue<int> q;q.push(1);f[1][0] = 0;deep[1] =1;deep[0] =1;while(q.size()){int u = q.front();q.pop();for(int i=head[u];i;i=Edge[i].next){int v= Edge[i].to;if(deep[v]) continue;q.push(v);deep[v] = deep[u]+1;w[v] = w[u] + Edge[i].val;f[v][0] = u;for(int i=1;i<=t;i++){f[v][i] = f[f[v][i-1]][i-1];}}}
}
struct Node{ ll x,res;} p[N];
bool cmp(Node a,Node b){return a.res<b.res;
}
int a[N];
int fg[N];  // 为1有检查点
void search(int u,int f){   // 筛选出H集合,根的儿子节点是0,节点属于H集合if(fg[u]==1) return ;int x = 0;int y  =0;for(int i=head[u];i;i=Edge[i].next){int v= Edge[i].to;if(v==f) continue;x++;search(v,u);if(fg[v]==1) y++;}if(x==y && x>=1) fg[u] = 1;
}
bool check(ll time){//  初始化 memset(fg,0,sizeof(fg));int cnt = 0;for(int i=1;i<=m;i++){int x = a[i];ll res = time;for(int j=t;j>=0;j--){if(deep[f[x][j]]>1 && res>=w[x]-w[f[x][j]]){res -= (w[x] - w[f[x][j]]);x = f[x][j];}}if(deep[x]==2) {//加入p[cnt++] = (Node){x,res};}else fg[x] = 1;        }// 选出H 集合search(1,0);// 注意一些小小细节,有可能一个节点有多个军队呢,但是军队当前的可用时间不一。// 所以距离最小的优先考虑sort(p,p+cnt,cmp);for(int i=0;i<cnt;i++){   // 删除部分H, 即是fg[u] = 1;if(fg[p[i].x]==0 && w[p[i].x]*2 >= p[i].res) {fg[p[i].x] = 1;p[i].res = 0;  // 待在原地就好,所以可用时间记为0}else{p[i].res -=w[p[i].x]; //移步到根节点}}sort(p,p+cnt,cmp);// 筛选出 没走到的根子节点vector<pair<ll,int> > H; // 距离 节点for(int i=head[1];i;i=Edge[i].next){int v= Edge[i].to;if(v==0) continue;if(fg[v]==0) H.push_back(make_pair(w[v],v));}sort(H.begin(),H.end());//  双指针int i=0,j=0;while(i<cnt && j<H.size()){if(p[i].res < H[j].first) i++;else {i++;j++;}}if(j==H.size()) return true;else return false;
}
void solve(){cin>>n;ll l = 0,r= 0;for(int i=1;i<n;i++){ll u,v,val;cin>>u>>v>>val;r+=val;AddEdge(u,v,val);AddEdge(v,u,val);}cin>>m;for(int i=1;i<=m;i++) cin>>a[i];bfs();// for(int i=1;i<=n;i++){//     cout<<i<<":";//     for(int j=0;j<=6;j++){//         printf("(%d) ",f[i][j]);//     }//     cout<<endl;// }ll ans =  -1;while(l<=r){ll mid = (l+r)>>1;if(check(mid)){ans = mid;r = mid -1;}else{l = mid + 1;}}cout<<ans<<endl;
}
int main (){//freopen("in.txt","r",stdin);//freopen("out.txt","w",stdout);solve();getchar();getchar();return 0;
}

《算法竞赛进阶指南》------图论篇2相关推荐

  1. 算法竞赛进阶指南 萌新入门!

    算法竞赛进阶指南 文章目录 算法竞赛进阶指南 前言 一.介绍本书 二.如何阅读本书 三.总结 **笔记思路和结构 ** 算法竞赛进阶指南 这篇文章就简单的写一下吧! 前言 ​ 作为一个想要入坑的算法的 ...

  2. 《算法竞赛进阶指南》数论篇

    <算法竞赛进阶指南>数论篇(1)-最大公约数,素数筛,欧拉函数,同余,欧拉定理,BSGS <算法竞赛进阶指南>数论篇(1)-最大公约数,素数筛,欧拉函数,同余,欧拉定理,BSG ...

  3. 《算法竞赛进阶指南》打卡-基本算法-AcWing 93. 递归实现组合型枚举:递归与递推、dfs、状态压缩

    文章目录 题目解答 题目链接 题目解答 分析: 此题和笔者另一篇博文很像,只不过是限定了个数.<算法竞赛进阶指南>打卡-基本算法-AcWing 92. 递归实现指数型枚举:递推与递归.二进 ...

  4. 《算法竞赛进阶指南(by 李煜东)》习题题解 集合

    又是笔者给自己挖的大坑. 这里是李煜东所著<算法竞赛进阶指南(by 李煜东)>的习题题解集合. 有任何错误请在对应文章下反馈或联系 nicest1919@163.com ,谢谢 qwq 从 ...

  5. CH5202 自然数拆分Lunatic版(算法竞赛进阶指南,完全背包)

    算法竞赛进阶指南,278页,完全背包 本题要点: 1.把完全背包的代码改改即可.常规的完全背包: 有n个物品,每个物品的体积是v[i], 价值是w[i], 求装到大小为m的大背包,能获得的最大价值(每 ...

  6. 中位数--《算法竞赛进阶指南》(货仓选址和七夕祭问题详解)

    中位数 今天又和大家见面了啦~ 依旧是 <算法竞赛进阶指南>的学习哦~ 中位数(Median)又称中值,统计学中的专有名词,是按顺序排列的一组数据中居于中间位置的数,代表一个样本.种群或概 ...

  7. 金字塔(算法竞赛进阶指南)

    虽然探索金字塔是极其老套的剧情,但是有一队探险家还是到了某金字塔脚下. 经过多年的研究,科学家对这座金字塔的内部结构已经有所了解. 首先,金字塔由若干房间组成,房间之间连有通道. 如果把房间看作节点, ...

  8. AcWing 122. 糖果传递【贪心】【《算法竞赛进阶指南》,微软面试题 , HAOI2008】

    AcWing 122. 糖果传递 一.题目链接 二.题目分析 (一)算法标签 (二)解题思路 三.AC代码 四.其它题解 一.题目链接 AcWing 122. 糖果传递 进阶题目 AcWing 105 ...

  9. 算法竞赛进阶指南 骑士放置

    4: 最大独立集 :选出最多的点,使得选出的点之间没有边. 求最大独立集:选出最小的点可以破坏所有的边 <==>最小点覆盖 <==>最大匹配数. 假设最大匹配数为m,共有n个点 ...

  10. 算法竞赛进阶指南0x3A 博弈论之SG函数

    算法竞赛进阶指南0x3A 博弈论之SG函数

最新文章

  1. Dubbo 源码分析 - 服务引用
  2. GRE Sub math 报名
  3. jzoj6803-NOIP2020.9.26模拟tom【构造】
  4. 我为期一个月的GitHub的经验教训
  5. 作者:司恩哲(1985-),男,就职于中国人民银行征信中心数据部
  6. 深挖前端 JavaScript 知识点 —— 史上最全面、最详细的 Cookie 总结
  7. MyCat分布式数据库集群架构工作笔记0023---高可用_单表存储千万级_海量存储_分表扩展范围约定
  8. 【大数据部落】R语言实现:混合正态分布EM最大期望估计法
  9. 通过ffmpeg将FLV文件转换为MP4
  10. about cisco DNA
  11. codeforce 379C New Year Ratings Change 题解
  12. django里template中的书名号
  13. 酷比魔方IWork1X 的做系统问题
  14. python中true什么意思_python中的bool是什么意思
  15. 2023牛客寒假算法集训营3
  16. Python中集合的常用操作
  17. 用Python实现爬虫爬取京东笔记本电脑图片
  18. IVM在什么情况下可以使用
  19. bzoj3083 遥远的国度 bzoj3626 LCA (树链剖分)
  20. ffmpeg截取视频第一帧

热门文章

  1. Javascript保留两位小数
  2. tf28: 手写汉字识别
  3. 量化投资学习——股指期货研究(七)
  4. C语言实现1024bit大数加法(1)
  5. 服务器系统添加休眠,服务器主机休眠功能
  6. 大数据技术——Flume原理分析
  7. 【机器学习】奇异值分解
  8. drf中allow_null,allow_blank的区别
  9. 探索中的VR+新闻,门槛高但是前途光明
  10. 微软服务器vhd,管理 VHD 文件时出错 - Windows Server | Microsoft Docs