1. 程式人生 > >清華集訓 2014--奇數國(線段樹&歐拉函數&乘法逆元&狀態壓縮)

清華集訓 2014--奇數國(線段樹&歐拉函數&乘法逆元&狀態壓縮)

-m 很多 class iostream div %d 正整數 ber 計劃

昨天想了一晚。。。早上AC了。。。

這麽長時間沒打線段樹,這回居然一次過了。。。

感覺數論方面應該已經沒有太大問題了。。。

之後要開始搞動態規劃之類的東西了。。。

題意

在一片美麗的大陸上有100000個國家,記為1到100000。這裏經濟發達,有數不盡的賬房,並且每個國家有一個銀行。某大公司的領袖在這100000個銀行開戶時都存了3大洋,他惜財如命,因此會不時地派小弟GFS清點一些銀行的存款或者讓GFS改變某個銀行的存款。該村子在財產上的求和運算等同於我們的乘法運算,也就是說領袖開戶時的存款總和為3^100000。這裏發行的軟妹面額是最小的60個素數(p1=2,p2=3,…,p60=281),任何人的財產都只能由這60個基本面額表示,即設某個人的財產為fortune(正整數),則fortune=p1^k1*p2^k2*......p60^K60。

領袖習慣將一段編號連續的銀行裏的存款拿到一個賬房去清點,為了避免GFS串通賬房叛變,所以他不會每次都選擇同一個賬房。GFS跟隨領袖多年已經摸清了門路,知道領袖選擇賬房的方式。如果領袖選擇清點編號在[a,b]內的銀行財產,他會先對[a,b]的財產求和(計為product),然後在編號屬於[1,product]的賬房中選擇一個去清點存款,檢驗自己計算是否正確同時也檢驗賬房與GFS是否有勾結。GFS發現如果某個賬房的編號number與product相沖,領袖絕對不會選擇這個賬房。怎樣才算與product不相沖呢?若存在整數x,y使得number*x+product*y=1,那麽我們稱number與product不相沖,即該賬房有可能被領袖相中。當領袖又賺大錢了的時候,他會在某個銀行改變存款,這樣一來相同區間的銀行在不同的時候算出來的product可能是不一樣的,而且領袖不會在某個銀行的存款總數超過1000000。

現在GFS預先知道了領袖的清點存款與變動存款的計劃,想請你告訴他,每次清點存款時領袖有多少個賬房可以供他選擇,當然這個值可能非常大,GFS只想知道對19961993取模後的答案。

Input

第一行一個整數x表示領袖清點和變動存款的總次數。 接下來x行,每行3個整數ai,bi,ci。ai為0時表示該條記錄是清點計劃,領袖會清點bi到ci的銀行存款,你需要對該條記錄計算出GFS想要的答案。ai為1時表示該條記錄是存款變動,你要把銀行bi的存款改為ci,不需要對該記錄進行計算。

Output

輸出若幹行,每行一個數,表示那些年的答案。

Solution

不得不說,出題人真是太好心了。。。感動。。。

雖然題目看起來很復雜,但實際上很多東西是對題目難度的限制。。。

國家數量強行當做100000。。。比一般線段樹的題要數量要稍微少一些。。。

為了讓題目簡單一些,用貨幣只有60種來降低難度。。。

又拐彎抹角的暗示了歐拉函數。。。

也就是說,只要知道歐拉函數,會打線段樹,知道乘法逆元,這就是一道送分題。。。

用線段樹來維護區間的乘積。。。用狀態壓縮來表示是否存在某個質因數。。。

代碼

#include<cstdio>
#include<iostream>
#define mod 19961993
#define LL long long
using namespace std;
LL two_n[65],ny[300],ans,ans2;
const int prime[65]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,
71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,
179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281};
struct node{
    int l,r;
    LL sum,how_pri;
}a[410000];
void calc(){
    for(int i=1;i<=60;i++)
        if(ans2&two_n[i])
            ans=ans*ny[prime[i]]%mod;
}
void build(int left,int right,int num){
    a[num].l=left;a[num].r=right;
    if(left==right){
        a[num].sum=3;
        a[num].how_pri=two_n[2];
        return;
    }
    int mid=(left+right)>>1;
    build(left,mid,num<<1);
    build(mid+1,right,(num<<1)|1);
    a[num].sum=a[num<<1].sum*a[(num<<1)|1].sum%mod;
    a[num].how_pri=a[num<<1].how_pri|a[(num<<1)|1].how_pri;
    return;
}
void update(int num,int dt,int b){
    if(a[num].l==a[num].r){
        a[num].sum=b;
        a[num].how_pri=0;
        for(int i=1;i<=60;i++)
            if(b%prime[i]==0)
                a[num].how_pri=a[num].how_pri|two_n[i];
        return;
    }
    if(dt<=a[num<<1].r) update(num<<1,dt,b);
    else update((num<<1)|1,dt,b);
    a[num].sum=a[num<<1].sum*a[(num<<1)|1].sum%mod;
    a[num].how_pri=a[num<<1].how_pri|a[(num<<1)|1].how_pri;
    return;
}
void find(int num,int left,int right){
    if(a[num].l==left&&a[num].r==right){
        ans=ans*a[num].sum%mod;
        ans2=ans2|a[num].how_pri;
        return;
    }
    if(right<=a[num<<1].r) find(num<<1,left,right);
    else if(left>a[num<<1].r) find((num<<1)|1,left,right);
    else{
        find(num<<1,left,a[num<<1].r);
        find((num<<1)|1,a[(num<<1)|1].l,right);
    }
    return;
}
int main(){
    int q,ty,a,b;
    ny[1]=1;
    for(int i=2;i<=281;i++) ny[i]=(mod-mod/i)*ny[mod%i]%mod;
    for(int i=1;i<=60;i++){
        if(i==1) two_n[i]=1;
        else two_n[i]=two_n[i-1]<<1;
        ny[prime[i]]=ny[prime[i]]*(prime[i]-1)%mod;
    }
    build(1,100000,1);
    scanf("%d",&q);
    for(int i=1;i<=q;i++){
        scanf("%d%d%d",&ty,&a,&b);
        if(ty==1) update(1,a,b);
        else{
            ans=1;
            ans2=0;
            find(1,a,b);
            calc();
            printf("%lld\n",ans);
        }
    }
    return 0;
}

This passage is made by Yukino.

清華集訓 2014--奇數國(線段樹&歐拉函數&乘法逆元&狀態壓縮)