1. 程式人生 > >poj3495 Bitwise XOR of Arithmetic Progression 等差數列異或和

poj3495 Bitwise XOR of Arithmetic Progression 等差數列異或和

題意

就是求等差數列異或和
x表示首項,y表示末項,z表示公差

怎麼做?

等差數列的和,我會啊 !(首項+末項)*項數/2
然而這題並不是這樣的
由於異或的特殊性,於是我們可以一位一位來考慮
我們定義一個函式f(a,b,c,n)表示的是首項是a,公差是bc是一個除數,n是項數的和
對於第i位,最後一位叫第0位,從右往左數
我們要知道的就是f(x,z,2i,(yx)/z+1)的奇偶性
於是我們決定用最簡單粗暴的方式,那就是求和。。
我們現在要求的等價與下面這個式子

i=0n1x+zic
顯然地,式子可以變成下面這一個
i=0n1xc(zci)x%
c+z%ci
c

那麼前面兩個部分,我們可以一開始就算出來,這個沒有關係
那麼問題就只剩下了後面這一個
i=0n1x%c+z%cic
這麼怎麼求呢?
我們可以把他看做是一個關於i的,截距為x%cc,斜率為z%cc的函式g(i)
那麼就是問這個函式,在x=n之前,他的下面x軸的上面有多少個整點
容易知道,他的影象是這樣的
要注意的是,n這個地方的點是不能取到的
拿了POPOQQQ的一張圖
左邊的是沒有模過,右邊是已經模過了的
這裡寫圖片描述
右邊這個圖顯然有一個特點,那就是截距肯定是小於1
於是最diao的地方就來了
我們旋轉座標軸
沒錯,黃色的那個就是了
這個座標軸取的原點是(
n,g(n))

我們的問題就等價與求這個新座標軸下包含了多少個點
容易知道,這樣做的話,點數既沒有變多,也沒有變少
於是我們考慮討論這條直線
斜率顯然是之前的倒數(感受一下),那麼就得到了cb%c
那麼考慮一下截距是什麼
我們知道,下取整後,以g(n)為最高點的可能有很多個點,我們要找x座標最小的一個,容易知道,用這個作為新的交點是等價的
怎麼做呢?
方法也很簡單
就是一個式子:
(bn+a)%cb%c
這個式子比較顯然。。我就不解釋了
然後這裡有一個問題我想了很久,就是我們能不能取g(n1)作為最高點
因為這樣子看似沒有任何問題,並且還可以使得我們的區域變得更小,不失為一個好方法
於是我就直接吧上面的式子的n
改為了(n1)
然後發現WA了很久,為什麼呢?
我們考慮一下,如果兩個值是一樣的,那麼就沒有區別
有區別的地方在於,剛剛好在n這個地方上升了1
那麼這個時候,如果你用這個方法構造出來的答案,截距就要加1了,至於為什麼,讀者可以自己想一想

然後我們就可以變成一個子問題了
發現,無論是截距還是斜率,都如我們所期待的,除了同一個數,那就是b%c,那麼就可以變成一個子問題了

如果再觀察一下,發現,a和c是類似與輾轉相出地進行的,於是有複雜度保證,就是log層就結束了

為什麼這個演算法會得到改進呢?
因為我們發現,如果一個直線,他是十分陡峭的,我們可以輕鬆地算出一大片答案,但是如果比較平緩那就很難搞了
於是我們可以通過旋轉座標軸使得這條直線又變得陡峭起來,這樣子又可以一次算出一堆答案了
從而剩下很多複雜度,這個方法可以學習

然後就沒有啦!完結散花

搞了一個晚上,終於把所有疑問搞懂了,感覺很舒服啊
如果還有在哪裡講得有問題,可以留言啊

CODE:

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
LL x,y,z;//x是首項   y是不能超過y z是公差 
LL f (LL a,LL b,LL c,LL n)//首項   公差   c    項數 
{
    //printf("%I64d %I64d %I64d %I64d\n",a,b,c,n);
    if (n==0) return 0;
    LL ans=(b/c)*n*(n-1)/2+(a/c)*n;
    ans=ans+f((b*n+a)%c,c,b%c,((b%c)*n+(a%c))/c);
    return ans;
}
int main()
{
    while (scanf("%I64d%I64d%I64d",&x,&y,&z)!=EOF)
    {
    LL ans=0;
    for (LL u=0;u<32;u++)
        ans=ans|((f(x,z,(1LL<<u),(y-x)/z+1)&1)<<u);
    printf("%I64d\n",ans);
    }
    return 0;
}