【浮*光】#省选复习# DAY-2 の 综合练习题

(1)倍增

 

#include<cmath>
#include<cstdio>
#include<cstring>
#include<cassert>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<deque>
using namespace std;
typedef long long ll;

int f[100019][40],a,x,LC,n,m,p,len,l,r;

int main(){ scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a),f[i][0]=a;
    LC=(int)(log(n)/log(2)); //log2(n)
    for(int j=1;j<=LC;j++) for(int i=1;i<=n-(1<<j)+1;i++)
        f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
    for(int i=1;i<=m;i++){ scanf("%d%d",&l,&r);
        p=(int)(log(r-l+1)/log(2)); //log2(区间长度)
        printf("%d\n",max(f[l][p],f[r-(1<<p)+1][p]));
    } //保证查询的两个区间有重叠部分,且将[l,r]全部包含
}
【p3865】ST表

 

for(int i=1;i<=n;i++) scanf("%d",&a),f[i][0]=a; //初始化f[i][0]
LC=(int)(log(n)/log(2)); //计算log2(n)
for(int j=1;j<=LC;j++) for(int i=1;i<=n-(1<<j)+1;i++)
   f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]); //建立ST表
for(int i=1;i<=m;i++){ scanf("%d%d",&l,&r); //区间max
   p=(int)(log(r-l+1)/log(2)); //log2(区间长度)
   printf("%d\n",max(f[l][p],f[r-(1<<p)+1][p]));
    } //保证查询的两个区间有重叠部分,且将[l,r]全部包含
}

 

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;

const int maxn=100019;

long long n,m,s[maxn],sum,f[maxn][20],ans=0x3f3f3f3f;

int main(){ 
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++) scanf("%lld%lld",&s[i],&f[i][0]);
    long long t=log2(n)+1; //log2(n)
    for(int j=1;j<t;j++) for(int i=1;i<=n-(1<<j)+1;i++)
        f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
    long long l=1,r=1; //尺取法的两个指针
    while(l<=n){ //维护段长sum
        while(r<=n&&sum<m) sum+=s[r++];
        if(sum<m) break; long long k=log2(r-l);
        ans=min(ans,max(f[l][k],f[r-(1<<k)+1][k]));
        sum-=s[l++]; //搜索下一个状态,队头--
    } printf("%lld\n",ans); return 0;
}
【p4085】Haybale Feast

 

(2)期望DP

 

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

/*【p4316】绿豆蛙的归宿 */

//【标签】概率dp + 反向连边 + 拓扑排序

/*【分析】设f[x]表示点x到终点n的期望路径总长(还有f[i]步走到终点)。
要求的答案为f[1],且有f[n]=0。对于边x->y,f[x]=(1/du[x])*∑(f[y]+w[x->y])。
对于图上问题,反向建图,根据拓扑序进行DP。按DP方程转移即可。*/

void reads(int &x){
    int fx=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+(s-'0');s=getchar();}
    x=x*fx;//正负号
}

const int N=100019;

int n,m,head[N],tot=0,du[N],rd[N]; double f[N];

struct EDGE{ int nextt,ver,w; }e[N*2];

void add(int x,int y,int z)
 { e[++tot].ver=y,e[tot].nextt=head[x],e[tot].w=z,head[x]=tot; }

void topo(){
    queue<int> q; q.push(n); //已知是DAG
    while(!q.empty()){ int x=q.front(); q.pop();
        for(int i=head[x];i;i=e[i].nextt){
            f[e[i].ver]+=1.0*(1.0/rd[e[i].ver])*(f[x]+e[i].w);
            // f[x]=(1/du[x])*∑(f[y]+w[x->y]); 注意精度
            du[e[i].ver]--; if(du[e[i].ver]==0) q.push(e[i].ver);
        }
    }
}

int main(){
    reads(n),reads(m); for(int i=1,x,y,z;i<=m;i++)
        reads(x),reads(y),reads(z),add(y,x,z),du[x]++; //反向边
    for(int i=1;i<=n;i++) rd[i]=du[i]; //保存一遍
    f[n]=0; topo(); printf("%.2lf\n",f[1]);
}
【p4316】绿豆蛙的归宿

期望DP+反向连图拓扑排序,f[x]即点x到终点n的期望路径总长

即:还有f[i]步走到终点。方程:对于边x->y,f[x]=(1/du[x])*∑(f[y]+w[x->y])。

 

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

/*【p1850】换教室*/

void reads(int &x){
    int fx=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+(s-'0');s=getchar();}
    x=x*fx;//正负号
}

const int MAXN=2e3+5;
const double inf=1e17+5;

int n,m,v,e,c[MAXN][2],g[305][305];

double k[MAXN],f[MAXN][MAXN][2],ans;

inline int read(){
    int fx=1,x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+(s-'0');s=getchar();}
    return x=x*fx;//正负号
}

