DFS序-树链剖序-欧拉序

,二叉树是一颗线段树,树状数组,树上的每个维护节点负责维护一个区间信息,节点之间又包含和属于的关系。例如线段树:

DFS序:

我们通过对每个节点设置两个量,in和out。从根节点开始DFS搜索,in为第一次搜索到时的时间戳,out为退出出栈时的时间戳。

可以得到,例如我们要查询以b为根节点我们只需要查询区间[2,5];要查询以c为根节点子树的信息,我们可以查询区间[6,7];查询a需要查询区间[1,8]。在程序中,当要查询修改时,我们可以用线段树去维护,因为这些序列的性质和线段树太像了。我们要注意线段树的父区间等于两个子区间的和,而这个序列的区间等于父节点以及所有后代节点,例如b树,区间[2,5]包括了b点和d,e,f点,所以在线段树的区间中我们要保存的是原图中的子树。但是对于一些不规则的区间,例如查询[5,7]或是[2,4],虽然身在线段树上我们能找到它,但是在原图中没有什么实际意义或者我不易知道这个区间的意义。所以查找时,一般是通过点找区间,而不是直接查找区间。

[1,1],[2,2],[3,3]...[x,x]代表了原图中节点x的值,in[x],out[x]对应了以x为根节点子树的信息

来两道例题:

卡卡屋前有一株苹果树,每年秋天,树上长了许多苹果。卡卡很喜欢苹果。树上有N个节点,卡卡给他们编号1到N,根的编号永远是1.每个节点上最多结一个苹果。卡卡想要了解某一个子树上一共结了多少苹果。

现在的问题是不断会有新的苹果长出来,卡卡也随时可能摘掉一个苹果吃掉。你能帮助卡卡吗?

Input

输入数据:第一行包含一个整数NN <= 100000),表示树上节点的数目。
接下来N-1行,每行包含2个整数uv,表示uv是连在一起的。
下一行包含一个整数MM ≤ 100,000).
接下来M行包含下列两种命令之一:
"x" 表示某个节点上的苹果发生了变化,如果原来没有苹果,则现在长出了一个苹果;如果原来有苹果,则是卡卡把它吃了。
"x" 表示查询x节点上的子树上的苹果有多少。包含节点x.

Output

对于每次查询,输出其结果。

查询子树,修改节点,,线段树节点维护区间( in[x] , out[x] ) 编号x的及其后代节点的和,非常裸

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;

const int maxn=1e5+10;
struct node{
    int u,v,nxt;
}g[maxn];
int head[maxn],in[maxn],out[maxn],sum[maxn*4],tim;
void pushup(int rt){
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void build(int rt,int L,int R){
    if(L==R){
        sum[rt]=1;
        return ;
    }
    int mid=(L+R)>>1;
    build(rt<<1,L,mid);
    build(rt<<1|1,mid+1,R);
    pushup(rt);
}
void update(int rt,int L,int R,int pos){
    if(L==R){
        if(sum[rt])
            sum[rt]=0;
        else
            sum[rt]=1;
        return ;
    }
    int mid=(L+R)>>1;
    if(pos<=mid)
        update(rt<<1,L,mid,pos);
    else
        update(rt<<1|1,mid+1,R,pos);
    pushup(rt);
}
int query(int rt,int L,int R,int l,int r){
    if(l<=L&&r>=R){
        return sum[rt];
    }
    int mid=(L+R)>>1,ans=0;
    if(r<=mid)
        ans=query(rt<<1,L,mid,l,r);
    else
        if(l>mid)
            ans=query(rt<<1|1,mid+1,R,l,r);
        else
            ans=query(rt<<1,L,mid,l,r)+query(rt<<1|1,mid+1,R,l,r);
    return ans;
}
void dfs(int now,int fa){
    in[now]=++tim;
    for(int i=head[now];i!=-1;i=g[i].nxt)
    {
        if(g[i].v==fa) continue;
        dfs(g[i].v,now);
    }
    out[now]=tim;
}

int main(){
    int n,m,cnt=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) head[i]=-1;
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        g[++cnt].u=u;
        g[cnt].v=v;
        g[cnt].nxt=head[u];
        head[u]=cnt;
    }
    dfs(1,0);
    build(1,1,n);
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        char ch;
        int x;
        getchar();
        scanf("%c%d",&ch,&x);
        if(ch==‘C‘)
            update(1,1,n,in[x]);
        else if(ch==‘Q‘)
            cout<<query(1,1,n,in[x],out[x])<<endl;
    }
    return 0;
}

