顯示具有 ABBYY Cup 標籤的文章。 顯示所有文章
顯示具有 ABBYY Cup 標籤的文章。 顯示所有文章

2016年1月14日 星期四

[HOJ 285][ABBYY Cup 3.0]Hanhan's homework

HOJ生病惹QQ,因此我在這裡做了題目備份,請參考~

這題看起來好複雜QAQ
總之先相信它可以用線段樹維護吧(?)
這裡假設你已經建好波那契數列f的表了
假設我們建了一棵線段樹,存的值是f(1)*a1、f(2)*a2、f(3)*a3...
那麼,區間查詢和單點修改應該不會太難(就不提囉~
題目要求的是Sum{f(k)*a_(l+k),k=0~r-l}
而我們可以求出Sum{f(l+k)*a_(l+k),k=0~r-l}
可以怎麼利用呢?
為了方便說明,先定義g(i,l,r)=Sum{f(i+k)*a_(l+k),k=0~r-l}
所以題目要求g(0,l,r),而我們可以求出g(l,l,r)
如果我們拿g(l,l,r)減掉g(l-1,l,r)會發生甚麼事呢?
這個東西變成了g(l-2,l,r)!
而我們現在知道了g(l-2,l,r)是啥,那就可以再拿g(l-1,l,r)減掉g(l-2,l,r)求出g(l-3,l,r),然後是g(l-4,l,r)...,很快的(?)就可以得到g(0,l,r)了!
可以發現,假設g(l,l,r)是A,g(l-1,l,r)是B,l和r隨意,那麼可以透過以上相減關係,預處理出所有(x,y)使得g(i,l,r)=x*A+y*B(預處理的過程需要轉一些彎,詳情請參考code)
所以我們可以建兩棵線段樹分別維護g(l,l,r)和g(l-1,l,r)
就這樣,我們實現了O(log N)的單點修改和區間查詢g(0,l,r)
那區間修改呢?
首先,加入懶惰標記不難,重點是往下放的時候要怎麼維護?
其實,我們只需要關心區間和,因此,只要在往下放的時候分別將兩棵子樹增加「f的一段區間和*懶標的值」就好了(f的區間和可以用前綴和相減來處理)

講完了,別忘了還要Maintain XD

code:

#include<cstdio>
#include<cassert>
#include<vector>
#include<utility>
using namespace std;
typedef long long LL;
const LL MOD=1000000000LL;
void Plus(LL &a,const LL &b){(a+=b)%=MOD;}
int N,M;
vector<LL>F,FSUM;
LL GetF(const int i)
{
    if(i<(int)F.size())return F[i];
    F.push_back((GetF(i-2)+GetF(i-1))%MOD);
    FSUM.push_back(FSUM[i-1]+F[i]);
    return F[i];
}
LL GetFSum(const int l,const int r){GetF(r);return FSUM[r]-FSUM[l-1];}
struct SegTree
{
    const int gap;
    vector<LL>sum,tag;
    SegTree(const int _gap):gap(_gap){}
    void Maintain(const int id){sum[id]=(sum[id*2]+sum[id*2+1])%MOD;}
    void PutDown(const int id,const int l,const int mid,const int r)
    {
        if(tag[id]==0LL)return;
        const LL v=tag[id];tag[id]=0LL;
        Plus(tag[id*2],v);
        Plus(tag[id*2+1],v);
        Plus(sum[id*2],v*GetFSum(gap+l,gap+mid));
        Plus(sum[id*2+1],v*GetFSum(gap+mid+1,gap+r));
    }
    void Build(const int id,const int l,const int r,const vector<LL>&s)
    {
        while((int)sum.size()<=id)sum.push_back(0LL),tag.push_back(0LL);
        tag[id]=0LL;
        if(l==r){sum[id]=(s[r]*GetF(gap+r))%MOD;return;}
        const int mid=(l+r)/2;
        Build(id*2,l,mid,s),Build(id*2+1,mid+1,r,s);
        Maintain(id);
    }
    void Add(const int id,const int l,const int r,const int bl,const int br,const LL &v)
    {
        if(r<bl||br<l)return;
        if(bl<=l&&r<=br){Plus(tag[id],v),Plus(sum[id],v*GetFSum(gap+l,gap+r));return;}
        const int mid=(l+r)/2;
        PutDown(id,l,mid,r);
        Add(id*2,l,mid,bl,br,v),Add(id*2+1,mid+1,r,bl,br,v);
        Maintain(id);
    }
    void Modify(const int id,const int l,const int r,const int loc,const LL &v)
    {
        if(l==r){assert(r==loc);sum[id]=(v*GetF(gap+r))%MOD;return;}
        const int mid=(l+r)/2;
        PutDown(id,l,mid,r);
        if(loc<=mid)Modify(id*2,l,mid,loc,v);
        else Modify(id*2+1,mid+1,r,loc,v);
        Maintain(id);
    }
    LL Query(const int id,const int l,const int r,const int bl,const int br)
    {
        if(r<bl||br<l)return 0LL;
        if(bl<=l&&r<=br)return sum[id];
        const int mid=(l+r)/2;
        PutDown(id,l,mid,r);
        return (Query(id*2,l,mid,bl,br)+Query(id*2+1,mid+1,r,bl,br))%MOD;
    }
}SEG1=SegTree(1),SEG2=SegTree(2);
vector<pair<LL,LL> >COE;
pair<LL,LL>GetCOE(const int i)
{
    if(i<(int)COE.size())return COE[i];
    const pair<LL,LL>p1=GetCOE(i-2),p2=GetCOE(i-1);
    COE.push_back(make_pair((p1.first-p2.first+MOD)%MOD,(p1.second-p2.second+MOD)%MOD));
    return COE[i];
}
int main()
{
//    freopen("in.txt","r",stdin);
    F.push_back(1),F.push_back(1);
    FSUM.push_back(1),FSUM.push_back(2);
    COE.push_back(make_pair(1LL,0LL)),COE.push_back(make_pair(-1LL,1LL));
    while(scanf("%d%d",&N,&M)==2)
    {
        if(true)
        {
            vector<LL>s;
            for(int i=0;i<N;i++)
            {
                static LL v;scanf("%lld",&v);
                s.push_back(v);
            }
            SEG1.Build(1,0,N-1,s),SEG2.Build(1,0,N-1,s);
            s.clear(),vector<LL>().swap(s);
        }
        for(int type;M--;)
        {
            scanf("%d",&type);
            switch(type)
            {
                case 1:
                {
                    static int x,v;
                    scanf("%d%d",&x,&v),x--;
                    SEG1.Modify(1,0,N-1,x,v),SEG2.Modify(1,0,N-1,x,v);
                }break;
                case 2:
                {
                    static int l,r;
                    scanf("%d%d",&l,&r),l--,r--;
                    const LL v1=SEG1.Query(1,0,N-1,l,r),v2=SEG2.Query(1,0,N-1,l,r);
                    const pair<LL,LL>p=GetCOE(SEG1.gap+l);
                    const LL ans=((p.first*v1+p.second*v2)%MOD+MOD)%MOD;
                    printf("%lld\n",ans);
                }break;
                case 3:
                {
                    static int l,r,d;
                    scanf("%d%d%d",&l,&r,&d),l--,r--;
                    SEG1.Add(1,0,N-1,l,r,d),SEG2.Add(1,0,N-1,l,r,d);
                }break;
                default:assert(0);break;
            }
        }
        break;
    }
    return 0;
}

[HOJ 284][ABBYY Cup 3.0]PE Lesson

HOJ生病惹QQ,因此我在這裡做了題目備份,請參考~

這題CBD的題解寫得不錯,不過從他的轉移公式要推出一般式可能會需要一些數學技巧QAQ
在這裡我用另一個簡單許多的角度解釋轉移公式(我承認是看了他的題解才會這題XD)
首先,我們先照著CBD的思路
對於一個排列,要怎麼判斷他是否可以在有限的交換次數內換回來呢?
我們知道,一個排列可以拆成很多圈,因此只要把這些圈分開研究就可以了
首先我們可以證明:
只要一個大小為n的集合裡面「只能換一次的人數」不超過2,就可以形成一個圈

先證明2人的情況可以形成一個圈:
假設要經過x0 -> x1 ->...-> x_i -> a -> x_(i+2) ->...-> x_j -> b -> x_(j+2) ->...-> x0的輪迴才能把序列變回來,其中a和b是只能交換一次的人
首先,先讓a和b得到他想要的東西,也就是進行交換(a,x_(i+2))和(b,x_(j+2))
然後i+2和i+3這兩個位置再互換...以此類推,b後面也是一樣
最後,剩下i和j這兩個位置還沒得到想要的東西(i(想要a)存的是b,j(想要b)存的是a)
這時,i和j再進行一次交換就好了

再來證明3人的情況無法形成一個圈:
要換回來,至少要交換n-1次,但是這群人能交換的最多次數是(3*1+(n-3)*2)/2向下取整=n-2次,所以是不可能換回來的

證明完畢---*
\
先跟大家講一下,上面那些證明不是重點www(遭踢飛
所以,我們可以先把只能換一次的人(以下稱X)依照位置編號,假設是1~A好了,可以換兩次的(以下稱Y)也依照位置編號為A+1~N吧
我們可以先決定1~A各要分配在那些圈裡面(同一個圈最多兩人),假設有c(A)種合法的分配
取其中一種分配方式
這時再把A+1~N插進入(順序和插入位置都是隨便的),我們對於每個X
1. 如果他沒有和另一個配對,就和排在他之後直到碰到另一個X之間的所有Y照排隊順序形成一個置換
2. 如果他和X2配對,那就把X2之後的Y也含進這個圈裡面,變成X、Y1、Y2...Yi、X2、Yi+1...
可以發現,以上分配法剛好跟每一種合法置換方式一一對應
而對於X的每一種分配方式,把Y插入之後可能產生的情況就有N!/A!種(先X和Y全部一起亂排,再把X排好),所以答案就是c(A)*N!/A!了!

再來講c(A)怎麼算

A=1時,c(A)=1
A=2時,c(A)=2
A=n時,第A個人有兩種分配方式
1. 自己的圈沒有其他人:c(A-1)
2. 從其他A-1個人選一個和自己住同一個圈:(A-1)*c(A-2)
因此,遞迴式就出來了:c(A)=c(A-1)+(A-1)*c(A-2)
可以用記憶化搜索實作,複雜度為O(N)

code: