Luogu P1972 [SDOI2009]HH的项链

Luogu P1972 [SDOI2009]HH的项链

之前做过的一道题
模拟赛出了原题(当然是改头换面了的)
我竟然没有看出来。。。
考完LinJY 找出HH的项链 然后我一看题面就想起怎么做了。。。
发一下之前自己在luogu上写的blog

之前的代码:

#include<cstdio>
#include<cstring>

int a[1000010],n,m,sum[1000010],len=0;
struct nod1{int l,r,id;}b[1000010];
struct nod2{int l,r,c,lc,rc;}tr[1000010];
int before[1000010],next[1000010],v[1000010];
//数组索性全部这么大才过了
//。。。之前RE了3次 

int dfs(int l,int r)
{
    int x=l,y=r,m=b[(x+y)/2].l;
    while(x<=y)
    {
        while(b[x].l<m)x++;
        while(b[y].l>m)y--;
        if(x<=y)
        {
            nod1 t=b[x];b[x]=b[y];b[y]=t;
            x++;y--;
        }
    }   
    if(l<y)dfs(l,y);
    if(x<r)dfs(x,r);
}//按左端点排序 

int bt(int l,int r)
{//普通建树 
    len++;
    int now=len;tr[now].l=l;tr[now].r=r;
    tr[now].lc=tr[now].rc=-1;tr[now].c=0;
    if(l<r)
    {
        int mid=(l+r)/2;
        tr[now].lc=len+1;bt(l,mid);
        tr[now].rc=len+1;bt(mid+1,r);
    }
    //if(l==r)tr[now].c=a[l];
}

int change(int now,int x,int k)//k其实没啥用 
{//修改 
    if(tr[now].l==tr[now].r)
    {
        tr[now].c=1;
        //表示标记这个数是当前区间独一无二的颜色 
        return 0;
    }

    int mid=(tr[now].l+tr[now].r)/2;
    int lc=tr[now].lc,rc=tr[now].rc;
    if(x<=mid)change(lc,x,k);
    else if(mid+1<=x) change(rc,x,k);

    tr[now].c=tr[lc].c+tr[rc].c;
}

int findsum(int now,int l,int r)
{//普通求和(计数) 
    if(l==tr[now].l&&r==tr[now].r)
    {
        return tr[now].c;
    }
    int mid=(tr[now].l+tr[now].r)/2;
    int lc=tr[now].lc,rc=tr[now].rc;
    if(r<=mid)return findsum(lc,l,r);
    else if(mid+1<=l)return findsum(rc,l,r);
    else return findsum(lc,l,mid)+findsum(rc,mid+1,r);

    tr[now].c=tr[lc].c+tr[rc].c;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    bt(1,n);//先建一棵空树 
    for(int i=1;i<=n;i++)
    {
        if(v[a[i]]==0)
        {
            change(1,i,1);
            v[a[i]]=i;//是这个区间第一个这种颜色 
        }
        else
        {//不是的活,标记为现在这个区间上一个颜色的继承人 
            before[i]=v[a[i]];
            v[a[i]]=i;
            next[before[i]]=i;
        }
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d",&b[i].l,&b[i].r);
        b[i].id=i;//b数组的id是记录是第几个询问
                  //因为一会要排序,而答案要按序输出 
    }//先把全部询问输进来,离线做 
    dfs(1,m);
    for(int i=1;i<=m;i++)
    {
        for(int j=b[i-1].l;j<b[i].l;j++)
        {
            change(1,next[j],1);
            //让上个区间的颜色把“资格”给他们继承人 
        }
        sum[b[i].id]=findsum(1,b[i].l,b[i].r);
        //把找到的这个区间的和记录在答案数组里 
    }
    for(int i=1;i<=m;i++)
        printf("%d\n",sum[i]);
}

重新做了一次(好像并没有多大区别qwq只是想贴上来)

#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

int len=0,n,m,pre[4000100],ans[4000100];
struct nod2{int x,next,v;}a[4000100];
struct nod1{int l,r,id;}q[4000100];
struct nod3{int l,r,lc,rc,c;}tr[4000100];