百度科技园内有nn个零食机,零食机之间通过n−1n−1条路相互连通。每个零食机都有一个值vv,表示为小度熊提供零食的价值。

由于零食被频繁的消耗和补充,零食机的价值vv会时常发生变化。小度熊只能从编号为0的零食机出发,并且每个零食机至多经过一次。另外,小度熊会对某个零食机的零食有所偏爱,要求路线上必须有那个零食机。

为小度熊规划一个路线,使得路线上的价值总和最大。

Input输入数据第一行是一个整数T(T≤10)T(T≤10),表示有TT组测试数据。

对于每组数据,包含两个整数n,m(1≤n,m≤100000)n,m(1≤n,m≤100000),表示有nn个零食机,mm次操作。

接下来n−1n−1行,每行两个整数xx和y(0≤x,y<n)y(0≤x,y<n),表示编号为xx的零食机与编号为yy的零食机相连。

接下来一行由nn个数组成,表示从编号为0到编号为n−1n−1的零食机的初始价值v(|v|<100000)v(|v|<100000)。

接下来mm行,有两种操作:0 x y0 x y,表示编号为xx的零食机的价值变为yy;1 x1 x,表示询问从编号为0的零食机出发,必须经过编号为xx零食机的路线中,价值总和的最大值。

本题可能栈溢出,辛苦同学们提交语言选择c++,并在代码的第一行加上:

`#pragma comment(linker, "/STACK:1024000000,1024000000") `Output对于每组数据,首先输出一行”Case #?:”,在问号处应填入当前数据的组数,组数从1开始计算。

对于每次询问,输出从编号为0的零食机出发,必须经过编号为xx零食机的路线中,价值总和的最大值。 
Sample Input

1
6 5
0 1
1 2
0 3
3 4
5 3
7 -5 100 20 -5 -7
1 1
1 3
0 2 -1
1 1
1 5

Sample Output

Case #1:
102
27
2
20

预处理每个节点到编号0的距离,线段树维护区间中的编号,到编号0的距离,查询经过x,就是查询,(in[x],out[x])即,x及x图中的后代节点到根节点的最大值

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<iostream>//单点改值 最大值
#include<string.h>
#include<stdio.h>
#include<vector>
using namespace std;
typedef long long LL;
const long long inf=1e17;
const int maxn=1e5+5;
vector<int> g[maxn];
LL dis[maxn],w[maxn];
int dfs_clock;
int n,m;
struct Tree{
    LL add,v;
}sum[maxn<<2];
int fa[maxn];//父节点
int dep[maxn];//深度
int sz[maxn];//以o为根节点的子树节点数
int son[maxn];//重子节点
int rk[maxn];//rk[i]=u树链序为i的节点是U
int top[maxn];//节点O所在重链的顶部节点
int id[maxn];//节点o在树链序的位置
void init(){
    dfs_clock=0;
//    memset(dis,0,sizeof(dis));
    memset(w,0,sizeof(w));
    memset(fa,0,sizeof fa);
    memset(dep,0,sizeof dep);
    memset(sz,0,sizeof sz);
    memset(son,0,sizeof son);
    memset(rk,0,sizeof rk);
    memset(top,0,sizeof top);
    memset(id,0,sizeof id);
    memset(sum,0,sizeof sum);
    for(int i=0;i<maxn;i++){
        sum[i].add=sum[i].v=0;
    }
    for(int i=0;i<maxn;i++) g[i].clear();
}
void push_up(int rt){
    sum[rt].v=sum[rt<<1].v+sum[rt<<1|1].v;
}
void build(int rt,int l,int r){
    if(l==r){
        sum[rt].v=w[rk[l]];
        return ;
    }
    int mid=(l+r)>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    push_up(rt);
}
void push_down(int rt,int l,int r){
    int m=(l+r)/2;
    if(sum[rt].add!=0){
        sum[rt<<1].v+=(m-l+1)*sum[rt].add;
        sum[rt<<1|1].v+=(r-m)*sum[rt].add;
        sum[rt<<1].add+=sum[rt].add;
        sum[rt<<1|1].add+=sum[rt].add;
        sum[rt].add=0;
    }
}
void update(int rt,int l,int r,int ll,int rr,LL d){
    if(ll<=l&&r<=rr){
        sum[rt].add+=d;
        sum[rt].v+=d*(r-l+1);
        return ;
    }
    push_down(rt,l,r);
    int mid=(l+r)>>1;
    if(ll<=mid) update(rt<<1,l,mid,ll,rr,d);
    if(rr>mid) update(rt<<1|1,mid+1,r,ll,rr,d);
    push_up(rt);
}

