1. 程式人生 > >P3747 相逢是問候 歐拉定理+線段樹

P3747 相逢是問候 歐拉定理+線段樹

tps sed style std 歐拉 不變 name 很多 這一

巨難!!!

去年六省聯考唯一的一道黑牌題,我今天一天從早到晚,把它從暴力15分懟到了90分,極端接近正解了。

bzoj上A了,但是洛谷和loj上面就不行。偽正解會T,奇奇怪怪的類正解會WA。。

那麽,網上的題解多得很,我就不細說了。

著重說一下我的理解感受和坑點。

1.不愧是黑牌題,顯得十分的繁雜(並不)。

首先要用到擴展歐拉定理,φ(),還有線段樹輔助,快速冪,大量奇奇怪怪的小細節.....要人命啊。

2.根據之前那題上帝集合,我們可以得知當一個數被操作很多很多很多很多次之後就不變了,成為一個常數。

3.我們首先算出這個次數:phi()到1就是了。特別的,phi(2)=1之後還要再寫個phi(1)=1,否則會錯。證明網上也很多,我比較推崇這個。(該證明並沒有被再次找到......)

4.第一個坑點來了:(c^c^i)%p ≠ (c^((c^i)%p))%p 什麽意思呢?意思就是你改一次之後不能接著改第二次,會WA。打暴力時就是這一點卡停了我的思路。如何解決:真·暴力!從初始值a[i]開始重新改起。我:......

5.解決了上面那一件事之後,我們開始著手研究擴展歐拉公式降次的那個式子。把(c^c^i)%p化開之後再一步步推下去,最後我們可以得到這麽一個可愛的函數:

技術分享圖片
 1 LL cal(int k,int t)
 2 {
 3     while(t>0)
 4     {
 5         if(k>=p[t]) k=qpow(c,k%p[t]+p[t],p[t-1
]); 6 else k=qpow(c,k,p[t-1]); 7 t--; 8 } 9 return k; 10 }
初等cal函數

看,它是如此的Cuty and goffy(?),這裏有個p[]數組,是之前預處理出來的每一層phi(P)。

6.然後加上一個線段樹,它滋磁區間求和,區間修改(每次修改到底),並記錄一個times表示修改的次數。

7.當某次修改時,如果times已經=cnt了,就return。否則修改,update。

8.開開心心的一交,又WA又T......

9.仔細觀察發現:那個可愛的cal中的判斷條件if(k>=p[t])顯然有誤。原因是計算卡速米(kasumi)時已經把結果%p[t-1]了,而上一層的p[t-1]就是這一層的p[t],於是那個if不會觸發。

10.翻看胡雨菲的題解,發現他把kasumi改了下,在kasumi裏記錄flag,保證了正確性。

11.交上去:T了兩個點。90分,bzojAC。本著不放棄不拋棄的原則繼續調試,發現要優化掉kasumi的時間復雜度,預處理一下。

12.那麽怎麽確定flag呢?①也預處理好。②每次在cal裏記錄一個tag,然後用log c p[t]<=tag來判定。

13.首先寫①,寫炸了。然後寫②,解決了T但是又WA了,依舊90分。然後轉①,繼續炸。但是理論上兩種方法都能AC。

14.over。

WA的代碼就不放了。放個11.中的代碼。

