相信大家初步了解了倍增之后,想要实践一下。这里选取了牛客的例题来讲解。来个👍 呗, 。
- 入门知识传送门
- 一些简单规定
指图上的最短路,在树上为
的简单路径。
指有根树上的深度。
[AHOI2008]MEET 紧急集合
题意
给出三个点 ,找到一个节点
使
最小。
分析
我们考虑两个节点,那么比较简单,只要不走回头路就是最短的,换而言之就是 上的任意一个节点就可以。那么两个节点可以这样做,三个节点呢?我们可以类比,只要
这三条路径没有重叠,那么这样的
点就是合法的。那么现在如何找这个
点,对于
中任意两个点来说,只要
在简单路径上就可以了。那么我们可以分类讨论。
令
如果
,那么由于
在
上,则
必然是
的祖先,或者子树。那么等同于
,或者
。
那么容易得到
两两求出
那么必然存在两个
是相同的,那么另一个
就是最优答案,那么剩下的就是树上距离公式了
。考虑倍增求
,那么总的复杂度为
。
代码
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define ll long long
int read() {
int x = 0,f = 0;char ch = getchar();
while(!isdigit(ch)) {if(ch == '-')f = 1;ch = getchar();}
while(isdigit(ch)) {x = x * 10 + ch - '0';ch = getchar();}
return f ? -x : x;
}
const int N = 5e5 + 100;
struct Edge {int to,nxt;}e[N << 1];
int Log[N],fa[N][21],dep[N],head[N],n,m,ecnt;
void add(int x,int y) {e[++ecnt] = (Edge){y,head[x]};head[x] = ecnt;}
int lca(int a,int b) {
if(dep[a] < dep[b]) swap(a,b);int k = dep[a] - dep[b];
for(int i = Log[k];~i;i--) if((k >> i) & 1) a = fa[a][i];
if(a == b) return a;
for(int i = Log[dep[a]];~i;i--) if(fa[a][i] ^ fa[b][i]) a = fa[a][i],b = fa[b][i];
return fa[a][0];
}
void dfs(int x,int F,int D) {
fa[x][0] = F;dep[x] = D;
for(int i = 1;i <= Log[dep[x]];i++) fa[x][i] = fa[fa[x][i - 1]][i - 1];
for(int i = head[x];i;i = e[i].nxt) {int y = e[i].to;if(y == F) continue;dfs(y,x,D + 1);}
}
int tdis(int a,int b) {return dep[a] + dep[b] - 2 * dep[lca(a,b)];}
int solve(int x,int a,int b,int c) {
return tdis(x,a) + tdis(x,b) + tdis(x,c);
}
int main() {
n = read();m = read();
for(int i = 2;i <= n;i++) Log[i] = Log[i >> 1] + 1;
for(int i = 1,a,b;i < n;i++) {
a = read();b = read();
add(a,b);add(b,a);
}
dfs(1,0,1);
while(m--) {
int a = read(),b = read(),c = read(),ans = 0,dis = 0;
int t1 = lca(a,b),t2 = lca(b,c),t3 = lca(a,c);
if(t1 == t2) ans = t3,dis = solve(t3,a,b,c);
if(t1 == t3) ans = t2,dis = solve(t2,a,b,c);
if(t2 == t3) ans = t1,dis = solve(t1,a,b,c);
printf("%d %d\n",ans,dis);
}
}疫情控制
题意
给你一个有根树,根节点为 ,要求同时调动多支军队,使军队出现任意叶子节点到
的路径上(
号节点不能出现军队)。求时间的最小值。
分析
由于你可以同时调动,那么其实就使求最大调度时间最小,那么这个可以考虑二分答案,将问题转化为判断问题。那么现在就是考虑在 的情况下是否可以使军队出现在任意叶子节点到
的路径上。
贪心,我们可以容易想到,由于可以同时调动,那么对于任意一支军队,都要使他尽量的往上,因为这样不会使答案更劣,但是这里我们并不能慢慢跳父亲,而且考虑倍增,考虑的方式可以类比倍增求
的过程,而且同时保存
的答案。
我们做完上一步,现在发现,军队被划分成两种。
- 可以到达
号节点。
- 不可以到达
号节点。
- 可以到达
那么我们先对所有子树遍历,考虑这个子树是否可以不需要军队了。那么最后我们剩下的就是,一些还需要军队来出现的子树和一些搁置在
号节点的军队。对于每个子树我们记录它的根节点到
的距离为
,和每一支可以到达
号节点还剩余的时间
。那么现在问题转化为。是否可以在
序列中选取
个元素,使
满足
。这个可以考虑贪心,将
和
都从小到大排序,维护两个指针
。
- 如果
的子树需要一个军队,那么我们可以考虑不让
跳到
号节点,直接使
的子树变为已覆盖,因为每一个没有被覆盖的子树至少需要一个军队到覆盖,那么在
还没有被访问时覆盖,这样一定可以使答案不更劣。
- 如果
直接覆盖,考虑下一个
和
。
- 如果
,考虑下一个
。
- 如果
最后判断所有子树都被覆盖就可以了,这样总的复杂度为 。
代码
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define ll long long
int read() {
int x = 0,f = 0;char ch = getchar();
while(!isdigit(ch)) {if(ch == '-')f = 1;ch = getchar();}
while(isdigit(ch)) {x = x * 10 + ch - '0';ch = getchar();}
return f ? -x : x;
}
const int N = 51010,inf = 0x3f3f3f3f;
struct Edge {int to,nxt,w;}e[N << 1];
int Log[N],head[N],f[N][21],g[N][21],dep[N],m,n,ecnt;
void add(int x,int y,int w) {e[++ecnt] = (Edge){y,head[x],w};head[x] = ecnt;}
void Init(int x,int fa,int de,int dis) {
dep[x] = de;f[x][0] = fa;g[x][0] = dis;
for(int i = 1;i <= Log[de];i++) f[x][i] = f[f[x][i - 1]][i - 1],g[x][i] = g[f[x][i - 1]][i - 1] + g[x][i - 1];
for(int i = head[x];i;i = e[i].nxt) {int y = e[i].to;if(y ^ fa) Init(y,x,de + 1,e[i].w);}
}
int Id[N],sub[N],top,Top;
bool vis[N];
pii st[N],St[N];
void init(int x,int fa,int Sub) {sub[x] = Sub;for(int i = head[x];i;i = e[i].nxt) if(e[i].to ^ fa) init(e[i].to,x,Sub);}
void solve(int x) {
bool P = 1;if(vis[x]) return;
for(int i = head[x];i;i = e[i].nxt) {
int y = e[i].to;if(y == f[x][0]) continue;
P = 0;solve(y);if(x != 1 && vis[y] == 0) return;
} if(x != 1 && P == 0) vis[x] = 1;
}
int getk(int x,int tot,int &z) {
z = 0;for(int i = Log[dep[x]];~i;i--) {
if(f[x][i] && g[x][i] <= tot) tot -= g[x][i],z += g[x][i],x = f[x][i];
}return x;
}
bool check(int mid) {
memset(vis,0,sizeof(vis));top = 0;Top = 0;
for(int i = 1;i <= m;i++) {
int x = Id[i],t;
x = getk(x,mid,t);
if(x != 1) vis[x] = 1;
else st[++top] = pii(mid - t,sub[Id[i]]);
}solve(1);
for(int i = head[1];i;i = e[i].nxt) {
int y = e[i].to;if(vis[y]) continue;
St[++Top] = pii(e[i].w,e[i].to);
}
sort(st + 1,st + 1 + top);sort(St + 1,St + 1 + Top);
int id = 1;
for(int i = 1;i <= top;i++) {
if(!vis[st[i].second]) vis[st[i].second] = 1;
else if(st[i].first >= St[id].first) vis[St[id].second] = 1;
while(vis[St[id].second] && id <= Top) ++id;
}
return id > Top;
}
int main() {
n = read();
for(int i = 2;i <= n;i++) Log[i] = Log[i >> 1] + 1;
for(int i = 1;i < n;i++) {
int a = read(),b = read(),c = read();
add(a,b,c);add(b,a,c);
}
Init(1,0,1,0);
for(int i = head[1];i;i = e[i].nxt) init(e[i].to,1,e[i].to);
m = read();int res = m;
for(int i = 1;i <= m;i++) Id[i] = read();
for(int i = 1;i <= n;i++) res -= (dep[i] == 2);
if(res < 0) {printf("-1\n");return 0;}
else {
int l = 0,r = inf,ans = 0;
while(l <= r) {
int mid = (l + r) >> 1;
if(check(mid)) ans = mid,r = mid - 1;
else l = mid + 1;
}printf("%d\n",ans);return 0;
}
}A and B and Lecture Rooms
题意
给你俩个节点 和一棵无根树,找出树上满足
的节点个数。
分析
我们比较好思考的就是 至多只有一个合法点,而且这个合法点一定是满足
的,所以当
时是一定无解的。那么我们现在考虑
的节点位置,比较好分析的是
只会在
处发生一次转折,所以
是
中深度较深的祖先,具体来讲是它的
级祖先
。现在只需要讨论
和
的关系就好了。
那么要使
,那么可以选择的节点就是,所有的节点除去以
为根的,
所在的两个子树的所有节点。
那么要使
,那么可以选择的节点就是,以祖先
为根的子树中,除去深度较深的
所处的子树节点。
那么总的复杂度为 ,因为我们需要查找
级祖先和查找
。
代码
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define ll long long
int read() {
int x = 0,f = 0;char ch = getchar();
while(!isdigit(ch)) {if(ch == '-')f = 1;ch = getchar();}
while(isdigit(ch)) {x = x * 10 + ch - '0';ch = getchar();}
return f ? -x : x;
}
const int N = 1e5 + 100;
struct Edge {int to,nxt;}e[N << 1];
int fa[N][21],siz[N],dep[N],Log[N],n,head[N],ecnt;
void add(int x,int y) {e[++ecnt]=(Edge){y,head[x]};head[x]=ecnt;}
int get(int x,int k) {
for(int i = Log[k];~i;i--) if((k >> i) & 1) x = fa[x][i];
return x;
}
int lca(int x,int y) {
if(dep[x] < dep[y]) swap(x,y);int k = dep[x] - dep[y];
x = get(x,k);if(x == y) return x;for(int i = Log[dep[x]];~i;i--) {
if(fa[x][i] ^ fa[y][i]) x = fa[x][i],y = fa[y][i];
}return fa[x][0];
}
void dfs(int x,int F,int D) {
siz[x] = 1;fa[x][0] = F;dep[x] = D;
for(int i = 1;i <= Log[dep[x]];i++) fa[x][i] = fa[fa[x][i - 1]][i - 1];
for(int i = head[x];i;i = e[i].nxt)
{int y = e[i].to;if(y == F) continue;dfs(y,x,D + 1);siz[x] += siz[y];}
}
int main() {
n = read();
for(int i = 2;i <= n;i++) Log[i] = Log[i >> 1] + 1;
for(int i = 1;i < n;i++) {
int u = read(),v = read();
add(u,v);add(v,u);
}
dfs(1,0,1);int Q = read();
while(Q--) {
int x = read(),y = read(),z = lca(x,y);
if(x == y) printf("%d\n",n);
else if(dep[x] == dep[y]) {
int k = dep[x] - dep[z] - 1;
x = get(x,k);y = get(y,k);
printf("%d\n",n - siz[x] - siz[y]);
}
else {
int len = dep[x] + dep[y] - dep[z] * 2;
if(len % 2) printf("0\n");
else {
if(dep[x] < dep[y]) swap(x,y);
z = get(x,len / 2);x = get(x,len / 2 - 1);
printf("%d\n",siz[z] - siz[x]);
}
}
}
}[SCOI2015]国旗计划
题意
给出 条线段,要求覆盖完整个环。求出某一条必选之后,至少需要的线段数量,线段无包含关系。
分析
常见的破环成链,那么我们把链倍长一次,现在就是要覆盖长度为 的序列就可以了。那么由于线段没有包含关系,那么线段相离或者有交集。那么我们按左端点排序,得到的区间右端点应该是递增的。这样我们就可以贪心,显然,如果我们要从一个区间转移到另一个区间,那么另一个区间的右端点一定是,所有左端点小于当前右端点的所有区间中的最大值。那么我们的每一次跳跃是固定的,这里我们就可以考虑用倍增来维护,这样单次询问的时间复杂度为
加上离散化的复杂度,总的复杂度为
。
代码
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define ll long long
int read() {
int x = 0,f = 0;char ch = getchar();
while(!isdigit(ch)) {if(ch == '-')f = 1;ch = getchar();}
while(isdigit(ch)) {x = x * 10 + ch - '0';ch = getchar();}
return f ? -x : x;
}
const int N = 8e5 + 1000;
struct Node {int l,r;}q[N],rq[N];
int B[N],nxt[N][21],n,m,tot,top;
bool cmp(Node a,Node b) {return a.l < b.l;}
int main() {
n = read();m = read();
for(int i = 1;i <= n;i++) {
int l = read(),r = read();
q[i] = (Node){l,r};
if(r < l) rq[++tot] = (Node) {l,r + m}, rq[++tot] = (Node) {l + m,m * 2};
else rq[++tot] = (Node) {l,r}, rq[++tot] = (Node) {l + m,r + m};
}
for(int i = 1;i <= tot;i++) B[++top] = rq[i].l , B[++top] = rq[i].r;
sort(B + 1,B + 1 + top);top = unique(B + 1,B + 1 + top) - B - 1;
sort(rq + 1,rq + 1 + tot,cmp);
for(int i = 1,r = 0,l = 1;i <= top;i++) {
while(l <= tot && rq[l].l <= B[i]) r = max(rq[l].r,r),l++;
nxt[i][0] = lower_bound(B + 1,B + 1 + top,r) - B;
// cout << nxt[i][0] << " " << top << endl;
}
for(int i = 1;i <= 20;i++) {
for(int j = 1;j <= top;j++) nxt[j][i] = nxt[nxt[j][i - 1]][i - 1];
}
for(int i = 1;i <= n;i++) {
if(q[i].l > q[i].r) q[i].r += m;int ans = 1;
int pos = lower_bound(B + 1,B + 1 + top,q[i].r) - B;
for(int j = 20;~j;j--) {
if(B[nxt[pos][j]] < q[i].l + m) {
ans += (1 << j);pos = nxt[pos][j];
}
}
ans++;printf("%d ",ans);
}printf("\n");
return 0;
} Smile House
题意
给你一张有向图,求出最小节点个数的正环。
分析
由于我不喜欢正环,代码中把每条边的权值取反,求的是负环,分析也采用负环分析。我们考虑直接枚举点数来转移,定义 表示,考虑
个点,从
到
的最短路,那么如果出现负环
。但是这样复杂度为
的,考虑弗洛伊德最短路,弗洛伊德的转移矩阵,每次是相同的,那么我们可以考虑把
的转移矩阵存下来,那么再二分一个答案,这样复杂度就是
了,其实任意可以从单调性分析,我们从大到小枚举
中的
,这样复杂度就可以减低到
了,分析可以类比
的分析(见入门知识)。
代码
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define ll long long
int read() {
int x = 0,f = 0;char ch = getchar();
while(!isdigit(ch)) {if(ch == '-')f = 1;ch = getchar();}
while(isdigit(ch)) {x = x * 10 + ch - '0';ch = getchar();}
return f ? -x : x;
}
const int N = 310;
int f[12][N][N],last[N][N],now[N][N],n,m;
int main() {
n = read();m = read();
memset(f,0x3f,sizeof(f));
for(int i = 1;i <= n;i++) f[0][i][i] = 0;
for(int i = 1;i <= m;i++) {
int a = read(),b = read(),c = -read(),d = -read();
f[0][a][b] = c;f[0][b][a] = d;
}
for(int l = 1;l <= 10;l++) {
for(int k = 1;k <= n;k++) {
for(int i = 1;i <= n;i++) {
for(int j = 1;j <= n;j++) {
f[l][i][j] = min(f[l][i][j],f[l - 1][i][k] + f[l - 1][k][j]);
}
}
}
}
memset(last,0x3f,sizeof(last));
for(int i = 1;i <= n;i++) last[i][i] = 0;
int ans = 0,ss = 0;
for(int l = 10;~l;l--) {
if(ans > n) break;
memcpy(now,last,sizeof(now));
for(int k = 1;k <= n;k++) {
for(int i = 1;i <= n;i++) {
for(int j = 1;j <= n;j++) {
now[i][j] = min(now[i][j],f[l][i][k] + last[k][j]);
}
}
}
int flag = 1;
for(int i = 1;i <= n;i++) {
if(now[i][i] < 0) {
flag = 0;ss = 1;break;
}
}
if(flag) {
ans += (1 << l);
for(int i = 1;i <= n;i++) for(int j = 1;j <= n;j++) last[i][j] = now[i][j];
}
}
if(ss) cout << ans + 1 << endl;
else cout << "0" << endl;
}[SCOI2016]萌萌哒
题意
在某些区间必须一样的情况下,可以组成多少个大数。
分析
这个区间必须一样我们可以考虑并查集来维护,但是我们合并并查集时就需要 的时间,这个必须优化,还是像
表,我们只需要维护
长度的所有区间就可以了,我们可以考虑一个区间拆除
个,然后不断递归下去,这样总复杂度看似为
的,但是考虑到只有
状态,而每一次递归势能都会减少,那么最后的复杂度为
了,那么最后就只需要考虑集合个数了。
代码
偷懒,用老师的代码了。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#define ll long long
using namespace std;
inline int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
const int P = 30;
const int N = 550000 + 10;
const int mod = 1e9 + 7;
int n, m;
int logn[N], fa[N][P];
void in_it() {
for (int p = 0; p < 19; p++)
for (int i = 1; i <= n; i++) fa[i][p] = i;
logn[1] = 0;
for (int i = 2; i <= n; i++) logn[i] = logn[i >> 1] + 1;
}
int find(int i, int j) {
if (fa[i][j] == i)
return i;
return fa[i][j] = find(fa[i][j], j);
}
void link(int x, int y, int k) {
if (k < 0)
return;
int fx = find(x, k), fy = find(y, k);
if (fx == fy)
return;
fa[fy][k] = fx;
link(x, y, k - 1);
link(x + (1 << k - 1), y + (1 << k - 1), k - 1);
}
int cnt = 0;
bool vis[N];
ll mpow(ll a, ll b) {
ll rt = 1;
for (rt; b; b >>= 1, a = a * a % mod) {
if (b & 1)
rt = rt * a % mod;
}
return rt;
}
int main() {
//n = read(), m = read();
cin>>n>>m;
in_it();
while (m--) {
int l1,r1,l2,r2;
cin>>l1>>r1>>l2>>r2;
int k = logn[r1 - l1 + 1];
link(l1, l2, k);
link(r1 - (1 << k) + 1, r2 - (1 << k) + 1, k);
}
for (int i = 1; i <= n; i++) {
int x = find(fa[i][0], 0);
if (!vis[x])
vis[x] = true, ++cnt;
}
ll ans;
if (cnt == 1)
ans = 10;
else {
ans = mpow(10, cnt - 1);
ans = 1LL * ans * 9 % mod;
}
cout << ans << endl;
return 0;
}
京公网安备 11010502036488号