int main(){
    memset(g,63,sizeof(g));

    n=read();m=read();v=read();e=read();

    for(int i=1;i<=n;i++)c[i][0]=read();
    for(int i=1;i<=n;i++)c[i][1]=read();
    for(int i=1;i<=n;i++)scanf("%lf",&k[i]);

    for(int i=1;i<=e;i++){
        int x=read(),y=read(),w=read();
        g[x][y]=g[y][x]=min(g[x][y],w);
    }

    for(int k=1;k<=v;k++)
        for(int i=1;i<=v;i++)
            for(int j=1;j<=v;j++)
                g[i][j]=min(g[i][j],g[i][k]+g[k][j]);

    for(int i=1;i<=v;i++) g[i][i]=g[i][0]=g[0][i]=0;

    for(int i=0;i<=n;i++)
        for(int j=0;j<=m;j++) f[i][j][0]=f[i][j][1]=inf;
        
    f[1][0][0]=f[1][1][1]=0;
    for(int i=2;i<=n;i++){
        f[i][0][0]=f[i-1][0][0]+g[c[i-1][0]][c[i][0]];
        for(int j=1;j<=min(i,m);j++){
            int C1=c[i-1][0],C2=c[i-1][1],C3=c[i][0],C4=c[i][1];
            f[i][j][0]=min(f[i][j][0],min(f[i-1][j][0]+g[C1][C3],
                f[i-1][j][1]+g[C1][C3]*(1-k[i-1])+g[C2][C3]*k[i-1]));
            f[i][j][1]=min(f[i][j][1],min(f[i-1][j-1][0]+g[C1][C3]*(1-k[i])+g[C1][C4]*k[i],
                f[i-1][j-1][1]+g[C2][C4]*k[i]*k[i-1]+g[C2][C3]*k[i-1]*(1-k[i])
                 +g[C1][C4]*(1-k[i-1])*k[i]+g[C1][C3]*(1-k[i-1])*(1-k[i])));
        }
    } ans=inf;
    for(int i=0;i<=m;i++) ans=min(ans,min(f[n][i][0],f[n][i][1]));
    printf("%.2lf",ans); return 0;
}
【p1850】换教室

 

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

#define R register

/*【p4819】杀人游戏
有一个杀手,每个平民知道一些人的身份:杀手或平民。
每个人成为杀手的可能性相同。求能确定杀手的概率。*/

//Tarjan缩点,缩点之后只需查询环中的一个人、就可以知道整个环的信息。
//=>入度为0的点的个数 就是 查不出来的杀手的人数。

//注意:如果知道了N-1个人的身份,那么剩下的那个人一定是杀手(排除法)。
//=>寻找一个大小为1且入度为0的点(缩点后,‘与世隔绝’的杀手),
//并且他指向的点入度不为1,说明可以从别的点反推排除,则标记flag=1。

//若flag==1,ans=1-(缩点后入度为0的点-1)/n;
//若flag==0,ans=1-(缩点后入度为0的点)/n.

void reads(int &x){ //读入优化(正负整数)
    ll f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f; //正负号
}

const int N=500019,M=500019;

int n,m,dfn[N],low[N],stack[N],vis[N];
 
int dfn_=0,top_=0,sum=0,col[N],siz[N];

int u[M],v[M],head[N],tot=0,rd[N],flag=0,ans=0;

struct node{ int ver,nextt; }e[M];

inline void add(R int x,R int y){ 
    e[++tot].nextt=head[x],e[tot].ver=y,head[x]=tot; 
}

inline void tarjan(int x){
    dfn[x]=low[x]=++dfn_,stack[++top_]=x,vis[x]=1;
    for(R int i=head[x];i;i=e[i].nextt){
        if(!dfn[e[i].ver]) tarjan(e[i].ver),low[x]=min(low[x],low[e[i].ver]);
        else if(vis[e[i].ver]) low[x]=min(low[x],dfn[e[i].ver]);
    } if(dfn[x]==low[x]){
        col[x]=++sum; siz[sum]++; vis[x]=0;
        while(stack[top_]!=x){ //x上方的节点是可以保留的
            col[stack[top_]]=sum,siz[sum]++;
            vis[stack[top_]]=0,top_--;
        } top_--; //col数组记录每个点所在连通块的编号
    }
}

