1. 程式人生 > >[Codeforces]849E Goodbye Souvenir

[Codeforces]849E Goodbye Souvenir

solution nod 合並 ++i ring 矩形 printf dash 這也

  又是一道比較新的模板題吧,即使是在Codeforces上小C還是貼了出來。

Description

  給定一個長度為n的序列a1~an,每個元素代表一種顏色。m次操作,每次操作為兩種中的一種:

    1 p x:將第p個位置上的顏色修改為x;

    2 l r:詢問[l,r]區間,求該區間內的每種顏色的“最大出現位置-最小出現位置”之和。

Input

  第一行兩個正整數n、m;

  第二行n個整數,表示a1~an;

  接下來m行,每行表示一個如題所示的操作。

Output

  對於每個操作2,輸出題目所求的答案。

Sample Input

  7 6
  1 2 3 1 3 2 1
  2 3 7
  2 1 3
  1 7 2
  1 3 2
  2 1 6
  2 5 7

Sample Output

  5
  0
  7
  1

HINT

  1 ≤ n,m ≤ 100 000,1≤ ai ≤ n;

  1 ≤ p,x ≤ n,1 ≤ l ≤ r ≤ n。

Solution

  應該說入手這道題還是很容易的,不管後面是怎麽做,我們首先可以判定它是一道數據結構題。

  我們考慮對於每個元素,我們維護上一個出現它的顏色的位置。

  這樣似乎就成為了我們很熟悉的矩形詢問一類的問題。我們類比一下詢問區間的顏色種數怎麽做:

  第一維代表區間下標,第二維代表上一次出現該顏色的位置,要維護的信息是該位置出現的次數(其實只有0和1),目的是求和。

  同理這一題似乎同樣可以這麽做:

  第一維代表區間下標,第二維代表上一次出現該顏色的位置,要維護的信息是 區間下標與上一次出現的位置的差 ,目的是求和。

  這樣似乎就很完美,我們可以直接樹套樹……然後並不能很爽地通過該題,因為炸空間了。

  那這可咋辦呀,我們就可以用到我們神奇的分治算法——cdq分治!

  cdq算法的主要思想就是將操作區間分成兩半,計算前一半操作對後一半詢問的影響。

  這樣就相當於將在線的修改去掉,將詢問改為離線。

  這也就要求詢問具有可合並性,如果操作之間會互相影響,cdq就不管用了。

  例如操作是加法而詢問是取max,這樣的詢問是不滿足可合並性的。

  對於這道題,每個操作對於答案的影響是獨立的,且每次修改顏色都會改變至多6次我們所維護的信息:

  設pre[x]為上一次出現該color[x]的位置,suc[x]為下一次出現color[x]的位置。而我們只要維護pre[x]。

  假設修改pre[x],改之後的pre[x]為npre[x],suc[x]同理。

  要修改的所有信息為:pre[x],pre[suc[x]],pre[nsuc[x]]。

  對於每個信息在二維平面上的操作是一次單點減和一次單點加,所以總共是3*2=6次。

  完全轉化成離線操作後,就只有詢問矩形和了,把詢問排序用一個普通線段樹都是可以做的。

  每次操作出現在logm個分治區間裏,單個操作的復雜度是logn,所以總時間復雜度O(mlogmlogn)。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <set>
#define ll long long
#define MM 264005
#define MN 500005
using namespace std;
struct meg{int ki,pos,lf,rf,val,aps;}px[MN];
struct node{int g,x,y;}b[MM];
set <int> se[MM];
ll t[MM],ans[MM];
int c[MM][4],las[MM],pre[MM],col[MM];
int MQ,n,m,pxin;

inline int read()
{
    int n=0,f=1; char c=getchar();
    while (c<0 || c>9) {if(c==-)f=-1; c=getchar();}
    while (c>=0 && c<=9) {n=n*10+c-0; c=getchar();}
    return n*f;
}