LL  query(int rt,int l,int r,int ll,int rr){
    if(ll<=l&&r<=rr){
        return sum[rt].v;
    }
    push_down(rt,l,r);
    int mid=(l+r)>>1;
    long long res=0;
    if(ll<=mid) res+=query(rt<<1,l,mid,ll,rr);
    if(rr>mid)    res+=query(rt<<1|1,mid+1,r,ll,rr);
    return res;
}

void TreeSplitDfs1(int u,int prev,int depth){
    fa[u]=prev;
    dep[u]=depth;
    sz[u]=1;
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];
        if(v==prev) continue;
        TreeSplitDfs1(v,u,depth+1);
        sz[u]+=sz[v];
        if(sz[v]>sz[son[u]]) son[u]=v;
    }
}
void TreeSplitDfs2(int u,int tp){
    top[u] = tp;
    id[u] = ++dfs_clock;
    rk[dfs_clock] = u;
    if (!son[u]) return;
    TreeSplitDfs2(son[u], tp);
    for(int i=0; i<g[u].size(); i++){
        int v=g[u][i];
        if( v==son[u] || v==fa[u]) continue;
        TreeSplitDfs2(v,v);
    }
}
LL TreeSplitQuery(int u,int v){
    LL ret=0;
//    cout<<"ii"<<u<<endl;
    while( top[u]!=top[v]){
//    cout<<"ss"<<endl;
        if(dep[ top[u] ]< dep[ top[v] ]) std::swap(u,v);
        ret+= query(1,1,n,id[ top[u] ],id[u]);
        u = fa[top[u]];
    }
    if(id[u] > id[v] ) std::swap(u,v);
    ret+=query(1,1,n,id[u],id[v]);
    return ret;
}
int main(){

    init();
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) scanf("%lld", &w[i]);
    for (int i = 1, u, v; i < n; ++i) {
    scanf("%d%d", &u, &v);
    g[u].push_back(v);
            g[v].push_back(u);
    }
    TreeSplitDfs1(1, 0, 1);
    TreeSplitDfs2(1, 1);
    build(1, 1, n);
    int opt,x,a;
    for(int i=1;i<=m;i++){
        scanf("%d",&opt);
        if(opt==1){
            scanf("%d%d",&x,&a);
            update(1,1,n,id[x],id[x],a);
        }
        if(opt==2){
            scanf("%d%d",&x,&a);
            update(1,1,n,id[x],id[x]+sz[x]-1,a);
        }
        if(opt==3){
            scanf("%d",&x);
            cout<<TreeSplitQuery(x,1)<<endl;
        }
    }
    return 0;
}

树链剖分

树链,即为通过dsf将树的一条长支作为一条链,有多少个叶节点就有多少条链,为了方便划分节点(重要的用处后面讲),我们将从一个节点出发到叶节点,能过使链最长的支成为这个节点的重链,且这个节点也属于这条链,他的其他分支都称为轻链。例如下图节点1的重链为1-3-7-10-13-16,其他都为他的轻链。以及要存储各节点的父节点,各条链的顶节点,节点的深度,节点的重子节点等等;

图片来源-LY学长