int main(){
    reads(n),reads(m);
    for(R int i=1;i<=m;i++)
        reads(u[i]),reads(v[i]),add(u[i],v[i]);
    for(R int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
    memset(head,0,sizeof(head)); tot=0; //缩点之后重新建图
    for(R int i=1;i<=m;i++) //重新连边、计算入度
        if(col[u[i]]!=col[v[i]]) rd[col[v[i]]]++,
            add(col[u[i]],col[v[i]]); //按连通块相连
    for(R int i=1;i<=sum;i++){ //缩点之后、点的个数
        if(!flag&&!rd[i]&&siz[i]==1){ //在没有找到满足FLAG的点的时候
            int okk=0; //记录是否每个出度的rd都>1
            for(R int j=head[i];j;j=e[j].nextt)
                if(rd[e[j].ver]==1) okk=-1;
            if(okk==0) flag=1;
        } if(!rd[i]) ans++; //记录rd=0的点的个数
    } if(flag==1) ans--; //用排除法减少询问
    printf("%.6lf\n",1.0-(double)ans/(double)n);
}
【p4819】杀人游戏 // tarjan + 概率计算

 

(3)树链剖分

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

//【p2590】树的统计

void reads(int &x){ //读入优化(正负整数)
    int fa=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
    x*=fa; //正负号
}

const int N=60019,M=200019;

int n,m,a[N],sumn,maxn,tot=0,head[N];

int siz[N],son[N],top[N],dep[N],fa[N];

int seg[N],rev[M],sum[M],Max[M];

struct node{ int nextt,ver,w; }e[N];

void add(int x,int y)
 { e[++tot].ver=y,e[tot].nextt=head[x],head[x]=tot; }

//--------线段树部分----------\\

void build(int rt,int l,int r){ int mid=(l+r)>>1;
    if(l==r){ Max[rt]=sum[rt]=a[rev[l]]; return; } //叶子节点
    build(rt<<1,l,mid),build((rt<<1)+1,mid+1,r); //左右子树
    sum[rt]=sum[rt<<1]+sum[(rt<<1)+1];//更新相关值 
    Max[rt]=max(Max[rt<<1],Max[(rt<<1)+1]); 
}

void change(int rt,int l,int r,int v,int x){ //单点修改
    if((x>r)||(x<l)) return; //x超出范围
    if((l==r)&&(r==x)){ //到达叶子节点x,开始修改
        sum[rt]=v,Max[rt]=v; return; 
    } int mid=(l+r)>>1;
    if(mid>=x) change(rt<<1,l,mid,v,x); //左儿子
    if(mid+1<=x) change((rt<<1)+1,mid+1,r,v,x); //右儿子 
    sum[rt]=sum[rt<<1]+sum[(rt<<1)+1];//更新相关的值 
    Max[rt]=max(Max[rt<<1],Max[(rt<<1)+1]);
}

void get(int rt,int l,int r,int x,int y){
//区间询问,rt是节点标号,l、r是当前区间,x、y是询问区间 
    if((x>r)||(y<l)) return; //与询问区间无交集 
    if((x<=l)&&(r<=y)) //询问区间包含于当前区间 
     { sumn+=sum[rt],maxn=max(maxn,Max[rt]); return; }
    int mid=(l+r)>>1;
    if(mid>=x) get(rt<<1,l,mid,x,y); //左儿子 
    if(mid+1<=y) get((rt<<1)+1,mid+1,r,x,y); //右儿子 
}

//--------树链剖分部分----------\\

void dfs1(int u,int fa_){ //第一遍dfs:求子树大小和重儿子
    siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
    for(int i=head[u];i;i=e[i].nextt){
        if(e[i].ver==fa_) continue;
        dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver]; //计算size
        if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
    }
}

void dfs2(int u,int fa_){ //第二遍dfs:确定dfs序和top值
    if(son[u]){ //先走重儿子,使重链在线段树中的位置连续
        seg[son[u]]=++seg[0]; //节点记入线段树中
        rev[seg[0]]=son[u]; //记录对应的原始编号
        top[son[u]]=top[u],dfs2(son[u],u); //更新top值
    } for(int i=head[u];i;i=e[i].nextt){
        if(top[e[i].ver]) continue; //除去u的重儿子或父亲
        seg[e[i].ver]=++seg[0],rev[seg[0]]=e[i].ver; //加入线段树
        top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点
    }
}

void query(int x,int y){ //路径询问
    int fx=top[x],fy=top[y];
    while(fx!=fy){ //↓↓选择深度较大的
        if(dep[fx]<dep[fy]) swap(x,y),swap(fx,fy); 
        get(1,1,seg[0],seg[fx],seg[x]);
        x=fa[fx],fx=top[x]; //往上跳、并更新此点的top值
    } if(dep[x]>dep[y]) swap(x,y); //x、y已在同一条重链上 
    get(1,1,seg[0],seg[x],seg[y]); 
}

//--------主程序部分----------\\

int main(){ 
    int u,v; reads(n);
    for(int i=1;i<n;i++)
        reads(u),reads(v),add(u,v),add(v,u);
    for(int i=1;i<=n;i++) reads(a[i]);
    dfs1(1,0),seg[0]=seg[1]=top[1]=rev[1]=1; //设1为根结点
    dfs2(1,0),build(1,1,seg[0]); //建立线段树
    reads(m); char ss[10];
    for(int i=1;i<=m;i++){
        scanf("%s",ss+1); reads(u),reads(v);
        if(ss[1]=='C') change(1,1,seg[0],v,seg[u]); //单点修改
        else{ sumn=0,maxn=-10000000,query(u,v); //询问
            if(ss[2]=='M') printf("%d\n",maxn);
            else printf("%d\n",sumn);
        }
    }
}
【p2590】树的统计 //单点修改 + 路径max + 路径sum

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

/*【p3178】树上操作 
有一棵点数为 N 的树,以点1为根,且有边权。M 个操作:
1:把某个节点 x 的点权增加 a(单点修改)
2:把某个节点 x 为根的子树中所有点的点权都增加 a(区间修改)
3:询问某个节点 x 到根的路径中所有点的点权和(区间查询) */

