18-2-11 刷题心得

本来今天要学fft的,结果还是没看下去。。。看来达到我智商的瓶颈了QAQ
题目cqoi2011 动态逆序对
比较裸的树状数组套线段树,然而还是卡了我好久。
因为这题很友好所以不用离散化;
以位置为主席树的时间轴,权值为主席树上的区间建树;
那么每次加一个数的时候的贡献就是之前出现的数中值比这个数大的个数;
对于修改,套一个树状数组,将修改的位置对之后的影响用树状数组全部消掉;
对于每次修改产生的影响,需要查询小于这个位置中所有大于这个数的和,由于树状数组只能保证前缀和的性质,对于区间查询其实可以想普通线段树那样进行查询,但是不怎么会传递数组,就查询了两个端点的前缀和再相减;
而查询大于这个数的位置且小于这个数的和的时候外边也要有一层差分,即所有位置小于这个数的和减去这个位置之前小于这个数的和;
我也不知道怎么着就过了这个题,还是多打点注释吧orz

#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdio>
#include<cctype>
#define LL long long
#define random(a,b) (a+rand()%(b-a+1))
const int maxn=100005;
int n,m;
int a[maxn];
int too[maxn];
int s[maxn];
int root[maxn];
int ntot=0;
int lc[maxn];
int lowbit(int x)
{
    return x&(-x);
}
struct asd
{
    int l,r,sum;
}t[maxn*100];
int build(int l,int r,int y,int pos,int v)
{
    ntot++; 
    int tmp=ntot;
    t[ntot]=t[y],t[ntot].sum+=v;
    while(l!=r)
    {
        int mid=(l+r)>>1;
        if(pos<=mid)r=mid,y=t[y].l,t[ntot].l=ntot+1;
        else l=mid+1,y=t[y].r,t[ntot].r=ntot+1;
        ntot++;
        t[ntot]=t[y],t[ntot].sum+=v;
    }
    return tmp;
}
int tp;
LL ans=0;
void del(int pos,int v)
{
    for(int i=pos;i<=n;i+=lowbit(i))
        s[i]=build(1,n,s[i],v,-1); //树状数组上主席树的修改,将其所能造成影响的时间轴的主席树全部进行修改
}
int query(int l,int r,int x,int pos)
{
    if(pos<1)return 0;//特判,之前调试的时候加上的好像没什么卵用?
    for(int i=x;i;i-=lowbit(i))too[i]=s[i];//将所用到的树状数组上的修改状态记下来;
    int nrt=root[x];
    int tans=0;
    while(l!=r)
    {
        int mid=(l+r)>>1;
        if(pos<=mid)
        {
            for(int i=x;i;i-=lowbit(i))too[i]=t[too[i]].l;
            nrt=t[nrt].l;
            r=mid;
        }
        else
        {
            for(int i=x;i;i-=lowbit(i))tans+=t[t[too[i]].l].sum;//因为是求的前缀和,要把左区间所有的值加起来
            tans+=t[t[nrt].l].sum;
            for(int i=x;i;i-=lowbit(i))too[i]=t[too[i]].r;
            nrt=t[nrt].r;
            l=mid+1;
        }
    }
    for(int i=x;i;i-=lowbit(i))tans+=t[too[i]].sum;//这里加的值是当前区间被修改的量
    tans+=t[nrt].sum;
    return tans;
}
int findv(int x,int nl,int nr)
{
    return query(1,n,x,nr)-query(1,n,x,nl-1);//左右端点的值相减
}
int read()
{
    char c=getchar();
    int tmp=0;
    while(!isdigit(c))c=getchar();
    while(isdigit(c))
    {
        tmp=tmp*10+c-'0';
        c=getchar();
    }
    return tmp;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        lc[a[i]]=i;
    }
    for(int i=1;i<=n;i++)
    {
        ans+=(LL)findv(i-1,a[i]+1,n); 
        root[i]=build(1,n,root[i-1],a[i],1);
    }
    printf("%lld\n",ans);
    for(int i=1;i<m;i++)
    {
        tp=read();
        if(tp<n)
            ans-=(LL)findv(lc[tp]-1,tp+1,n);//查询小于这个数的位置中值大于这个数的数目和
        if(tp>1)
            ans-=(LL)findv(n,1,tp-1)-(LL)findv(lc[tp]-1,1,tp-1); //所求区间为总区间的数目减去不符合的区间的数目
        del(lc[tp],tp);
        printf("%lld\n",ans);   
    }
    return 0;   
}

题目:bzoj 2653 middle
clj出的题果然就是神,整体构思非常巧妙,所以我身为蒟蒻又是看的题解
对于每个数,大于等于这个数为1,小于则为-1
先将所有值排序,按值的大小为主席树的时间轴建树,每次把这个位置改成-1;
如果一个数能够成为中位数,那么给定区间中肯定有一个区间和为0或1;
而在以这个数为时间点,所有小于等于这个数的位置全被改成-1,所以应以当前时间前一个时间点的主席树开始求值;
题目要求的是最大中位数,所以如果以mid为值求的最大和大于等于0的话,说明真正的值大于等于mid,这样就可以二分了;
这里的mid是以数值排完序后数组下标
而最大和需要求中间覆盖区间的区间和以及左边的最大右区间和右边的最大左区间,在线段树上都可以很好的维护;