我们通过dfs可以得到和DFS序一样的in序列,一条下划线的代表一条链,但是即有DFS序,为何又要有树链剖分呢?DFS中我们说过,当操作的区间不是一整个子树的区间时,他的意义很迷。但是在剖序中,当我们要查询节点9到节点15的这条路径中节点的信息,因为树链变很容易。首先15比9更深,且不在同一条链上,我们用15开始操作:15是它所在链的顶节点,所以我们从15跳的他的父节点11,我们查11和9不在同一链,所以跳到11的顶节点3,在到3的父节点1,1和9在同一条链上,结束。好了,我们得到一些关键的数字,15,11,3,1,9,我们只需对[ id9 , id1 ] , [ id3 , id7 ]  ,[ id11,id11 ] ,[ di15 , id15] 。对于为什么是这样解决,我认为的是,还是刚才的例子,现在我们只看下面的树链序:15的父节点是上一条链的中的11节点,因为他们属于两条链,分别访问[ id15,id15 ]和[ id3, id11 ] ,为什么不直接[ id15, id11]呢?其实这条路径上的点我们都要访问,对不同链分开访问实际是防止重复(我是不是啰嗦了?‘~‘),这也就是为啥分轻重链,其实没啥关键的原因,就是为了给顶节点分配一条链,是链的划分更有序啦

一道题:

有一棵点数为 N 的树,以点 1 为根,且树点有边权。然后有 M 个

操作,分为三种:

操作 1 :把某个节点 x 的点权增加 a 。

操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a 。

操作 3 :询问某个节点 x 到根的路径中所有点的点权和。

Input

第一行包含两个整数 N, M 。表示点数和操作数。接下来一行 N 个整数,表示树中节点的初始权值。接下来 N-1

行每行三个正整数 fr, to , 表示该树中存在一条边 (fr, to) 。再接下来 M 行,每行分别表示一次操作。其中

第一个数表示该操作的种类( 1-3 ) ,之后接这个操作的参数( x 或者 x a ) 。

Output

对于每个询问操作,输出该询问的答案。答案之间用换行隔开。

Sample Input5 5 1 2 3 4 5 1 2 1 4 2 3 2 5 3 3 1 2 1 3 5 2 1 2 3 3

Sample Output6 9 13 Hint

对于 100% 的数据, N,M<=100000 ,且所有输入数据的绝对值都不会超过 10^6 。

对于1,2操作,dfs序都能完成,操作3涉及了两个节点所以要用树链剖分,这里线段树节点维护的信息是区间和。值得注意的是操作2,像我一样不要迷,区间更新在树剖也是首节点x到尾(节点x加上根x的子树的节点数,DFS序节点x的out也可以这样表示)构成了图中根节点x的子树(DFS序更新一样)。

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<iostream>//单点改值 最大值
#include<string.h>
#include<stdio.h>
#include<vector>
using namespace std;
typedef long long LL;
const long long inf=1e17;
const int maxn=1e5+5;
vector<int> g[maxn];
LL dis[maxn],w[maxn];
int dfs_clock;
int n,m;
struct Tree{
    LL add,v;
}sum[maxn<<2];
int fa[maxn];//父节点
int dep[maxn];//深度
int sz[maxn];//以o为根节点的子树节点数
int son[maxn];//重子节点
int rk[maxn];//rk[i]=u树链序为i的节点是U
int top[maxn];//节点O所在重链的顶部节点
int id[maxn];//节点o在树链序的位置
void init(){
    dfs_clock=0;
//    memset(dis,0,sizeof(dis));
    memset(w,0,sizeof(w));
    memset(fa,0,sizeof fa);
    memset(dep,0,sizeof dep);
    memset(sz,0,sizeof sz);
    memset(son,0,sizeof son);
    memset(rk,0,sizeof rk);
    memset(top,0,sizeof top);
    memset(id,0,sizeof id);
    memset(sum,0,sizeof sum);
    for(int i=0;i<maxn;i++){
        sum[i].add=sum[i].v=0;
    }
    for(int i=0;i<maxn;i++) g[i].clear();
}
void push_up(int rt){
    sum[rt].v=sum[rt<<1].v+sum[rt<<1|1].v;
}
void build(int rt,int l,int r){
    if(l==r){
        sum[rt].v=w[rk[l]];
        return ;
    }
    int mid=(l+r)>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    push_up(rt);
}
void push_down(int rt,int l,int r){
    int m=(l+r)/2;
    if(sum[rt].add!=0){
        sum[rt<<1].v+=(m-l+1)*sum[rt].add;
        sum[rt<<1|1].v+=(r-m)*sum[rt].add;
        sum[rt<<1].add+=sum[rt].add;
        sum[rt<<1|1].add+=sum[rt].add;
        sum[rt].add=0;
    }
}
void update(int rt,int l,int r,int ll,int rr,LL d){
    if(ll<=l&&r<=rr){
        sum[rt].add+=d;
        sum[rt].v+=d*(r-l+1);
        return ;
    }
    push_down(rt,l,r);
    int mid=(l+r)>>1;
    if(ll<=mid) update(rt<<1,l,mid,ll,rr,d);
    if(rr>mid) update(rt<<1|1,mid+1,r,ll,rr,d);
    push_up(rt);
}