void reads(ll &x){ //读入优化(正负整数)
    ll fa=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
    x*=fa; //正负号
}

const ll N=1000019;

ll n,m,a[N*2],tot=0,head[N*2];

ll siz[N*2],son[N*2],top[N*2],dep[N*2],fa[N*2];

ll rev[N*2],seg[N*4],sum[N*4],lazy[N*4];

struct node{ ll nextt,ver,w; }e[N*2];

void add(ll x,ll y)
 { e[++tot].ver=y,e[tot].nextt=head[x],head[x]=tot; }

//--------线段树部分----------//

void build(ll rt,ll l,ll r){ ll mid=(l+r)>>1;
    if(l==r){ sum[rt]=a[rev[l]]; return; } //叶子节点
    build(rt<<1,l,mid),build(rt<<1|1,mid+1,r); //左右子树
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}

void PushDown(ll rt,ll l,ll r){ //标记下移
    if(!lazy[rt]) return; ll mid=(l+r)>>1;
    sum[rt<<1]+=lazy[rt]*(mid-l+1);
    sum[rt<<1|1]+=lazy[rt]*(r-mid);
    lazy[rt<<1]+=lazy[rt],lazy[rt<<1|1]+=lazy[rt];
    lazy[rt]=0; //此点标记清零
}

void change(ll rt,ll l,ll r,ll v,ll x,ll y){ //区间修改
    if((x>r)||(y<l)) return; //不相交区间
    if((x<=l)&&(r<=y)) //此区间完全被询问区间包含
     { sum[rt]+=v*(r-l+1),lazy[rt]+=v; return; }
    ll mid=(l+r)>>1; PushDown(rt,l,r);
    change(rt<<1,l,mid,v,x,y),change(rt<<1|1,mid+1,r,v,x,y);
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}

ll get(ll rt,ll l,ll r,ll x,ll y){
//区间询问,rt是节点标号,l、r是当前区间,x、y是询问区间 
    if((x>r)||(y<l)) return 0; //与询问区间无交集 
    if((x<=l)&&(r<=y)) return sum[rt];
    ll mid=(l+r)>>1; PushDown(rt,l,r);
    return get(rt<<1,l,mid,x,y)+get(rt<<1|1,mid+1,r,x,y);
}

//--------树链剖分部分----------//

void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子
    siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
    for(ll i=head[u];i;i=e[i].nextt){
        if(e[i].ver==fa_) continue;
        dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver]; //计算size
        if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
    }
}

void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值
    if(son[u]){ //先走重儿子,使重链在线段树中的位置连续
        seg[son[u]]=++seg[0]; //节点记入线段树中
        rev[seg[0]]=son[u]; //记录对应的原始编号
        top[son[u]]=top[u],dfs2(son[u],u); //更新top值
    } for(ll i=head[u];i;i=e[i].nextt){
        if(top[e[i].ver]) continue; //除去u的重儿子或父亲
        seg[e[i].ver]=++seg[0],rev[seg[0]]=e[i].ver; //加入线段树
        top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点
    }
}

ll query(ll x,ll y){ //路径询问
    ll fx=top[x],fy=top[y],ans=0;
    while(fx!=fy){ //↓↓选择深度较大的
        if(dep[fx]<dep[fy]) swap(x,y),swap(fx,fy); 
        ans=ans+get(1,1,seg[0],seg[fx],seg[x]);
        x=fa[fx],fx=top[x]; //往上跳、并更新此点的top值
    } if(dep[x]>dep[y]) swap(x,y); //x、y已在同一条重链上 
    ans=ans+get(1,1,seg[0],seg[x],seg[y]); return ans;
}

//--------主程序部分----------//

int main(){
    ll u,v,op; reads(n),reads(m);
    for(ll i=1;i<=n;i++) reads(a[i]);
    for(ll i=1;i<n;i++) 
        reads(u),reads(v),add(u,v),add(v,u);
    dfs1(1,0),seg[0]=seg[1]=top[1]=rev[1]=1; //设1为根结点
    dfs2(1,0),build(1,1,seg[0]); //建立线段树
    for(ll i=1;i<=m;i++){ reads(op),reads(u);
        if(op==1) reads(v),change(1,1,seg[0],v,seg[u],seg[u]);
        if(op==2) reads(v),change(1,1,n,v,seg[u],seg[u]+siz[u]-1);
        if(op==3) printf("%lld\n",query(1,u));
    }
}
【p3178】树上操作 //单点修改 + 子树修改 + 路径sum

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

/*【p3833】魔法树 //路径修改 + 子树sum */

void reads(ll &x){ //读入优化(正负整数)
    ll fa=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
    x*=fa; //正负号
}

const ll N=1000019;

ll n,m,a[N*2],tot=0,head[N*2];

ll siz[N*2],son[N*2],top[N*2],dep[N*2],fa[N*2];

ll rev[N*2],id[N*4],sum[N*4],lazy[N*4];

struct node{ ll nextt,ver,w; }e[N*2];

