清華集訓 2014--奇數國(線段樹&歐拉函數&乘法逆元&狀態壓縮)
昨天想了一晚。。。早上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。
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--奇數國(線段樹&歐拉函數&乘法逆元&狀態壓縮)