LL  query(int rt,int l,int r,int ll,int rr){
    if(ll<=l&&r<=rr){
        return sum[rt].v;
    }
    push_down(rt,l,r);
    int mid=(l+r)>>1;
    long long res=0;
    if(ll<=mid) res+=query(rt<<1,l,mid,ll,rr);
    if(rr>mid)    res+=query(rt<<1|1,mid+1,r,ll,rr);
    return res;
}

void TreeSplitDfs1(int u,int prev,int depth){
    fa[u]=prev;
    dep[u]=depth;
    sz[u]=1;
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];
        if(v==prev) continue;
        TreeSplitDfs1(v,u,depth+1);
        sz[u]+=sz[v];
        if(sz[v]>sz[son[u]]) son[u]=v;
    }
}
void TreeSplitDfs2(int u,int tp){
    top[u] = tp;
    id[u] = ++dfs_clock;
    rk[dfs_clock] = u;
    if (!son[u]) return;
    TreeSplitDfs2(son[u], tp);
    for(int i=0; i<g[u].size(); i++){
        int v=g[u][i];
        if( v==son[u] || v==fa[u]) continue;
        TreeSplitDfs2(v,v);
    }
}
LL TreeSplitQuery(int u,int v){
    LL ret=0;
//    cout<<"ii"<<u<<endl;
    while( top[u]!=top[v]){
//    cout<<"ss"<<endl;
        if(dep[ top[u] ]< dep[ top[v] ]) std::swap(u,v);
        ret+= query(1,1,n,id[ top[u] ],id[u]);
        u = fa[top[u]];
    }
    if(id[u] > id[v] ) std::swap(u,v);
    ret+=query(1,1,n,id[u],id[v]);
    return ret;
}
int main(){

    init();
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) scanf("%lld", &w[i]);
    for (int i = 1, u, v; i < n; ++i) {
    scanf("%d%d", &u, &v);
    g[u].push_back(v);
            g[v].push_back(u);
    }
    TreeSplitDfs1(1, 0, 1);
    TreeSplitDfs2(1, 1);
    build(1, 1, n);
    int opt,x,a;
    for(int i=1;i<=m;i++){
        scanf("%d",&opt);
        if(opt==1){
            scanf("%d%d",&x,&a);
            update(1,1,n,id[x],id[x],a);
        }
        if(opt==2){
            scanf("%d%d",&x,&a);
            update(1,1,n,id[x],id[x]+sz[x]-1,a);
        }
        if(opt==3){
            scanf("%d",&x);
            cout<<TreeSplitQuery(x,1)<<endl;
        }
    }
    return 0;
}

后言:我们应该用DFS去确定区间,然后用线段树维护,序列的意义不是线段树决定的,线段树的区间维护的是图节点x的子树的或节点x和它的后代节点的信息。

欧拉序

对有树进行dfs,无论是递归正向

原文地址:https://www.cnblogs.com/jjl0229/p/11337403.html

时间: 2024-10-11 05:33:32

DFS序-树链剖序-欧拉序的相关文章

【BZOJ 3772】精神污染 主席树+欧拉序

这道题的内存-------真·精神污染---.. 这道题的思路很明了,我们就是要找每一个路径包含了多少其他路径那么就是找,有多少路径的左右端点都在这条路径上,对于每一条路径,我们随便选定一个端点作为第一关键字,另一个作为第二关键字,于是就有了两维限制,按照主席树的一般思路,我们把建树顺序作为一维,然后在里面维护另一维,那么我们在外面限制第一关键字,就是在树上建主席树,查询减LCA,在里面的话我们把每个点作为第一关键字对应的第二关键字,放入主席树,而主席树维护的是欧拉序区间,所以我们每次查询只用查

lca 欧拉序+rmq(st) 欧拉序+rmq(线段树) 离线dfs