inline void getadd(int x,int z) {for (x+=MQ;x;x>>=1) t[x]+=z;}
inline ll getsum(int x,int y)
{
    register ll lt=0;
    for (x+=MQ,y+=MQ;x<=y;x>>=1,y>>=1)
    {
        if ( x&1) lt+=t[x++];
        if (~y&1) lt+=t[y--];
    }
    return lt;
}

bool cmp(const meg& a,const meg& b) {return a.pos<b.pos || a.pos==b.pos && a.ki<b.ki;}
void solve()
{
    sort(px+1,px+pxin+1,cmp);
    register int i;
    for (i=1;i<=pxin;++i)
        if (px[i].ki==0) {if (px[i].lf) getadd(px[i].lf,px[i].val);}
        else ans[px[i].aps]+=getsum(px[i].lf,px[i].rf)*px[i].val;
    for (i=1;i<=pxin;++i) if (px[i].ki==0&&px[i].lf) getadd(px[i].lf,-px[i].val);
}

void work(int L,int R,int gs)
{
    if (!gs||L==R) return;
    int i,qet=0,mid=L+R>>1;
    pxin=0;
    for (i=L;i<=mid;++i)
        if (b[i].g==1)
        {
            px[++pxin]=(meg){0,b[i].x,c[i][0],0,c[i][0]-b[i].x,0};
            px[++pxin]=(meg){0,b[i].x,c[i][1],0,b[i].x-c[i][1],0};
            px[++pxin]=(meg){0,c[i][2],b[i].x,0,b[i].x-c[i][2],0};
            px[++pxin]=(meg){0,c[i][2],c[i][0],0,c[i][2]-c[i][0],0};
            px[++pxin]=(meg){0,c[i][3],c[i][1],0,c[i][1]-c[i][3],0};
            px[++pxin]=(meg){0,c[i][3],b[i].x,0,c[i][3]-b[i].x,0};
        }
    for (i=mid+1;i<=R;++i)
        if (b[i].g==2)
        {
            px[++pxin]=(meg){1,b[i].x-1,b[i].x,b[i].y,-1,i};
            px[++pxin]=(meg){1,b[i].y  ,b[i].x,b[i].y, 1,i};
            ++qet;
        }
    solve(); work(L,mid,gs-qet); work(mid+1,R,qet);
}

int main()
{
    register int i,x,qet=0;
    n=read(); m=read();
    pxin=0;
    for (MQ=1;MQ<n;MQ<<=1); --MQ;
    for (i=1;i<=n;++i)
    {
        col[i]=x=read();
        las[i]=pre[x]; pre[x]=i;
        se[x].insert(i);
        px[++pxin]=(meg){0,i,las[i],0,i-las[i],0};
    }
    for (i=1;i<=m;++i)
    {
        b[i].g=read(); b[i].x=read(); b[i].y=read();
        if (b[i].g==1)
        {
            set<int> ::iterator k;
            k=se[col[b[i].x]].lower_bound(b[i].x);
            if (k!=se[col[b[i].x]].begin())    --k,c[i][0]=*k,++k; else c[i][0]=0;
            if ((++k)!=se[col[b[i].x]].end()) c[i][2]=*k; else c[i][2]=0;
            se[col[b[i].x]].erase(--k);
            col[b[i].x]=b[i].y;
            k=se[col[b[i].x]].lower_bound(b[i].x);
            if (k!=se[col[b[i].x]].end()) c[i][3]=*k; else c[i][3]=0;
            if (k!=se[col[b[i].x]].begin()) c[i][1]=*(--k); else c[i][1]=0;
            se[col[b[i].x]].insert(b[i].x);
        }
        else
        {
            px[++pxin]=(meg){1,b[i].x-1,b[i].x,b[i].y,-1,i};
            px[++pxin]=(meg){1,b[i].y  ,b[i].x,b[i].y, 1,i};
            ++qet;
        }
    }
    solve(); work(1,m,qet);
    for (i=1;i<=m;++i) if (b[i].g==2) printf("%I64d\n",ans[i]);
}

Last Word

  感覺這題會讓人覺得惡心的只有set的插入刪除操作了。

  相比樹套樹,只需要用到普通線段樹還是比較賞心悅目的。

[Codeforces]849E Goodbye Souvenir