void add(ll x,ll y)
 { e[++tot].ver=y,e[tot].nextt=head[x],head[x]=tot; }

//--------线段树部分----------//

void build(ll rt,ll l,ll r){ ll mid=(l+r)>>1;
    if(l==r){ sum[rt]=a[rev[l]]; return; } //叶子节点
    build(rt<<1,l,mid),build(rt<<1|1,mid+1,r); //左右子树
    sum[rt]=(sum[rt<<1]+sum[rt<<1|1]);
}

void PushDown(ll rt,ll l,ll r){ //标记下移
    if(!lazy[rt]) return; ll mid=(l+r)>>1;
    sum[rt<<1]+=lazy[rt]*(mid-l+1);
    sum[rt<<1|1]+=lazy[rt]*(r-mid);
    lazy[rt<<1]+=lazy[rt], //↓↓此点标记清零
    lazy[rt<<1|1]+=lazy[rt]; lazy[rt]=0;
}

void update(ll rt,ll l,ll r,ll x,ll y,ll v){ //区间修改
    if((x>r)||(y<l)) return; //不相交区间
    if((x<=l)&&(r<=y)) //此区间完全被询问区间包含
     { sum[rt]+=v*(r-l+1),lazy[rt]+=v; return; }
    ll mid=(l+r)>>1; PushDown(rt,l,r);
    update(rt<<1,l,mid,x,y,v),update(rt<<1|1,mid+1,r,x,y,v);
    sum[rt]=(sum[rt<<1]+sum[rt<<1|1]);
}

ll query(ll rt,ll l,ll r,ll x,ll y){
//区间询问,rt是节点标号,l、r是当前区间,x、y是询问区间 
    if((x>r)||(y<l)) return 0; //与询问区间无交集 
    if((x<=l)&&(r<=y)) return sum[rt];
    ll mid=(l+r)>>1; PushDown(rt,l,r);
    return (query(rt<<1,l,mid,x,y)+query(rt<<1|1,mid+1,r,x,y));
}

//--------树链剖分部分----------//

void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子
    siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
    for(ll i=head[u];i;i=e[i].nextt){
        if(e[i].ver==fa_) continue;
        dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver]; //计算size
        if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
    }
}

void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值
    if(son[u]){ //先走重儿子,使重链在线段树中的位置连续
        id[son[u]]=++id[0]; //节点记入线段树中
        rev[id[0]]=son[u]; //记录对应的原始编号
        top[son[u]]=top[u],dfs2(son[u],u); //更新top值
    } for(ll i=head[u];i;i=e[i].nextt){
        if(top[e[i].ver]) continue; //除去u的重儿子或父亲
        id[e[i].ver]=++id[0],rev[id[0]]=e[i].ver; //加入线段树
        top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点
    }
}

ll q_route(ll x,ll y){ //【路径查询】
    ll ans=0; while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        ans+=query(1,1,n,id[top[x]],id[x]); x=fa[top[x]];
    } if(dep[x]>dep[y]) swap(x,y); 
    ans+=query(1,1,n,id[x],id[y]); return ans;
}

void upd_route(ll x,ll y,ll k){ //(1)
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        update(1,1,n,id[top[x]],id[x],k); x=fa[top[x]];
    } if(dep[x]>dep[y]) swap(x,y); update(1,1,n,id[x],id[y],k);
}

//【子树查询】子树区间右端点为id[x]+siz[x]-1,直接求值/修改即可

ll q_son(ll x){ return query(1,1,n,id[x],id[x]+siz[x]-1); } //(2)

void upd_son(ll x,ll k){ update(1,1,n,id[x],id[x]+siz[x]-1,k); }


//--------主程序部分----------//

int main(){
    reads(n); for(ll i=1,u,v;i<n;i++) 
        reads(u),reads(v),u++,v++,add(u,v),add(v,u);
    dfs1(1,0),top[1]=rev[1]=id[1]=id[0]=1;
    dfs2(1,0),build(1,1,n); reads(m); char op[19];
    for(ll i=1,u,v,w;i<=m;i++){ cin>>op; reads(u),u++;
        if(op[0]=='A') reads(v),v++,reads(w),upd_route(u,v,w);
        if(op[0]=='Q') printf("%lld\n",q_son(u));
    }
}
【p3833】 魔法树 //路径修改 + 子树sum

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

/*【p4315】月下毛景树 //边权:单点修改 + 路径修改 + 路径max */

//【处理‘边权’的树链剖分问题】因为一个点最多只有一个父亲结点,
// 那么,可以考虑把[此点--父亲结点の边权]放置到[此点的点权]。

//【注意】在路径询问函数中,每条路径上の父亲节点的点权不应该考虑在内。

void reads(ll &x){ //读入优化(正负整数)
    ll fa=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
    x*=fa; //正负号
}

const ll N=1000019; struct node{ ll nextt,ver,w; }e[N*2];

ll n,m,a[N*2],tot=0,head[N*2],siz[N*2],son[N*2],top[N*2],dep[N*2],fa[N*2];

ll rev[N*2],id[N*4],maxx[N*4],lazy[N*4],tag[N*4]; //修改标记,替换标记

