UOJ Logo SHYI的博客

博客

[Scoi2010]序列操作 题解

2016-07-30 10:19:07 By SHYI

题目大意:有一个01序列,现在对于这个序列有五种变换操作和询问操作: 0 a b 把[a, b]区间内的所有数全变成0;1 a b 把[a, b]区间内的所有数全变成1;2 a b 把[a,b]区间内的所有数全部取反,也就是说把所有的0变成1,把所有的1变成0;3 a b 询问[a, b]区间内总共有多少个1;4 a b 询问[a, b]区间内最多有多少个连续的1。

思路:维护每一段数的和、左端和右端以及整段中连续的0和1的长度,并使用标记进行下传。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#define N 400000
using namespace std;

int root,num,cnt,lmax[2][N],rmax[2][N],mmax[2][N],sum[N],tag[2][N],rev[N],h[N],t[N],rc[N],lc[N],a[N];

void up_date(int x,int k)
{
     int l=lc[x],r=rc[x];
     lmax[k][x]=lmax[k][l],rmax[k][x]=rmax[k][r];
     if (lmax[k][l]==t[l]-h[l]+1) lmax[k][x]+=lmax[k][r];
     if (rmax[k][r]==t[r]-h[r]+1) rmax[k][x]+=rmax[k][l];
     mmax[k][x]=max(max(mmax[k][l],mmax[k][r]),rmax[k][l]+lmax[k][r]);
}

void build(int l,int r,int &cur)
{
     cur=++num,tag[0][cur]=tag[1][cur]=rev[cur]=0;
     h[cur]=l,t[cur]=r;
     if (l==r)
     {
         lc[cur]=rc[cur]=0;
         lmax[0][cur]=rmax[0][cur]=mmax[0][cur]=(a[l]==0);
         lmax[1][cur]=rmax[1][cur]=sum[cur]=mmax[1][cur]=(a[r]==1);
         return;
     }
     int mid=l+r>>1;
     build(l,mid,lc[cur]),build(mid+1,r,rc[cur]);
     up_date(cur,0),up_date(cur,1),sum[cur]=sum[lc[cur]]+sum[rc[cur]];
}

void mark(int x,int k)
{
     tag[k][x]=1,tag[k^1][x]=rev[x]=0,sum[x]=(t[x]-h[x]+1)*k;
     lmax[k][x]=rmax[k][x]=mmax[k][x]=t[x]-h[x]+1;
     lmax[k^1][x]=rmax[k^1][x]=mmax[k^1][x]=0;
}

void re(int x)
{
     rev[x]^=1,sum[x]=t[x]-h[x]+1-sum[x];
     swap(lmax[0][x],lmax[1][x]),swap(rmax[0][x],rmax[1][x]),swap(mmax[0][x],mmax[1][x]);
}

void push_down(int x)
{
     if (tag[0][x]) mark(lc[x],0),mark(rc[x],0),tag[0][x]=0;
     if (tag[1][x]) mark(lc[x],1),mark(rc[x],1),tag[1][x]=0;
     if (rev[x]) re(lc[x]),re(rc[x]),rev[x]=0;
}

void change(int l,int r,int cur,int k)
{
     if (h[cur]>r || t[cur]<l) return;
     if (h[cur]>=l && t[cur]<=r)
     {
         if (k<2) mark(cur,k);
         else re(cur);
         return;
     }
     push_down(cur);
     change(l,r,lc[cur],k),change(l,r,rc[cur],k);
     up_date(cur,0),up_date(cur,1),sum[cur]=sum[lc[cur]]+sum[rc[cur]];
}

int ask1(int l,int r,int cur)
{
     if (h[cur]>r || t[cur]<l) return 0;
     if (h[cur]>=l && t[cur]<=r) return sum[cur];
     push_down(cur);
     return ask1(l,r,lc[cur])+ask1(l,r,rc[cur]);
}

int ask2(int l,int r,int cur)
{
    if (l==h[cur] && r==t[cur]) return cur;
    int mid=h[cur]+t[cur]>>1;
    push_down(cur);
    if (r<=mid) return ask2(l,r,lc[cur]);
    else if (l>mid) return ask2(l,r,rc[cur]);
         else
         {
             int ans=++cnt;
             lc[ans]=ask2(l,mid,lc[cur]),rc[ans]=ask2(mid+1,r,rc[cur]);
             up_date(ans,0),up_date(ans,1),sum[ans]=sum[lc[ans]]+sum[rc[ans]];
             return ans;
         }
}

