7.17 学习笔记
作者:互联网
7.17 学习笔记
1 RMQ Range Mximum/Minimun Query
区间最值问题。
1.1 st 表
设 \(f_{i,j}\) 表示从 \(i\) 开始,取 \(2^j\) 个数的最小值。形式化来说,取得是 \([i,i+2^j-1]\) 的最小值。
其实是一个倍增的过程。可以做到 \(O(n\log n)\) 预处理,\(O(1)\) 查询最值。
同样运用倍增思想的还有快速幂。
过于简单,不放代码。
2 LCA
2.1 倍增求 LCA
我们只需要利用倍增的思想预处理出所有节点 \(2\) 的幂网上的父亲是谁,以及每个点的深度,然后往上跳就可以。
树上倍增法同时也是我们在树上收集信息的常用方法。
预处理复杂度 \(O(n\log n)\) 。询问复杂度 \(O(\log n)\) 。
过于简单,不过代码。
2.2 \(O(1)\) 求 LCA
这里给出欧拉序的严格定义。如图:
也就是说,我们 dfs 每次经过都要把该点加入欧拉序。我们发现每一个点加入的次数是所有儿子的个数加 \(1\) ,
那么除了根节点不给其它节点做儿子外,其它节点都给别人做儿子,所以除了根节点,每一个点的贡献都是 \(2\) 。
这个 \(2\) 的贡献,一个是给别人当儿子,另一个是其本身。
容易证明,在欧拉序上,设 \(x\) 第一次出现的编号为 \(x_0\) ,\(y\) 第一次出现的编号为 \(y_1\) ,那么 \(x,y\) 的 lca 在欧拉序上应该是欧拉序上\(x_0,y_0\) 之间深度最小的点。证明这个东西只需要证明 lca 一定在这个区间并且在这个区间内不会出现比 lca 深度小或相等的点。这两者都容易证明。
以前 \(O(1)\) lca 并没有打过,所以我们这里把代码写一下。
不知道是不是我打的常数太大了,跑的没有树链剖分快,也许是洛谷模板题询问有点少。
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 1000010
#define M 3000010
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
template<typename T> inline T Min(T a,T b){
return a<b?a:b;
}
struct edge{
int to,next;
inline void intt(int to_,int ne_){
to=to_;next=ne_;
}
};
edge li[M];
int head[N],tail;
inline void add(int from,int to){
li[++tail].intt(to,head[from]);
head[from]=tail;
}
struct point_deep{
int id,deep;
inline point_deep() {}
inline point_deep(int id,int deep) : id(id),deep(deep) {}
inline bool operator < (const point_deep &b) const{
return deep<b.deep;
}
};
point_deep xulie[N];
int deep[N],tailx,FirApp[N];
inline void dfs(int k,int fa){
deep[k]=deep[fa]+1;
xulie[++tailx]=point_deep(k,deep[k]);
if(!FirApp[k]) FirApp[k]=tailx;
for(int x=head[k];x;x=li[x].next){
int to=li[x].to;
if(to==fa) continue;
dfs(to,k);
xulie[++tailx]=point_deep(k,deep[k]);
}
}
point_deep st[N][21];
inline void build_st(){
for(int i=1;i<=tailx;i++) st[i][0]=xulie[i];
for(int i=1;i<=20;i++){
for(int j=1;j+(1<<i)-1<=tailx;j++){
st[j][i]=Min(st[j][i-1],st[j+(1<<(i-1))][i-1]);
}
}
}
inline point_deep query(int l,int r){
int len=log2(r-l+1);
return Min(st[l][len],st[r-(1<<len)+1][len]);
}
int n,m,s;
int main(){
read(n);read(m);read(s);
for(int i=1;i<=n-1;i++){
int from,to;read(from);read(to);
add(from,to);add(to,from);
}
dfs(s,0);
build_st();
for(int i=1;i<=m;i++){
int l,r;
read(l);read(r);
int fl=FirApp[l],fr=FirApp[r];
if(fl>fr) swap(fl,fr);
point_deep nowans=query(fl,fr);
printf("%d\n",nowans.id);
}
return 0;
}
2.3 LCA 的应用
LCA 有很多应用,包括但不限于树上差分,求树上两点距离,轻重链剖分也需要用 lca。
这些东西我好像都写过博客,所以这里不再赘述。
3 二维 st 表
对于一个二维数组有对应的二维 st 表。设 \(f_{i,j,a,b}\) 表示横坐标在 \([i,i+2^a-1]\) 纵坐标在 \([j,j+2^b+1]\) 之间的最值。
转移也是类似的。我们直接按照二进制分割就可以。
代码:
咕咕咕
4 例题
过水的题不整理。
4.1 删数问题加强版
我们考虑选首位,首先一定要让首位选最好的,在给定 \(m\) 的情况下,首位能选的位置是有限的,选最小的数,这是一个经典的 RMQ 问题,所以我们可以用 st 表做。接下来更新下一个的备选区间就可以。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 1010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
template<typename T> T Min(T a,T b){
return a>b?b:a;
}
typedef pair<int,int> P;
P st[N][10];
int ans[N],tail,a[N],len,m,lg2[N];
char s[N];
inline void build_st(){
memset(st,0,sizeof(st));
lg2[0]=-1;for(int i=1;i<=len;i++) lg2[i]=lg2[i/2]+1;
for(int i=1;i<=len;i++){
st[i][0].first=a[i];st[i][0].second=i;
// printf("i:%d j:0 st:%d %d\n",i,st[i][0].first,st[i][0].second);
}
for(int i=1;i<=10;i++){
for(int j=1;j+(1<<i)-1<=len;j++){
st[j][i]=Min(st[j][i-1],st[j+(1<<(i-1))][i-1]);
// printf("i:%d j:%d st:%d %d\n",i,j,st[j][i].first,st[j][i].second);
}
}
}
inline P query(int l,int r){
int lglen=lg2[r-l+1];
return Min(st[l][lglen],st[r-(1<<lglen)+1][lglen]);
}
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
while(cin>>s>>m){
tail=0;
len=strlen(s);
for(int i=1;i<=len;i++) a[i]=s[i-1]-'0';
int l=1;m=len-m;build_st();
for(int i=m;i>=1;i--){
P now=query(l,len-m+1);
ans[++tail]=now.first;
l=now.second+1;m--;
}
int head=0;
while(head+1<tail&&ans[head+1]==0) head++;
for(int i=head+1;i<=tail;i++) printf("%d",ans[i]);
putchar('\n');
}
}
4.2 Difference Is Beautiful
首先发现答案有二分性,然后考虑以固定点为端点向右能够延伸的最大长度是多少,这个东西可以用双指针 \(O(n)\) 统计,我们考虑二分答案。设当前二分为 \(mid\) ,设当前询问区间为 \(l,r\) ,所以我们可以用 \(st\) 表维护长度最值,对于一个区间,我们只需要求 \([l,r-mid+1]\) 的最值就可以了,看这个最值是否大于等于 \(mid\) ,为什么是 \(r-mid+1\) 是因为要保证区间长度至少要保证有 \(mid\)。
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 200010
#define M 2000010
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e6;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
template<typename T> inline void write(T a){
if(a<0){putchar('-');a=-a;}
if(!a) return;
write(a/10);putchar(a%10+'0');
}
template<typename T> inline T Max(T a,T b){
return a<b?b:a;
}
int n,m,a[N],cnt[M],b[N],log_2[N];
int st[N][21];
// build
inline void build_st(){
log_2[0]=-1;for(int i=1;i<=n;i++) log_2[i]=log_2[i/2]+1;
for(int i=1;i<=n;i++) st[i][0]=b[i];
for(int i=1;i<=log_2[n];i++){
for(int j=1;j+(1<<i)-1<=n;j++){
st[j][i]=Max(st[j][i-1],st[j+(1<<(i-1))][i-1]);
}
}
}
inline int query(int l,int r){
int len=log_2[r-l+1];
return Max(st[l][len],st[r-(1<<len)+1][len]);
}
inline bool check(int z,int y,int mid){
if(mid>(y-z+1)) return 0;
int res=query(z,y-mid+1);
if(res>=mid) return 1;
else return 0;
}
inline int erfen(int z,int y){
int l=1,r=n,ans;
while(l<=r){
int mid=(l+r)>>1;
if(check(z,y,mid)) ans=mid,l=mid+1;
else r=mid-1;
}
return ans;
}
int main(){
read(n);read(m);
for(int i=1;i<=n;i++){read(a[i]);a[i]+=mod;}
for(int l=1,r=0;l<=n;l++){
while(r+1<=n&&cnt[a[r+1]]<=0){r++;cnt[a[r]]++;}
b[l]=r-l+1;cnt[a[l]]--;
}
build_st();
for(int i=1;i<=m;i++){
int z,y;read(z);read(y);z++;y++;
int ans=erfen(z,y);
printf("%d\n",ans);
}
return 0;
}
4.3 CF1301E Nanosoft
咕咕咕
4.4 UVA1707 Surveillance
咕咕咕
4.5 CF609E Minimum spanning tree for each edge
我们考虑先求出最小生成树,设边权和为 \(sum\)。
对于一条边,如果他在最小生成树上,那么就可以不做任何操作。
否则,我们在树上找到这两个点路径上的最大值,建取最大值,然后把我们当前的这条边加进去。
上面这个贪心的想法正确性显然,因为我们要保证权值尽量小。
至于两个点路径上的最大值,我们可以用树上倍增法来预处理。
在与处理时注意特判:
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define int long long
#define uint unsigned int
#define ull unsigned long long
#define N 200010
#define M 200010
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
template<typename T> inline T Max(T a,T b){
return a<b?b:a;
}
struct UnionFindSet{
int fa[N];
inline void init(int n){
for(int i=1;i<=n;i++) fa[i]=i;
}
inline int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
inline bool Merge_IsTheSame(int a,int b){
int faa=find(a),fab=find(b);
if(faa==fab) return 1;
fa[faa]=fab;return 0;
}
};
UnionFindSet ufs;
struct Bian{
int from,to,w,id;
inline bool operator < (const Bian &b)const{
return w<b.w;
}
};
Bian bian[M];
bool IsChoose[M];
struct edge{
int to,next,w;
inline void intt(int to_,int ne_,int w_){
to=to_;next=ne_;w=w_;
}
};
edge li[M<<1];
int head[N],tail;
inline void add(int from,int to,int w){
li[++tail].intt(to,head[from],w);
head[from]=tail;
}
int n,m;
inline int Kruscal(){
ufs.init(n);
sort(bian+1,bian+m+1);
int cnt=0,nowans=0;
for(int i=1;i<=m;i++){
if(!ufs.Merge_IsTheSame(bian[i].from,bian[i].to)){
IsChoose[i]=1;nowans+=bian[i].w;
add(bian[i].from,bian[i].to,bian[i].w);add(bian[i].to,bian[i].from,bian[i].w);
}
if(tail==2*n-2) break;
}
return nowans;
}
int ans[M];
int fa[N][21],MaxEdge[N][21],deep[N];
inline void dfs(int k,int fat){
fa[k][0]=fat;for(int i=1;i<=20;i++) fa[k][i]=fa[fa[k][i-1]][i-1];
deep[k]=deep[fat]+1;
for(int i=1;i<=20;i++) MaxEdge[k][i]=Max(MaxEdge[k][i-1],MaxEdge[fa[k][i-1]][i-1]);
for(int x=head[k];x;x=li[x].next){
int to=li[x].to,w=li[x].w;
if(to==fat) continue;
MaxEdge[to][0]=w;dfs(to,k);
}
}
inline int GetMax(int a,int b){
int maxx=-INF;
if(deep[a]<deep[b]) swap(a,b);
for(int i=20;i>=0;i--)
if(deep[fa[a][i]]>=deep[b]){maxx=Max(maxx,MaxEdge[a][i]);a=fa[a][i];}
for(int i=20;i>=0;i--)
if(fa[a][i]!=fa[b][i]){
maxx=Max(maxx,Max(MaxEdge[a][i],MaxEdge[b][i]));a=fa[a][i];b=fa[b][i];
}
if(a!=b) maxx=Max(MaxEdge[a][0],Max(maxx,MaxEdge[b][0]));
return maxx;
}
signed main(){
read(n);read(m);
for(int i=1;i<=m;i++){
read(bian[i].from);read(bian[i].to);read(bian[i].w);bian[i].id=i;
}
int nowans=Kruscal();dfs(1,0);
for(int i=1;i<=m;i++){
if(IsChoose[i]){ans[bian[i].id]=nowans;continue;}
int now=GetMax(bian[i].from,bian[i].to);
ans[bian[i].id]=nowans-now+bian[i].w;
}
for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
return 0;
}
标签:7.17,return,int,mid,long,学习,笔记,getchar,define 来源: https://www.cnblogs.com/TianMeng-hyl/p/15025786.html