int dfs1(int l,int r)
{
    int x=l,y=r,m=q[(x+y)/2].l;
    while(x<=y)
    {
        while(q[x].l<m)x++;
        while(q[y].l>m)y--;
        if(x<=y)
        {
            nod1 t=q[x];q[x]=q[y];q[y]=t;
            x++;y--;
        }
    }
    if(l<y)dfs1(l,y);
    if(x<r)dfs1(x,r);
}

int dfs2(int l,int r)
{
    int x=l,y=r,m=q[(x+y)/2].r;
    while(x<=y)
    {
        while(q[x].r<m)x++;
        while(q[y].r>m)y--;
        if(x<=y)
        {
            nod1 t=q[x];q[x]=q[y];q[y]=t;
            x++;y--;
        }
    }
    if(l<y)dfs2(l,y);
    if(x<r)dfs2(x,r);
}

void bt(int l,int r)
{
    len++;
    int now=len;
    tr[now].l=l;tr[now].r=r;
    tr[now].lc=tr[now].rc=-1;tr[now].c=0;
    if(l<r)
    {
        int mid=(l+r)/2;
        tr[now].lc=len+1;bt(l,mid);
        tr[now].rc=len+1;bt(mid+1,r);
    }
}

int findsum(int now,int l,int r)
{
    //printf("4444 ");
    if(tr[now].l==l&&tr[now].r==r)
    {
        //printf("1111 ");
        return tr[now].c;
    }
    int mid=(tr[now].l+tr[now].r)/2;
    int lc=tr[now].lc,rc=tr[now].rc;
    if(r<=mid)return findsum(lc,l,r);
    else if(mid+1<=l)return findsum(rc,l,r);
    else return findsum(lc,l,mid)+findsum(rc,mid+1,r);
}

void change(int now,int x,int k)
{
    if(tr[now].l==tr[now].r)
    {//printf("11111\n");
        tr[now].c=k;return ;
    }
    int mid=(tr[now].l+tr[now].r)/2;
    int lc=tr[now].lc,rc=tr[now].rc;
    if(x<=mid)change(lc,x,k);
    else if(mid+1<=x)change(rc,x,k);

    tr[now].c=tr[lc].c+tr[rc].c;
}

int main()
{
    scanf("%d",&n);
    bt(1,n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i].x);
        int x=a[i].x;
        if(pre[x]==0)
        {
            a[i].v=1;
            change(1,i,1);
        }
        a[pre[x]].next=i;
        pre[x]=i;
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d",&q[i].l,&q[i].r);
        q[i].id=i;
    }
    dfs1(1,m);//ok
    /*for(int i=1;i<=m;i++)
    {
        if(q[i].l==q[i+1].l)
        {
            int t=i+1;
            while(q[t].l==q[t+1].l)t++;
            dfs2(i,t);
            i=t;
        }
    }*/
    /*for(int i=1;i<=m;i++)
    {
        printf("%d %d |",q[i].l,q[i].r);
    }printf("\n");//ok*/
    for(int i=1;i<=m;i++)
    {
        //printf("2222 ");
        int l;
        l=ans[q[i].id]=findsum(1,q[i].l,q[i].r);
        //printf("3333 ");
        //printf("%d\n",l);
        int nl=q[i+1].l;
        if(q[i].l!=nl)
        {
            for(int j=q[i].l;j<nl;j++)
            {
                if(a[j].v)
                {
                    //printf("x:%d\n",j);
                    a[j].v=0;
                    change(1,j,0);
                    int y=a[j].next;
                    a[y].v=1;
                    if(y<=n&&y>=1)
                    {
                        //printf("y:%d\n",y);
                        change(1,y,1);
                    }
                }
            }
        }

    }
    for(int i=1;i<=m;i++)
    {
        printf("%d\n",ans[i]);
    }
}
思路,就是
1. 把询问全弄进来,按左端点排序,然后顺序做
2. 预处理每个颜色第一个“代表”(代表就是有资格被计数的珠子)
3. 对于处理每次询问前,我们将在当前询问左端点前的颜色“代表”将“资格”给他们的继承人
4. 可以确定只要从上一次询问左端点起让颜色将资格后传即可(因为再前面已经一样传了啊~)
5. 答案就是对本次询问l~r中“资格”数

猜你喜欢

转载自blog.csdn.net/qq_42142540/article/details/80842303