#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdio>
#define LL long long
#define random(a,b) (a+rand()%(b-a+1))
const int maxn=20005;
int n,m;
using std::max;
struct asd
{
    int v,lc; 
    bool operator <(const asd & p)
    const {
    return v<p.v;
    }
}a[maxn];
struct qwe
{
    int l,r,sum,lmax,rmax;
}t[maxn*40]; 
int ntot=0;
int root[maxn];
void updata(int x)
{
    int lson=t[x].l;
    int rson=t[x].r;
    t[x].sum=t[lson].sum+t[rson].sum;
    t[x].lmax=max(t[lson].lmax,t[lson].sum+t[rson].lmax);
    t[x].rmax=max(t[rson].rmax,t[rson].sum+t[lson].rmax); 
} 
void build(int l,int r,int &x)
{
    x=++ntot;
    if(l==r)
    {
        t[x].sum=t[x].lmax=t[x].rmax=1;
        return;
    }
    int mid=(l+r)>>1;
    build(l,mid,t[x].l);
    build(mid+1,r,t[x].r);
    updata(x);
}
void insert(int l,int r,int &x,int y,int pos,int v)
{
    t[++ntot]=t[y],x=ntot;
    if(l==r)
    {
        t[x].lmax=t[x].rmax=t[x].sum=v;
        return;     
    } 
    int mid=(l+r)>>1;
    if(pos<=mid)insert(l,mid,t[x].l,t[y].l,pos,v);
    else insert(mid+1,r,t[x].r,t[y].r,pos,v);
    updata(x); 
}
int querysum(int l,int r,int x,int nl,int nr)
{
    if(nl<=l&&nr>=r)return t[x].sum;
    int mid=(l+r)>>1;
    int tans=0;
    if(nl<=mid)tans+=querysum(l,mid,t[x].l,nl,nr);
    if(nr>mid)tans+=querysum(mid+1,r,t[x].r,nl,nr); 
    return tans;
}
int querylmax(int l,int r,int x,int nl,int nr)
{
    if(nl<=l&&nr>=r)return t[x].lmax;
    int mid=(l+r)>>1;
    if(nl<=mid&&nr>mid)
        return max(querysum(l,mid,t[x].l,nl,nr)+querylmax(mid+1,r,t[x].r,nl,nr),querylmax(l,mid,t[x].l,nl,nr)); 
    else if(nl<=mid)
        return querylmax(l,mid,t[x].l,nl,nr);
    else return querylmax(mid+1,r,t[x].r,nl,nr);
}
int queryrmax(int l,int r,int x,int nl,int nr)
{
    if(nl<=l&&nr>=r)return t[x].rmax;
    int mid=(l+r)>>1;
    if(nl<=mid&&nr>mid)
        return max(queryrmax(l,mid,t[x].l,nl,nr)+querysum(mid+1,r,t[x].r,nl,nr),queryrmax(mid+1,r,t[x].r,nl,nr));   
    else if(nl<=mid)
        return queryrmax(l,mid,t[x].l,nl,nr);
    else return queryrmax(mid+1,r,t[x].r,nl,nr);
}
int findans(int x,int u,int v,int y)
{
    int l=1,r=n;
    int tans=0;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        int sumv=querysum(1,n,root[mid-1],u,v);//求中间的区间和
        if(u>x)
        sumv+=max(queryrmax(1,n,root[mid-1],x,u-1),0);//这里需要和0进行比较,因为上边的区间和是带端点的
        if(v<y)
        sumv+=max(querylmax(1,n,root[mid-1],v+1,y),0);
        if(sumv>=0)l=mid+1,tans=mid;
        else r=mid-1;
    }
    return a[tans].v;
}
int tp[4],q;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i].v);
        a[i].lc=i;
    }
    build(1,n,root[0]);
    std::sort(a+1,a+n+1);
    for(int i=1;i<=n;i++)
        insert(1,n,root[i],root[i-1],a[i].lc,-1);//将这个数的位置上的值标为-1;
    scanf("%d",&q);
    int la=0; 
    for(int i=1;i<=q;i++)
    {
        for(int j=0;j<4;j++)
        {
            scanf("%d",&tp[j]);
            tp[j]=(tp[j]+la)%n;
            tp[j]++;
        }
        std::sort(tp,tp+4);
        printf("%d\n",la=findans(tp[0],tp[1],tp[2],tp[3])); 
    }
    return 0;        
} 

题目:scoi2014 旅行
一道比较好想的树链剖分加线段树做法,而且只需要单点修改和区间求和以及求最值;
给每一种颜色建一颗线段树,一开始并没有任何节点,进行动态开点;
将所有节点其所对应颜色的线段树上的对应位置加上这个点的权值,每一次修改时空都是logn
对于修改操作,如果改颜色,将当前颜色线段树上的值设为0,新颜色线段树上的值加上这个节点的权值
如果改权值,直接改对应颜色线段树上的值,不要忘了数组中的值也要改;
剩下的就是树剖的常见操作了
我比较闲的是,线段树不想写递归版的,于是记了线段树上每个点的父亲,修改完之后再倒着updata一下,中间还手残了调了很久。。。