https://www.luogu.org/problemnew/show/P3379 1.欧拉序+rmq(st) 1 /* 2 在这里,对于一个数,选择最左边的 3 选择任意一个都可以,[left_index,right_index],深度都大于等于这个数的深度 4 */ 5 #include <cstdio> 6 #include <cstdlib> 7 #include <cmath> 8 #include <cstring> 9 #include &

欧拉序动态维护树直径

https://zhuanlan.zhihu.com/p/84236967 https://www.cnblogs.com/TinyWong/p/11260601.html 一个月过去了,我还是没有学动态点分治... 欧拉序保存了每个节点进入和返回的情况,$n$ 个结点的树,欧拉序列长度为 $2n - 1$. 两个结点的LCA就是它们在欧拉序中出现的位置的区间中,深度最小的那个结点. 对于边权为正的树,上面定义中的深度也可以换成到根的距离,用dis[u]表示结点 $u$ 到根的距离. 那么两个节

Underground Lab CodeForces - 782E (欧拉序)

大意:$n$结点,$m$条边无向图, 有$k$个人, 每个人最多走$\left\lceil\frac {2n}{k}\right\rceil$步, 求一种方案使得$k$个人走遍所有的点 $n$结点树的欧拉序长度为$2n-1$, 直接取$dfs$树的欧拉序即可 #include <iostream> #include <algorithm> #include <math.h> #include <cstdio> #include <vector>

HDU 2586(LCA欧拉序和st表)

什么是欧拉序,可以去这个大佬的博客(https://www.cnblogs.com/stxy-ferryman/p/7741970.html)巨详细 因为欧拉序中的两点之间,就是两点遍历的过程,所以只要找遍历过程中对应的最小的深度就行了,这里用st表存,first存第一个u出现的地方,用value存欧拉序,同时用depth存对应深度 模板 1 struct node{ 2 int v,next,dist; 3 }a[maxn<<1]; 4 int n,m,tot,len; 5 int st[m

P3379 【模板】最近公共祖先(LCA)(欧拉序+rmq)

P3379 [模板]最近公共祖先(LCA) 用欧拉序$+rmq$维护的$lca$可以做到$O(nlogn)$预处理,$O(1)$查询 从这里剻个图 #include<iostream> #include<cstdio> #include<vector> using namespace std; int read(){ char c=getchar(); int x=0; while(c<'0'||c>'9') c=getchar(); while('0'&l

POJ-2513 Colored Sticks(字典树+并查集+欧拉)

题目链接:Colored Sticks 一道3个知识点结合的题目,可以说单个知识点的题目,都会做,一旦知识点结合起来,题目就不简单了 思路:这个题开始看就知道是并查集,但是不好处理的不同种单词的统计,所以理所应当联想到字典树,上次做字典树的题目啸爷出的是统计相同单词数,这个题目和那个一样,把flag加个编号即可,再利用并查集. 1750ms  水过 #include <iostream> #include <cstdio> #include <cstdlib> #inc

P2633|主席树+dfs序+树链剖分求lca+离散化

不知道为什么会RE.. 待补 思路:链上求u和v两点路径第k小利用lca就转变为了 U+V-LCA-FA(LCA) 上的第k小,这因为每个点的主席树的root是从其父转移来的.可以用树链剖分求lca:在dfs序上建立主席树将树上问题转变为区间问题,询问的时候用主席树求区间k小值. 终于能写出这种题了,开心! #include<bits/stdc++.h> using namespace std; const int maxn = 1e5+100; int n,m,e = 1,num,ans=0

CF1114F Please, another Queries on Array?(线段树,数论,欧拉函数,状态压缩)

这题我在考场上也是想出了正解的……但是没调出来. 题目链接:CF原网 题目大意:给一个长度为 $n$ 的序列 $a$,$q$ 个操作:区间乘 $x$,求区间乘积的欧拉函数模 $10^9+7$ 的值. $1\le n\le 4\times 10^5,1\le q\le 2\times 10^5,1\le a_i,x\le 300$.时限 5.5s,空限 256MB. 明显线段树. 有一个想法是维护区间积的欧拉函数,但是这样时间复杂度和代码复杂度都很高…… 我的做法是维护区间积.而欧拉函数,就是看看