void add(ll x,ll y,ll z)
 { e[++tot].ver=y,e[tot].nextt=head[x],e[tot].w=z,head[x]=tot; }

//--------线段树部分----------//

void build(ll rt,ll l,ll r){ 
    tag[rt]=-1; ll mid=(l+r)>>1;
    if(l==r){ maxx[rt]=a[l]; return; } //叶子节点
    build(rt<<1,l,mid),build(rt<<1|1,mid+1,r); //左右子树
    maxx[rt]=max(maxx[rt<<1],maxx[rt<<1|1]);
}

void PushDown(ll rt,ll l,ll r){ //标记下移
    ll ls=rt<<1,rs=rt<<1|1;
    if(tag[rt]>=0) lazy[ls]=lazy[rs]=0, //区间替换标记
        maxx[ls]=maxx[rs]=tag[ls]=tag[rs]=tag[rt],tag[rt]=-1;
    if(lazy[rt]) lazy[ls]+=lazy[rt],lazy[rs]+=lazy[rt],
        maxx[ls]+=lazy[rt],maxx[rs]+=lazy[rt],lazy[rt]=0;
}

void update1(ll rt,ll l,ll r,ll x,ll y,ll v){ //区间替换
    if((x>r)||(y<l)) return; //不相交区间
    if((x<=l)&&(r<=y)) //此区间完全被询问区间包含
     { maxx[rt]=tag[rt]=v,lazy[rt]=0; return; }
    ll mid=(l+r)>>1; PushDown(rt,l,r);
    update1(rt<<1,l,mid,x,y,v),update1(rt<<1|1,mid+1,r,x,y,v);
    maxx[rt]=max(maxx[rt<<1],maxx[rt<<1|1]);
}

void update2(ll rt,ll l,ll r,ll x,ll y,ll v){ //区间修改
    if((x>r)||(y<l)) return; //不相交区间
    if((x<=l)&&(r<=y)) //此区间完全被询问区间包含
     { maxx[rt]+=v,lazy[rt]+=v; return; }
    ll mid=(l+r)>>1; PushDown(rt,l,r);
    update2(rt<<1,l,mid,x,y,v),update2(rt<<1|1,mid+1,r,x,y,v);
    maxx[rt]=max(maxx[rt<<1],maxx[rt<<1|1]);
}

ll query(ll rt,ll l,ll r,ll x,ll y){
//区间询问,rt是节点标号,l、r是当前区间,x、y是询问区间 
    if((x>r)||(y<l)) return 0; //与询问区间无交集 
    if((x<=l)&&(r<=y)) return maxx[rt];
    ll mid=(l+r)>>1; PushDown(rt,l,r);
    return max(query(rt<<1,l,mid,x,y),query(rt<<1|1,mid+1,r,x,y));
}

//--------树链剖分部分----------//

void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子
    siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
    for(ll i=head[u];i;i=e[i].nextt){
        if(e[i].ver==fa_) continue; rev[e[i].ver]=e[i].w;
        dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver];
        if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
    }
}

void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值
    if(son[u]){ //先走重儿子,使重链在线段树中的位置连续
        id[son[u]]=++id[0]; //节点记入线段树中
        a[id[0]]=rev[son[u]]; //记录对应的原始编号
        top[son[u]]=top[u],dfs2(son[u],u); //更新top值
    } for(ll i=head[u];i;i=e[i].nextt){
        if(top[e[i].ver]) continue; //除去u的重儿子或父亲
        id[e[i].ver]=++id[0],a[id[0]]=rev[e[i].ver]; //加入线段树
        top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点
    }
}

//--------修改&查询操作部分----------//

ll q_route(ll x,ll y){ //【路径查询】//(4)
    ll ans=0; while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        ans=max(ans,query(1,1,n,id[top[x]],id[x])); x=fa[top[x]];
    } if(dep[x]>dep[y]) swap(x,y); 
    ans=max(ans,query(1,1,n,id[x]+1,id[y])); return ans;
}

void upd_route1(ll x,ll y,ll k){ //(2)
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        update1(1,1,n,id[top[x]],id[x],k); x=fa[top[x]];
    } if(dep[x]>dep[y]) swap(x,y); update1(1,1,n,id[x]+1,id[y],k);
}

void upd_route2(ll x,ll y,ll k){ //(3)
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        update2(1,1,n,id[top[x]],id[x],k); x=fa[top[x]];
    } if(dep[x]>dep[y]) swap(x,y); update2(1,1,n,id[x]+1,id[y],k);
}

//【子树查询】子树区间右端点为id[x]+siz[x]-1,直接求值/修改即可

ll q_son(ll x){ return query(1,1,n,id[x],id[x]+siz[x]-1); }

void upd_son(ll x,ll k){ update1(1,1,n,id[x],id[x]+siz[x]-1,k); }


//--------主程序部分----------//