技術分享圖片
  1 #include <cstdio>
  2 #include <iostream>
  3 #include <algorithm>
  4 using namespace std;
  5 typedef long long LL;
  6 const int N = 50010;
  7 LL a[N],sum[N<<2],times[N<<2],p[N],c,P,cnt;
  8 inline LL read()
  9 {
 10     LL ans=0,f=1;char ch=getchar();
 11     while(ch<0||ch>9) {if(ch==-)f=-1;ch=getchar();}
 12     while(ch>=0&&ch<=9) {ans=ans*10;ans+=(ch-0);ch=getchar();}
 13     return ans*f;
 14 }
 15 inline LL qpow(LL a,LL b,LL m,bool &flag)
 16 {
 17     LL ans=1;
 18     flag=0;
 19     while(b)
 20     {
 21         if(b&1)
 22         {
 23             ans=ans*a;
 24             if(ans>=m) flag=1,ans%=m;
 25         }
 26         b=b>>1;
 27         a=a*a;
 28         if(a>=m) flag=1,a%=m;
 29     }
 30     return ans;
 31 }
 32 inline LL phi(LL x)
 33 {
 34     LL ans=x;
 35     for(register int i=2;i*i<=x;i++)
 36     {
 37         if(x%i==0)
 38         {
 39             while(x%i==0) x/=i;
 40             ans=(ans/i)*(i-1);
 41         }
 42     }
 43     if(x>1) ans=(ans/x)*(x-1);
 44     return ans;
 45 }
 46 inline void pre()
 47 {
 48     p[0]=P;
 49     while(P>1)
 50     {
 51         p[++cnt]=phi(P);
 52         P=p[cnt];
 53     }
 54     p[++cnt]=1;
 55     P=p[0];
 56     return;
 57 }
 58 inline void update(LL l,LL r,LL o)
 59 {
 60     sum[o]=sum[o<<1]+sum[o<<1|1];
 61     times[o]=min(times[o<<1],times[o<<1|1]);
 62     return;
 63 }
 64 inline void build(LL l,LL r,LL o)
 65 {
 66     if(l==r)
 67     {
 68         sum[o]=a[r]%P;
 69         return;
 70     }
 71     int mid=(l+r)>>1;
 72     build(l,mid,o<<1);
 73     build(mid+1,r,o<<1|1);
 74     update(l,r,o);
 75     return;
 76 }
 77 inline LL cal(int k,int t)
 78 {
 79     bool flag=(k>=p[t]);
 80     while(t>0)
 81     {
 82         if(flag) k=qpow(c,k%p[t]+p[t],p[t-1],flag);
 83         else k=qpow(c,k,p[t-1],flag);
 84         t--;
 85     }
 86     return k;
 87 }
 88 inline void add(int L,int R,int l,int r,int o)
 89 {
 90     if(times[o]>=cnt) return;
 91     if(l==r)
 92     {
 93         times[o]++;
 94         sum[o]=cal(a[r],times[o]);
 95         return;
 96     }
 97     int mid=(l+r)>>1;
 98     if(L<=mid) add(L,R,l,mid,o<<1);
 99     if(mid<R) add(L,R,mid+1,r,o<<1|1);
100     update(l,r,o);
101     return;
102 }
103 inline LL ask(int L,int R,int l,int r,int o)
104 {
105     if(L<=l&&r<=R) return sum[o];
106     if(R<l||r<L) return 0;
107     int mid=(l+r)>>1;
108     return (ask(L,R,l,mid,o<<1)+ask(L,R,mid+1,r,o<<1|1))%P;
109 }
110 int main()
111 {
112     LL m,n;
113     //scanf("%lld%lld%lld%lld",&n,&m,&P,&c);
114     n=read();m=read();P=read();c=read();
115     for(register int i=1;i<=n;i++) a[i]=read();//scanf("%lld",&a[i]);
116     pre();
117     build(1,n,1);
118     LL flag,x,y;
119     for(register int i=1;i<=m;i++)
120     {
121         //scanf("%d%d%d",&flag,&x,&y);
122         flag=read();
123         x=read();y=read();
124         if(flag) printf("%lld\n",ask(x,y,1,n,1));
125         else add(x,y,1,n,1);
126     }
127     return 0;
128 }
90分代碼

題外話:可以看見我加了很多的常數優化,但是洛谷的#9和#11兩個點劇毒。關於WA就放個鏈接吧,可以看出#3和#11比較毒,每次WA都有你們。

15分暴力->90分花了我一個上午。之後下午晚上都在優化那最後10分,還沒搞出來。效率堪憂啊。其實可以搞一搞其他幾道題的。

明天就是省選了。敬請收看:省選醬油記

P3747 相逢是問候 歐拉定理+線段樹