#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdio>
#define LL long long
#define random(a,b) (a+rand()%(b-a+1))
int n,q;
using std::max;
using std::swap;
const int maxn=100005;
int tp1,tp2;
int node[maxn];
int siz[maxn],d[maxn],start[maxn],tp[maxn],fa[maxn],son[maxn];
int a[maxn],b[maxn],c[maxn];
int rt[maxn];
struct qwe
{
    int l,r,sum,mv,fa,lv,rv;
    qwe()
    {
        fa=0;
    }
}t[maxn*40];
struct asd
{
    int next,to;
}edge[maxn*2];
int etot=0;
void add(int x,int y)
{
    edge[++etot].next=node[x];
    node[x]=etot;   
    edge[etot].to=y;
}
int dfs(int x)
{
    siz[x]=1;
    son[x]=0;
    d[x]=d[fa[x]]+1;
    for(int i=node[x];i;i=edge[i].next)
    if(edge[i].to!=fa[x])
    {
        fa[edge[i].to]=x;
        siz[x]+=dfs(edge[i].to);
        if(siz[son[x]]<siz[edge[i].to])son[x]=edge[i].to;
    }   
    return siz[x];
}
int tot=0; 
void dfs1(int x,int top)
{
    tp[x]=top;
    start[x]=++tot;
    if(son[x])dfs1(son[x],top);
    for(int i=node[x];i;i=edge[i].next)
        if(edge[i].to!=fa[x]&&edge[i].to!=son[x])dfs1(edge[i].to,edge[i].to);
}
int ntot=0;
void modify(int x,int pos,int v)
{
    int l=1,r=n;
    while(l!=r)
    {
        t[x].lv=l,t[x].rv=r;
        int mid=(l+r)>>1;
        if(pos<=mid) {
            if(t[x].l)x=t[x].l;
            else t[x].l=++ntot,t[ntot].fa=x,x=ntot;
            r=mid;
        }   else {
            if(t[x].r)x=t[x].r;
            else t[x].r=++ntot,t[ntot].fa=x,x=ntot;
            l=mid+1;
            }
    }
    t[x].lv=t[x].rv=l;
    t[x].sum=t[x].mv=v;
    while(x=t[x].fa)
    {
        t[x].sum=t[t[x].l].sum+t[t[x].r].sum;
        t[x].mv=max(t[t[x].l].mv,t[t[x].r].mv);
    }
}
int query(int l,int r,int x,int nl,int nr,int check)
{
    if(nl<=l&&nr>=r)
    {
        if(check)return t[x].sum;
        else return t[x].mv;
    }
    int mid=(l+r)>>1;
    int ans=0;
    if(nl<=mid&&t[x].l)
    {
        if(check)ans+=query(l,mid,t[x].l,nl,nr,check);
        else ans=max(ans,query(l,mid,t[x].l,nl,nr,check));
    }
    if(nr>mid&&t[x].r)
    {
        if(check)ans+=query(mid+1,r,t[x].r,nl,nr,check);
        else ans=max(ans,query(mid+1,r,t[x].r,nl,nr,check));
    }
    return ans;
}
int findans(int x,int y,int check)
{
    int ans=0;
    int tc=c[x];
    int tx=tp[x];
    int ty=tp[y];
    while(tx!=ty)
    {
        if(d[tx]<d[ty]){
            swap(tx,ty);
            swap(x,y); 
        }
        if(check) 
        ans+=query(1,n,rt[tc],start[tx],start[x],check);
        else ans=max(ans,query(1,n,rt[tc],start[tx],start[x],check));
        x=fa[tx];
        tx=tp[x];   
    }
    if(d[x]<d[y])swap(x,y);
    {
        if(check) 
            ans+=query(1,n,rt[tc],start[y],start[x],check);
        else ans=max(ans,query(1,n,rt[tc],start[y],start[x],check));
    }
    return ans;
}
int main()
{
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&a[i],&c[i]);
    }
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&tp1,&tp2);
        add(tp1,tp2); 
        add(tp2,tp1);
    } 
    dfs(1);
    dfs1(1,1);
    for(int i=1;i<=100000;i++)rt[i]=i;
    ntot=100000;
    for(int i=1;i<=n;i++)
        modify(rt[c[i]],start[i],a[i]); 
    char s[5];
    while(q--)
    {
        scanf("%s%d%d",s,&tp1,&tp2);
        if(s[1]=='C')
        {
            modify(rt[c[tp1]],start[tp1],0);
            c[tp1]=tp2;
            modify(rt[c[tp1]],start[tp1],a[tp1]);
        }
        else if(s[1]=='W')
        {
            a[tp1]=tp2;
            modify(rt[c[tp1]],start[tp1],a[tp1]);
        }
        else if(s[1]=='S')
            printf("%d\n",findans(tp1,tp2,1));
        else
            printf("%d\n",findans(tp1,tp2,0));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值