int main(){
    reads(n); for(ll i=1,u,v,w;i<n;i++) 
        reads(u),reads(v),reads(w),add(u,v,w),add(v,u,w);
    dfs1(1,0),top[1]=rev[1]=id[1]=id[0]=1;
    dfs2(1,0),build(1,1,n); char op[19];
    while(1){ cin>>op; if(op[0]=='S') break;
        ll u,v,w; reads(u),reads(v);
        if(op[1]=='h') //↓↓判断(按输入顺序的)第k条树枝的父亲是谁
            u=dep[e[u*2-1].ver]<dep[e[u*2].ver]?e[u*2].ver:e[u*2-1].ver,
            update1(1,1,n,id[u],id[u],v); //单点修改
        if(op[1]=='o') reads(w),upd_route1(u,v,w);
        if(op[1]=='d') reads(w),upd_route2(u,v,w);
        if(op[1]=='a') printf("%lld\n",q_route(u,v));
    }
}
【p4315】月下毛景树 //边权:单点修改 + 路径修改 + 路径max

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

/*【p4116】Qtree3 //单点修改(黑白) + 求1~v上第一个黑点 */

void reads(ll &x){ //读入优化(正负整数)
    ll fa=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
    x*=fa; //正负号
}

const ll N=1000019;

ll n,m,a[N*2],tot=0,head[N*2],rev[N*2],id[N*4];

ll siz[N*2],son[N*2],top[N*2],dep[N*2],fa[N*2];

struct node{ ll nextt,ver,w; }e[N*2];

void add(ll x,ll y)
 { e[++tot].ver=y,e[tot].nextt=head[x],head[x]=tot; }

//--------线段树部分----------//

#define lc rt<<1
#define rc rt<<1|1

struct segment{ int v;bool f; segment(){v=-1;} }t[N*4];

inline void pushup(int rt){
    t[rt].f=t[lc].f|t[rc].f; //子区间中有黑点,当前区间有黑点
    t[rt].v=t[lc].f?t[lc].v:(t[rc].f?t[rc].v:-1); 
    //↑↑优先取左子区间的黑点,使距离根节点尽可能近
}

void update(int rt,int l,int r,int p){
    if(l==r){ t[rt].f^=1; t[rt].v=t[rt].f?rev[l]:-1; return;}
    int m=l+r>>1; if(p<=m) update(lc,l,m,p);
    else update(rc,m+1,r,p); pushup(rt);
}
int query(int rt,int l,int r,int L,int R){
    if(l>R||r<L) return -1; if(L<=l&&r<=R) return t[rt].v;
    int m=l+r>>1,l1=query(lc,l,m,L,R),r1=query(rc,m+1,r,L,R);
    return l1==-1?r1:l1; //优先取左子区间
}

//--------树链剖分部分----------//

void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子
    siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
    for(ll i=head[u];i;i=e[i].nextt){
        if(e[i].ver==fa_) continue;
        dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver]; //计算size
        if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
    }
}

void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值
    if(son[u]){ //先走重儿子,使重链在线段树中的位置连续
        id[son[u]]=++id[0]; //节点记入线段树中
        rev[id[0]]=son[u]; //记录对应的原始编号
        top[son[u]]=top[u],dfs2(son[u],u); //更新top值
    } for(ll i=head[u];i;i=e[i].nextt){
        if(top[e[i].ver]) continue; //除去u的重儿子或父亲
        id[e[i].ver]=++id[0],rev[id[0]]=e[i].ver; //加入线段树
        top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点
    }
}

void solve(ll x){
    ll ans=-1,rt; 
    while(top[x]!=1){
        rt=query(1,1,n,id[top[x]],id[x]);
        ans=(rt==-1?ans:rt); x=fa[top[x]];
    } rt=query(1,1,n,1,id[x]),cout<<(rt==-1?ans:rt)<<endl;
}

//--------主程序部分----------//

int main(){ 
    ll x,y; reads(n),reads(m); //初始全为白点
    for(ll i=1;i<n;i++) reads(x),reads(y),add(x,y),add(y,x);
    dfs1(1,0),id[0]=id[1]=top[1]=rev[1]=1,dfs2(1,0); 
    for(ll i=1,op,x;i<=m;i++){ cin>>op; reads(x);
        if(op==0) update(1,1,n,id[x]); //单点换色
        if(op==1) solve(x); //求1~v上第一个黑点
    }
}
【p4116】Qtree3 //单点修改(黑白) + 求1~v上第一个黑点

 

// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

/*【p4092】树 //单点标记 + 询问离v最近的一个有标记的祖先 */

//线段树维护每一段区间中被标记的最深的节点。

//查询时,在链上往上跳,只要找到了有标记的节点就输出。

void reads(ll &x){ //读入优化(正负整数)
    ll fa=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
    x*=fa; //正负号
}

const ll N=1000019;

ll n,m,a[N*2],tot=0,head[N*2],rev[N*2],id[N*4];

ll siz[N*2],son[N*2],top[N*2],dep[N*2],fa[N*2];

struct node{ ll nextt,ver,w; }e[N*2];

void add(ll rt,ll y)
 { e[++tot].ver=y,e[tot].nextt=head[rt],head[rt]=tot; }

//--------线段树部分----------//

struct Tree{ int left,right,deepest; }tree[800019];