int main()
{
    int n,m,i,x,y,z;
    scanf("%d%d",&n,&m);
    for (i=1;i<=n;i++) scanf("%d",&a[i]);
    build(1,n,root);
    for (i=1;i<=m;i++)
    {
        scanf("%d%d%d",&z,&x,&y);
        x++,y++;
        if (z==3) printf("%d\n",ask1(x,y,root));
        else if (z==4) cnt=num,printf("%d\n",mmax[1][ask2(x,y,root)]);
             else change(x,y,root,z);
    }
    return 0;
}

[JLOI2014]松鼠的新家

2016-07-21 20:47:29 By SHYI

题目大意:给你一棵树,要从编号为a[1]的节点走到编号为a[2]的节点再走到编号为a[3]的节点……一直走到编号为a[n]的节点。问每个节点最少访问多少次。

思路:将其进行轻重链剖分,则从a[i]走到a[i+1]实际上就是在几段重链的节点上+1,于是就用线段树来维护一下即可。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define M 1200000
using namespace std;

int ans[M],to[M],head[M],next[M],vis[M],size[M],deep[M],id[M],top[M],sum[M],fa[M],a[M],cnt,dfn,n;

void add(int x,int y)
{
    to[++cnt]=y;
    next[cnt]=head[x];
    head[x]=cnt;
}

void dfs1(int x)
{
    size[x]=vis[x]=1;
    for (int i=head[x];i;i=next[i])
        if (!vis[to[i]])
        {
            deep[to[i]]=deep[x]+1;
            fa[to[i]]=x;
            dfs1(to[i]);
            size[x]+=size[to[i]];
        }
}

void dfs2(int x,int chain)
{
    int k=0,i;
    id[x]=++dfn;
    top[x]=chain;
    for (i=head[x];i;i=next[i])
        if (deep[to[i]]>deep[x] && size[to[i]]>size[k]) k=to[i];
    if (!k) return;
    dfs2(k,chain);
    for (i=head[x];i;i=next[i])
        if (deep[to[i]]>deep[x] && to[i]!=k) dfs2(to[i],to[i]);
}

void push_down(int cur)
{
    sum[cur<<1]+=sum[cur];
    sum[cur<<1|1]+=sum[cur];
    sum[cur]=0;
}

void ADD(int L,int R,int l,int r,int cur)
{
    if (l<=L && r>=R)
    {
        sum[cur]++;
        return;
    }
    push_down(cur);
    int mid=L+R>>1;
    if (l>mid) ADD(mid+1,R,l,r,cur<<1|1);
    else if (r<=mid) ADD(L,mid,l,r,cur<<1);
         else ADD(L,mid,l,mid,cur<<1),ADD(mid+1,R,mid+1,r,cur<<1|1);
}

int ask(int l,int r,int x,int cur)
{
    if (l==r) return sum[cur];
    push_down(cur);
    int mid=l+r>>1;
    if (x>mid) return sum[cur]+ask(mid+1,r,x,cur<<1|1);
    else return sum[cur]+ask(l,mid,x,cur<<1);
}

void Add(int x,int y)
{
    for (;top[x]!=top[y];x=fa[top[x]])
    {
        if (deep[top[x]]<deep[top[y]]) swap(x,y);
        ADD(1,n,id[top[x]],id[x],1);
    }
    if (deep[x]<deep[y]) swap(x,y);
    ADD(1,n,id[y],id[x],1);
}

int main()
{
    int i,x,y;
    scanf("%d",&n);
    for (i=1;i<=n;i++) scanf("%d",&a[i]);
    for (i=1;i<n;i++) scanf("%d%d",&x,&y),add(x,y),add(y,x);
    dfs1(1);
    dfs2(1,1);
    for (i=1;i<n;i++) Add(a[i],a[i+1]);
    for (i=1;i<=n;i++)
    {
        ans[i]=-(i!=a[1]);
        ans[i]+=ask(1,n,id[i],1);
    }
    for (i=1;i<=n;i++) printf("%d\n",ans[i]);
    return 0;
}

[ZJOI2008]树的统计Count 题解

2016-07-21 20:37:02 By SHYI

题目大意:一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w。有一些操作:1.把结点u的权值改为t;2.询问从点u到点v的路径上的节点的最大权值 3.询问从点u到点v的路径上的节点的权值和。

思路:进行轻重树链剖分,再根据每个节点的dfs序建立线段树,维护其最大值以及和,询问时用树剖后的结果将重链作为区间一段一段求和。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#define M 1000009
using namespace std;

int n,dfn,cnt,to[M],next[M],head[M],size[M],vis[M],deep[M],fa[M],top[M],w[M],mx[M],sum[M],id[M];

void add(int x,int y)
{
    to[++cnt]=y,next[cnt]=head[x],head[x]=cnt;
}

void dfs1(int x)
{
    size[x]=vis[x]=1;
    for (int i=head[x];i;i=next[i])
        if (!vis[to[i]])
        {
            deep[to[i]]=deep[x]+1;
            fa[to[i]]=x;
            dfs1(to[i]);
            size[x]+=size[to[i]];
        }
}

void dfs2(int x,int chain)
{
    int k=0,i;
    id[x]=++dfn;
    top[x]=chain;
    for (i=head[x];i;i=next[i])
        if (deep[to[i]]>deep[x] && size[to[i]]>size[k]) k=to[i];
    if (!k) return;
    dfs2(k,chain);
    for (i=head[x];i;i=next[i])
        if (deep[to[i]]>deep[x] && to[i]!=k) dfs2(to[i],to[i]);
}

int LCA(int x,int y)
{
    for (;top[x]!=top[y];x=fa[top[x]])
        if (deep[top[x]]<deep[top[y]]) swap(x,y);
    return deep[x]<deep[y]?x:y;
}

void change(int l,int r,int x,int y,int cur)
{
    if (l==r)
    {
        mx[cur]=sum[cur]=y;
        return;
    }
    int mid=l+r>>1;
    if (x<=mid) change(l,mid,x,y,cur<<1);
    else change(mid+1,r,x,y,cur<<1|1);
    mx[cur]=max(mx[cur<<1],mx[cur<<1|1]);
    sum[cur]=sum[cur<<1]+sum[cur<<1|1];
}

int SUM(int L,int R,int l,int r,int cur)
{
    if (l<=L && r>=R) return sum[cur];
    int mid=L+R>>1;
    if (l>mid) return SUM(mid+1,R,l,r,cur<<1|1);
    else if (r<=mid) return SUM(L,mid,l,r,cur<<1);
         else return SUM(L,mid,l,r,cur<<1)+SUM(mid+1,R,mid+1,r,cur<<1|1);
}

int MAX(int L,int R,int l,int r,int cur)
{
    if (l<=L && r>=R) return mx[cur];
    int mid=L+R>>1;
    if (l>mid) return MAX(mid+1,R,l,r,cur<<1|1);
    else if (r<=mid) return MAX(L,mid,l,r,cur<<1);
         else return max(MAX(L,mid,l,mid,cur<<1),MAX(mid+1,R,mid+1,r,cur<<1|1));
}

int Sum(int x,int y)
{
    int ans=0;
    for (;top[x]!=top[y];x=fa[top[x]])
    {
        if (deep[top[x]]<deep[top[y]]) swap(x,y);
        ans+=SUM(1,n,id[top[x]],id[x],1);
    }
    if (deep[x]>deep[y]) swap(x,y);
    return ans+SUM(1,n,id[x],id[y],1);
}

int Max(int x,int y)
{
    int ans=-999999999;
    for (;top[x]!=top[y];x=fa[top[x]])
    {
        if (deep[top[x]]<deep[top[y]]) swap(x,y);
        ans=max(ans,MAX(1,n,id[top[x]],id[x],1));
    }
    if (deep[x]>deep[y]) swap(x,y);
    return max(ans,MAX(1,n,id[x],id[y],1));
}

int main()
{
    int m,i,x,y;
    scanf("%d",&n);
    for (i=1;i<n;i++) scanf("%d%d",&x,&y),add(x,y),add(y,x);
    dfs1(1);
    dfs2(1,1);
    for (i=1;i<=n;i++) scanf("%d",&w[i]),change(1,n,id[i],w[i],1);
    scanf("%d",&m);
    for (i=1;i<=m;i++)
    {
        char ch[9];
        scanf("%s%d%d",ch,&x,&y);
        if (ch[0]=='C') w[x]=y,change(1,n,id[x],y,1);
        else
            if (ch[1]=='S') printf("%d\n",Sum(x,y));
            else printf("%d\n",Max(x,y));
    }
    return 0;
}