void build(ll rt,ll l,ll r)
 {  tree[rt].left=l; tree[rt].right=r;tree[rt].deepest=-1; 
    if(r-l>1) build(rt*2,l,(l+r)/2),build(rt*2+1,(l+r)/2,r); }

void update(int x,int l,int r){
    if(l<=tree[x].left&&r>=tree[x].right)
     { tree[x].deepest=l; return; } //只有一个元素
    int mid=(tree[x].left+tree[x].right)/2;
    if(l<mid) update(x*2,l,r); if(r>mid) update(x*2+1,l,r);
    tree[x].deepest=max(tree[x*2].deepest,tree[x*2+1].deepest); 
}

int query(int x,int l,int r){
    if(l<=tree[x].left&&r>=tree[x].right) return tree[x].deepest;
    int mid=(tree[x].left+tree[x].right)/2,ans=-1;
    if(l<mid) ans=max(ans,query(x*2,l,r));
    if(r>mid) ans=max(ans,query(x*2+1,l,r)); return ans;
}

//--------树链剖分部分----------//

void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子
    siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
    for(ll i=head[u];i;i=e[i].nextt){
        if(e[i].ver==fa_) continue;
        dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver]; //计算size
        if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
    }
}

void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值
    if(son[u]){ //先走重儿子,使重链在线段树中的位置连续
        id[son[u]]=++id[0]; //节点记入线段树中
        rev[id[0]]=son[u]; //记录对应的原始编号
        top[son[u]]=top[u],dfs2(son[u],u); //更新top值
    } for(ll i=head[u];i;i=e[i].nextt){
        if(top[e[i].ver]) continue; //除去u的重儿子或父亲
        id[e[i].ver]=++id[0],rev[id[0]]=e[i].ver; //加入线段树
        top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点
    }
}

ll q_ans(ll u,ll v){
    ll ans=-1; while(top[u]!=top[v]){
        if(dep[id[u]]<dep[id[v]]) swap(u,v);
        ans=query(1,id[top[u]],id[u]+1);
        if(ans!=-1) return rev[ans]; u=fa[top[u]];
    } if(dep[u]>dep[v]) swap(u,v);
    ans=query(1,id[u],id[v]+1); return rev[ans];
}

//--------主程序部分----------//

int main(){ 
    ll x,y; char op[19]; reads(n),reads(m); //初始全未标记
    for(ll i=1;i<n;i++) reads(x),reads(y),add(x,y),add(y,x);
    dfs1(1,0),id[0]=id[1]=top[1]=rev[1]=1,dfs2(1,0);
    build(1,1,n+1),update(1,1,2); //预先给根节点打标记 
    for(ll i=1;i<=m;i++){ cin>>op; reads(x);
        if(op[0]=='C') update(1,id[x],id[x]+1);
        if(op[0]=='Q') printf("%lld\n",q_ans(x,1));
    }
}
【p4092】树 //单点标记 + 询问离v最近的一个有标记的祖先

线段树维护每一段区间中被标记的最深的节点。

查询时,在链上往上跳,只要找到了有标记的节点就输出。

 

(4)区间DP

 

https://www.cnblogs.com/FloraLOVERyuuji/p/9558267.html

 

#include <bits/stdc++.h>
using namespace std;

const int N=1005,inf=1e9;

int a[N],sum[N],d[N][N],f[N][N],g[N][N]; //用f、g数组来优化DP数组d

int read(){ //读入优化
    int x=0,f=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x*=10;x+=(ch-'0');ch=getchar();}
    return x*f;
}

void print(int x){ //输出优化
    if(x<0) putchar('-'),x=-x;
    if(x>9) print(x/10); 
    putchar(x%10+'0');
}

int main(){
    int T,n; T=read();
    while(T--) {
        n=read(); for(int i=1;i<=n;i++)
            a[i]=read(),sum[i]=sum[i-1]+a[i]; //前缀和sum数组
        for(int i=1;i<=n;i++) f[i][i]=g[i][i]=d[i][i]=a[i]; //初始化边界
        for(int L=1;L<=n;L++){ //枚举长度
            for(int i=1;i<=n-L;i++){ //区间向后滚动
                int j=i+L,cnt=0; //递推部分
                cnt=min(cnt,min(f[i+1][j],g[i][j-1]));
                d[i][j]=sum[j]-sum[i-1]-cnt; 
                //↑↑↑ d[i][j]:目前剩下区间为i、j时,先手可能达到的max得分
                f[i][j]=min(d[i][j],f[i+1][j]);
                g[i][j]=min(d[i][j],g[i][j-1]);
            }
        } print(d[1][n]); putchar('\n');
    }
}
【p1430】序列取数

 

(5)点分治

 

https://www.cnblogs.com/FloraLOVERyuuji/p/10149612.html

 

 

❤ 享受最后一天的晚自习美好时光 ❤

 

 

 

                                  ——时间划过风的轨迹,那个少年,还在等你

 

posted @ 2019-04-04 19:39  花神&缘浅flora  阅读(193)  评论(0编辑  收藏  举报