[Ahoi2009]Seq 维护序列seq 题解

2016-07-21 20:22:56 By SHYI

题目大意:有长为N的数列,有如下三种操作形式: (1)把数列中的一段数全部乘一个值; (2)把数列中的一段数全部加一个值; (3)询问数列中的一段数的和,由于答案可能很大,你只需输出这个数模P的值。

思路:用线段树来维护当前的值和要加以及乘的值,由于加与乘是有序的,所以要在做子树之前将标记下传,加和乘分开来、合起来处理都可以。

代码:(当初手抽将1打成l一直RE调了半天才发现)

#include<cstdio>
#include<cstring>
#include<iostream>
#define MAX 400000
#define LL long long
using namespace std;

LL sum[MAX],mul[MAX],add[MAX],mod;

void up_date(int cur)
{
    sum[cur]=(sum[cur<<1]+sum[cur<<1|1])%mod;
}

void creat(int L,int R,int x,int y,int cur)
{
    mul[cur]=1;
    add[cur]=0;
    sum[cur]+=y;
    if (L==R) return;
    int mid=L+R>>1;
    if (x>mid) creat(mid+1,R,x,y,cur<<1|1);
    else creat(L,mid,x,y,cur<<1);
    up_date(cur);
}

void push_down(int cur,int l,int r,int mid)
{
    if (mul[cur]==1 && add[cur]==0) return;
    mul[cur<<1]=mul[cur<<1]*mul[cur]%mod;
    add[cur<<1]=(add[cur<<1]*mul[cur]%mod+add[cur])%mod;
    sum[cur<<1]=(sum[cur<<1]*mul[cur]%mod+add[cur]*(LL)(mid-l+1)%mod)%mod;
    mul[cur<<1|1]=mul[cur<<1|1]*mul[cur]%mod;
    add[cur<<1|1]=(add[cur<<1|1]*mul[cur]%mod+add[cur])%mod;
    sum[cur<<1|1]=(sum[cur<<1|1]*mul[cur]%mod+add[cur]*(LL)(r-mid)%mod)%mod;
    mul[cur]=1;
    add[cur]=0;
    return;
}

void change_mul(int L,int R,int l,int r,int x,int cur)
{
    if (L>=l && R<=r)
    {
        mul[cur]=mul[cur]*(LL)x%mod;
        add[cur]=add[cur]*(LL)x%mod;
        sum[cur]=sum[cur]*(LL)x%mod;
        return;
    }
    int mid=L+R>>1;
    push_down(cur,L,R,mid);
    if (l<=mid) change_mul(L,mid,l,r,x,cur<<1);
    if (r>mid) change_mul(mid+1,R,l,r,x,cur<<1|1);
    up_date(cur);
}

void change_add(int L,int R,int l,int r,int x,int cur)
{
    if (L>=l && R<=r)
    {
        add[cur]=(add[cur]+(LL)x)%mod;
        sum[cur]=(sum[cur]+(LL)(R-L+1)*x%mod)%mod;
        return;
    }
    int mid=L+R>>1;
    push_down(cur,L,R,mid);
    if (l<=mid) change_add(L,mid,l,r,x,cur<<1);
    if (r>mid) change_add(mid+1,R,l,r,x,cur<<1|1);
    up_date(cur);
}

LL ask(int L,int R,int l,int r,int cur)
{
    if (L>=l && R<=r) return sum[cur];
    int mid=L+R>>1;
    LL ans=0;
    push_down(cur,L,R,mid);
    if (l<=mid) ans=(ans+ask(L,mid,l,r,cur<<1))%mod;
    if (r>mid) ans=(ans+ask(mid+1,R,l,r,cur<<1|1))%mod;
    up_date(cur);
    return ans;
}

int main()
{
    int n,m,a,b,c,i,x;
    scanf("%d%lld",&n,&mod);
    for (i=1;i<=n;i++) scanf("%d",&a),creat(1,n,i,a%mod,1);
    scanf("%d",&m);
    for (i=1;i<=m;i++)
    {
        scanf("%d",&x);
        if (x==1) scanf("%d%d%d",&a,&b,&c),change_mul(1,n,a,b,c%mod,1);
        if (x==2) scanf("%d%d%d",&a,&b,&c),change_add(1,n,a,b,c%mod,1);
        if (x==3) scanf("%d%d",&a,&b),printf("%lld\n",ask(1,n,a,b,1));
    }
    return 0;
}

Pku3468 A Simple Problem with Integers 题解

2016-07-21 20:09:18 By SHYI

题目大意:一个数列,有两个操作:1.修改操作,将一段区间内的数加上c;2.查询操作,查询一段区间内的数的和。

思路:线段树裸题,区间修改、区间查询,维护和以及加上的数,由于无序,不需要向下推标记,只需在子树更新完之后更新根节点即可。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;

long long sum[400001],mor[400001];

void up(int cur,int t)
{
    sum[cur]=sum[cur<<1]+sum[cur<<1|1]+t*mor[cur];
}

void add(int L,int R,int l,int r,int x,int cur)
{
    if (L>=l && R<=r)
    {
        mor[cur]+=x;
        sum[cur]+=(R-L+1)*x;
        return;
    }
    int mid=L+R>>1;
    if (l<=mid) add(L,mid,l,r,x,cur<<1);
    if (r>mid) add(mid+1,R,l,r,x,cur<<1|1);
    up(cur,R-L+1);
}

long long ask(int L,int R,int l,int r,int cur)
{
    if (L>=l && R<=r) return sum[cur];
    int mid=L+R>>1;
    long long ans=mor[cur]*(r-l+1);
    if (l<=mid) ans+=ask(L,mid,l,min(mid,r),cur<<1);
    if (r>mid) ans+=ask(mid+1,R,max(l,mid+1),r,cur<<1|1);
    return ans;
}

int main()
{
    int n,m,i,a,b,c;
    scanf("%d%d",&n,&m);
    for (i=1;i<=n;i++) scanf("%d",&a),add(1,n,i,i,a,1);
    for (i=1;i<=m;i++)
    {
        char ch=getchar();
        while (ch<'A' || ch>'Z') ch=getchar();
        if (ch=='Q') scanf("%d%d",&a,&b),printf("%lld\n",ask(1,n,a,b,1));
        else scanf("%d%d%d",&a,&b,&c),add(1,n,a,b,c,1);
    }
    return 0;
}

新博客 [JSOI2008]最大数maxnumber 题解

2016-07-21 19:19:51 By SHYI

题目大意:维护一个数列,有两种操作:1、 查询当前数列中末尾L个数中的最大的数,并输出这个数的值。限制:L不超过当前数列的长度。2、插入操作:将n加上t,其中t是最近一次查询操作的答案(如果还未执行过查询操作,则t=0),并将所得结果对一个固定的常数D取模,再将所得答插入到数列的末尾。初始时数列是空的,没有一个数。

思路:讲道理这道题用线段树肯定不是最好的(比如用栈),但是我现在在学线段树,就拿来练练手。线段树裸题,有单点加入、区间查询操作。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;

int da[800001];

void add(int l,int r,int x,int y,int cur)
{
    da[cur]=max(da[cur],y);
    if (l==r) return;
    int mid=l+r>>1;
    if (x>mid) add(mid+1,r,x,y,cur<<1|1);
    else add(l,mid,x,y,cur<<1);
}

int ask(int L,int R,int l,int r,int cur)
{
    if (L>=l && R<=r) return da[cur];
    int mid=L+R>>1,ans=0;
    if (l<=mid) ans=max(ans,ask(L,mid,l,r,cur<<1));
    if (r>mid) ans=max(ans,ask(mid+1,R,l,r,cur<<1|1));
    return ans;
}

int main()
{
    int m,mod,ans=0,n=0;
    scanf("%d%d",&m,&mod);
    for (int i=1;i<=m;i++)
    {
        int l;
        char ch=getchar();
        while (ch<'A' || ch>'Z') ch=getchar();
        scanf("%d",&l);
        if (ch=='A')
        {
            n++;
            int x=(l+ans)%mod;
            add(1,m,n,x,1);
        }
        else printf("%d\n",ans=ask(1,m,n-l+1,n,1));
    }
    return 0;
}
共 16 篇博客