2015年3月31日 星期二

[POI 19 Stage 1] Rendezvous

作法:

每個點的出度都只有1,所以這是一個類似水母圖的東西,是一個有向圈再接上一些指向他的樹所形成的。對每個詢問,分成幾種情形:

1. x 和 y 在不同的連通塊上
2. x 和 y 在相同連通塊上,但從 x 開始一直走,走到圈上的第一個點和 y 走到圈上的第一個點不同。
3. x 和 y 在相同連通塊上,他們走到圈上的第一個點相同。

1. 直接輸出 -1 -1 即可。至於 2. ,我們會需要 x 走到圈的距離,還有 y 走到圈的距離,還有 x 走到圈上的那個點與 y 走到圈上的那個點的距離是多少。為了求出這個東西,我們會需要對每個點紀錄他走到圈上的第一個點是誰,並且為了求出他們在圈上的距離,還需要對每個在圈上的點紀錄一個圈上的距離值,把這個值兩個相減就可以得到在圈上的距離。而因為在第2種情況時有兩種走法,所以還需要一個圈的大小。

3. 的話就變成了求LCA的問題了,只要把在圈上的點的父親都設成自己,然後把所有邊都反向作LCA即可。

code :

#include<bits/stdc++.h>
using namespace std;
const int maxn=500000+10 ;
 
int nex[maxn] ;
int dis[maxn],ord[maxn],cycpt[maxn],cycid[maxn] ;
int size[maxn] ;
int ccnt=0 ;
 
int fa[maxn],vis[maxn] ;
void dfs(int x)
{
    if(vis[x]==-1)
    {
        ccnt++ ;
        int cnt=0 ;
        for(int y=x;;y=fa[y])
        {
            size[ccnt]++ ;
            ord[y]=++cnt ;
            cycpt[y]=y ;
            cycid[y]=ccnt ;
            if(fa[y]==x || fa[y]==-1) break ;
        }
        vis[x]=1 ;
        return ;
    }
 
    vis[x]=-1 ;
    if(vis[nex[x]]!=1) fa[nex[x]]=x , dfs(nex[x]) ;
 
    vis[x]=1 ;
    if(ord[x]) return ;
    dis[x]=dis[nex[x]]+1 ;
    cycpt[x]=cycpt[nex[x]] ;
    vis[x]=1 ;
}
 
vector<int> v[maxn] ;
int anc[19][maxn],dep[maxn] ;
void dfs2(int x)
{
    for(int i=1;i<19;i++) anc[i][x]=anc[i-1][anc[i-1][x]] ;
    for(int i=0;i<v[x].size();i++)
        anc[0][v[x][i]]=x , dep[v[x][i]]=dep[x]+1 ,
        dfs2(v[x][i]) ;
}
 
int getfa(int x,int d)
{
    if(!d) return x ;
    for(int i=18;i>=0 && d;i--) if(d&(1<<i))
        x=anc[i][x] , d^=(1<<i) ;
    return x ;
}
 
int LCA(int x,int y)
{
    if(dep[x]!=dep[y]) return dep[x]>dep[y] ?
        LCA(getfa(x,dep[x]-dep[y]),y) :
        LCA(x,getfa(y,dep[y]-dep[x])) ;
    if(x==y) return x ;
    for(int i=18;i>=0;i--) if(anc[i][x]!=anc[i][y])
        x=anc[i][x] , y=anc[i][y] ;
    return anc[0][x] ;
}
 
int ans1,ans2 ;
bool better(int d1,int d2)
{
    if(ans1==-1) return 1 ;
    if(max(d1,d2)!=max(ans1,ans2)) return max(d1,d2)<max(ans1,ans2) ;
    if(min(d1,d2)!=min(ans1,ans2)) return min(d1,d2)<min(ans1,ans2) ;
    return d1>=d2 ;
}
inline void update(int d1,int d2) { if(better(d1,d2)) ans1=d1 , ans2=d2 ; }
 
main()
{
    int n,Q ; scanf("%d%d",&n,&Q) ;
    for(int i=1;i<=n;i++) scanf("%d",&nex[i]) ;
 
    for(int i=1;i<=n;i++) if(!vis[i])
        fa[i]=-1 , dfs(i) ;
 
    for(int i=1;i<=n;i++) if(!ord[i] || !ord[nex[i]])
        v[nex[i]].push_back(i) ;
    for(int i=1;i<=n;i++) if(ord[i])
        anc[0][i]=i , dfs2(i) ;
 
    while(Q--)
    {
        int x,y ; scanf("%d%d",&x,&y) ;
        if(cycid[cycpt[x]]!=cycid[cycpt[y]])
            { printf("-1 -1\n") ; continue ; }
        if(cycpt[x]==cycpt[y])
        {
            int lca=LCA(x,y) ;
            printf("%d %d\n",dep[x]-dep[lca],dep[y]-dep[lca]) ;
            continue ;
        }
 
        ans1=ans2=-1 ;
        int d1=dis[x] , d2=dis[y] , id=cycid[cycpt[x]] ;
        int sz=size[id] ;
        int cdis=(ord[cycpt[x]]-ord[cycpt[y]]+sz)%sz ;
        update(d1+cdis,d2) ;
        update(d1,d2+sz-cdis) ;
        printf("%d %d\n",ans1,ans2) ;
    }
}
 

[POI 19 Stage 1] Distance

作法:

設d[ i ] 代表 i 是由幾個質數乘起來得到的,例如d[ 8 ] = 3 ,那麼就可以知道 x 和 y 的距離就等於 d[ x ] + d[ y ] - 2 * d[ gcd( x , y ) ] 。所以當我們在算一個數 x 的答案時,我們可以枚舉所以他的因數,用來當 gcd( x , y ) ,而在確定了這兩個數之後,剩下的目的就變成要讓 d[ y ] 越小越好。所以對每個數,我們可以紀錄在數列裡的哪個數是他的 d 值最小而且又是那個數的倍數的。並且之後直接查詢就好了。但這樣會有個問題,如果 x 的某個因數 z ,他紀錄下來的數恰好是 x ,那就沒有用了,所以應該要保存兩個最佳解,兩個都試試看。

最後還要注意到的是,當我們去看 z 的最佳的兩個倍數的時候,他和 x 的 gcd 值不一定是 z ,可能會是 z 的倍數,但這個方法可以保證找到最佳的解。這個的證明也不難,因為如果有個沒被找到的話就可以推出那個數不會是最佳解。

code :

#include<bits/stdc++.h>
using namespace std;
const int maxn=100000+10 , maxm=1000000+10 ;
 
int vis[maxm] ;
int la[maxm],MA=0 ;
void prime()
{
     for(int i=2;i*i<=MA;i++) if(!vis[i])
          for(int j=i*i;j<=MA;j+=i) vis[j]=1 , la[j]=i ;
}
 
int d[maxm] ;
void getd()
{
     d[1]=0 ;
     for(int i=2;i<=MA;i++) d[i]= vis[i] ? d[i/la[i]]+1 : 1 ;
}
 
int getdis(int x,int y)
{
     int g=__gcd(x,y) ;
     return d[x]+d[y]-2*d[g] ;
}
 
struct P
{
     int id,val ;
     bool operator < (const P &rhs) const
     {
          return d[val]==d[rhs.val] ? id<rhs.id : d[val]<d[rhs.val] ;
     }
};
 
P v[maxm][3] ;
int sz[maxm] ;
void PB(int vid,const P &p)
{
     v[vid][sz[vid]++]=p ;
     if(sz[vid]==3)
     {
          for(int i=0;i<2;i++) for(int j=2;j>i;j--)
               if(v[vid][j]<v[vid][j-1]) swap(v[vid][j],v[vid][j-1]) ;
          sz[vid]-- ;
     }
}
 
int a[maxn] ;
main()
{
     int n ; scanf("%d",&n) ;
     for(int i=1;i<=n;i++) scanf("%d",&a[i]) , MA=max(MA,a[i]) ;
 
     prime() ;
     getd() ;
     for(int i=1;i<=n;i++)
          for(int j=1;j*j<=a[i];j++) if(a[i]%j==0)
     {
          PB(j,(P){i,a[i]}) ;
          if(a[i]!=j*j) PB(a[i]/j,(P){i,a[i]}) ;
     }
 
     for(int i=1;i<=n;i++)
     {
          int ans=maxm , id=n+1 ;
 
          for(int j=1;j*j<=a[i];j++) if(a[i]%j==0)
               for(int t=0;t<2;t++)
          {
               int x= (t==0 ? j : a[i]/j) ;
               if(v[x][0].id==i && sz[x]==1) continue ;
 
               P tmp= (v[x][0].id==i ? v[x][1] : v[x][0]) ;
               int val=getdis(tmp.val,a[i]) ;
               if(val<ans || (val==ans && tmp.id<id))
                    ans=val , id=tmp.id ;
          }
          printf("%d\n",id) ;
     }
}
 

2015年3月30日 星期一

[POI 20 Stage 1] Multidrink

這題簡單來說就是每次可以走距離不超過 2 的點,問是否存在起點到終點的哈密頓路徑。之後提到的「走」都是指移動到離他距離為 1 或 2 的點。

作法:

以起點當根轉換為有根樹,記起點為 st ,終點為 ed ,並且起點沿最短路走到終點的路徑為 x_0 = st -> x_1 -> ... -> x_r = ed 。所以我們的走法是:一開始在 x_0 時,必須要先把 x_0 的不包含 x_1 所在子樹的整個子樹都走一遍,最後停在 x_0 的某個非 x_1 的子節點 ( 若 x_0 沒有 x_1 以外的子節點則會停在 x_0 ),然後再進入 x_1 的子樹,先不走 x_2 所在的子樹,就這樣一直重複下去,最後到了 x_r 時則是變成先走完所有 x_r 的子孫,最後再停在 x_r。

由上述第一步的「先把 x_0 的不包含 x_1 所在子樹的整個子樹都走一遍,最後停在 x_0 的某個非 x_1 的子節點」,可以得到我們需要考慮這樣的一個子問題:現在在一個子樹的根 u ,是否有辦法走遍這個子樹,最後停在 u 的其中一個子節點。( 註:所以我先把連接 x_i 和 x_( i + 1 ) 的邊拔掉,這樣就可以讓DFS下去的子問題是正確的。 )

對於這個子問題,首先如果 u 有子節點是葉子的話,那可以先不理他,可以最後再一次把他們走完,所以只需要關心度 > 1 的子節點們。而我們可以先對每個節點的子節點按照其 degree 由大到小排序,這樣之後就會比較方便。如果 u 的度 > 1 的子節點數目 > 1 的話,可以發現這樣一定沒辦法達成目標,所以他至多有一個度 > 1 的子節點。如果沒有的話那就全部都是葉子,輕鬆把它處理完。如果有的話,設他叫作 v 好了,那麼這時候 u 踏出去的第一步一定是走到 v 的其中一個子節點,然後把 v 的子樹全部走完,最後停在 v 。

所以這樣就產生了第二種子問題:要從某一個子樹 u 的子節點開始走,走遍這棵子樹,最後停在 u 。一樣可以先不管 u 的葉子子節點,那麼類似的可以發現,如果 u 的度 > 1 的子節點數目 > 1 的話一樣沒辦法達成目標。沒有的話一樣很簡單,有的話就會再次把問題轉換為第一個子問題的情形。

但事實上還有一種情況要考慮,例如 x_0 只有 x_1 一個子節點時,這時候 x_1 可以變成從他的其中一個子節點開始走,但最後不一定要停留在 x_1 ,可以停留在 x_1 的另一個子節點,這樣也可以在走完 x_1 的子樹時下一步踏入 x_2 的領域。所以我們又多了另一個子問題( 或不如說是上述第二個子問題的要求較不嚴苛的版本 ),也就是允許最後停留的點是當前根節點的子節點。又因為多考慮了這件事,所以當走完 x_i 的子樹時我們必須還要知道目前到底是停在 x_i 還是 x_i 的其中一個子節點,這會影響到進入 x_( i + 1 ) 的子樹時的形式,所以必須在解決完一個子問題之後回傳最後是停在根還是停在根的某個子節點。

而關於第三個子問題的討論就比較麻煩了,大致上是如果 u 的度 > 1 的子節點數目 > 2 的話就沒辦法達成目標,沒有的話一樣簡單,如果有一個的話,也把他叫 v 好了,我們先試試看這棵子樹有沒有辦法從某個 u 的子節點開始把他全部走完,然後停在 u ,測試方法根前面第二個子問題一樣。如果沒辦法的話,就改成先踏上 u ,然後去試有沒有辦法從 v 的某個子節點開始走,走遍 v 的子樹,最後停在 v ,這樣就等於停在 u 的其中一個子節點了。

如果 u 的度 > 1 的子節點數目 = 2 ,把他們叫作 s1 和 s2 好了,那麼這時的走法就變成:先走到 s1 ,然後走遍 s1 的子樹,最後停在 s1 的某個子節點,然後走到 u ,再從 s2 的某個子節點開始走,最後停在 s2 。或是 s1 和 s2 可以交換地位。如果這兩種都不行就代表不存在要求的路徑。

最後,我在實作上還有先把每個點連往他父節點的(有向)邊都拔掉,不然每次還要判是不是父親很麻煩。這題真是蠻恐怖的討論題阿......各種好麻煩的東西QQ

code :

#include<bits/stdc++.h>
using namespace std;
const int maxn=500000+10 ;
 
vector<int> v[maxn] ;
int fa[maxn],nex[maxn] ;
 
void del(int x,int y)
{
    for(int i=0;i<v[x].size();i++) if(v[x][i]==y)
    {
        for(int j=i;j+1<v[x].size();j++) v[x][j]=v[x][j+1] ;
        v[x].resize((int)v[x].size()-1) ;
        break ;
    }
}
 
void dfs0(int x)
{
    for(int i=0;i<v[x].size();i++) if(v[x][i]!=fa[x])
        fa[v[x][i]]=x , dfs0(v[x][i]) ;
}
 
bool cmp(int a,int b){ return v[a].size()>v[b].size() ; }
 
int cnt=0 , ans[maxn] ;
 
int solve1(int x) ;
int solve2(int x,int type)
{
    if(v[v[x][0]].empty())
    {
        for(int i=0;i<v[x].size();i++) ans[cnt++]=v[x][i] ;
        ans[cnt++]=x ;
        return 1 ;
    }
 
    int num=0 ;
    while(num<v[x].size() && !v[v[x][num]].empty()) num++ ;
    if(num>2) return 0 ;
    if(type==0)
    {
        if(num>1) return 0 ;
        for(int i=1;i<v[x].size();i++) ans[cnt++]=v[x][i] ;
        if(!solve1(v[x][0])) return 0 ;
        ans[cnt++]=x ;
        return 1 ;
    }
 
    if(num==1)
    {
        int tmp=cnt ;
        for(int i=1;i<v[x].size();i++) ans[cnt++]=v[x][i] ;
        if(!solve1(v[x][0]))
        {
            cnt=tmp ;
            ans[cnt++]=x ;
            if(!solve2(v[x][0],0)) return 0 ;
            for(int i=1;i<v[x].size();i++) ans[cnt++]=v[x][i] ;
            return 2 ;
        }
        else
        {
            ans[cnt++]=x ;
            return 1 ;
        }
    }
    else for(int i=0;i<2;i++)
    {
        int s1=v[x][i] , s2=v[x][(i+1)%2] ;
        int tmp=cnt ;
        for(int j=2;j<v[x].size();j++) ans[cnt++]=v[x][j] ;
        if(solve1(s1))
        {
            ans[cnt++]=x ;
            if(solve2(s2,0)) return 2 ;
        }
        cnt=tmp ;
    }
    return 0 ;
}
int solve1(int x)
{
    if(v[x].size()>=2 && !v[v[x][0]].empty()
        && !v[v[x][1]].empty()) return 0 ;
 
    ans[cnt++]=x ;
    if(v[x].empty()) return 1 ;
    if(!v[v[x][0]].empty())
    {
        if(!solve2(v[x][0],0)) return 0 ;
        for(int i=1;i<v[x].size();i++) ans[cnt++]=v[x][i] ;
    }
    else for(int i=0;i<v[x].size();i++) ans[cnt++]=v[x][i] ;
    return 2 ;
}
 
main()
{
    int n ; scanf("%d",&n) ;
    for(int i=1;i<n;i++)
    {
        int x,y ; scanf("%d%d",&x,&y) ;
        v[x].push_back(y) ;
        v[y].push_back(x) ;
    }
 
    fa[1]=1 ; dfs0(1) ;
    for(int x=n;x!=1;x=fa[x]) nex[fa[x]]=x , del(fa[x],x) ;
    for(int x=2;x<=n;x++) del(x,fa[x]) ;
 
    for(int i=1;i<=n;i++) sort(v[i].begin(),v[i].end(),cmp) ;
 
    int last=2 ;
    for(int i=1;i!=n;i=nex[i])
    {
        if(last==1 && !v[i].empty()) last=solve2(i,1) ;
        else last=solve1(i) ;
        if(!last) { printf("BRAK\n") ; return 0 ; }
    }
    if(!v[n].empty() && last==2) { printf("BRAK\n") ; return 0 ; }
    if(v[n].empty()) ans[cnt++]=n ;
    else if(!solve2(n,0)) { printf("BRAK\n") ; return 0 ; }
    for(int i=0;i<n;i++) printf("%d\n",ans[i]) ;
}
 

[TOJ 219] 円円想要快點去玩!!( 在線求區間逆序數對數 )

作法:

基本上和這篇一模一樣。TIOJ 上的那題沒有強制在線,所以可以用莫隊作。而且 TIOJ 還有保證數字是介於 1 ~ n 之間的兩兩相異的數,這裡則沒有,所以在一些步驟上會麻煩許多。以下先解釋 TIOJ 那個版本的作法。

一樣是把整個序列分成好幾塊,每塊的長度都是 x ( 最後一塊除外 ),以下記第 i 塊為 A_i,還有原始的序列為 a[ i ] 。我們先看要如何求出一個詢問的答案,再來決定應該要預處理好甚麼東西。

假設現在在詢問的左界和右界分別落在第 i 塊和第 j 塊中,首先我們求出第 i 塊到第 j 塊這一整條的逆序數對數有多少,然後再扣掉不該算的。把要算的東西分成落在同一塊內和落在不同塊內,如果是落在同一塊內的,那麼就可以預處理好每一塊裡面的逆序數對數有多少,但從第 i 塊一直加到第 j 塊太慢了,所以要預處理的應該是每塊裡面的逆序數對數的前綴和。至於落在不同塊內的,如果先算好對於任意兩塊,有多少逆序數對是由他們兩個產生的,那麼在算答案時需要的就是某種這個陣列的二維前綴和,所以預處理好的東西應該也是某種二維前綴和,這樣才可以 O( 1 ) 得到他的個數。

再來是要扣掉不該算的,圖中的兩塊紅色區塊代表不在詢問範圍內的數字。首先對每個落在左邊紅色區塊的數,扣掉「在他右邊且和他同塊且值小於他」的個數,對每個落在右邊紅色區塊的數則是扣掉「在他左邊且和他同塊且值大於他」的個數。再來對於每個在左邊紅色區塊的數,還要扣掉「在 i + 1 ~ j 塊中和他形成逆序數對的數的個數」,在右邊紅色區塊的則是扣掉「在 i ~ j - 1 塊中和他形成逆序數對的數的個數」。而一塊一塊加也太慢了,所以預處理好的應該是前綴和陣列。

但這樣會多扣掉一些東西,也就是一個數落在左邊紅色區塊,一個數落在右邊紅色區塊的逆序數對數,必須把它們加回來。而我們會用 merge sort 算一塊裡的逆序數對數,所以可以順便得到每一塊排序之後的結果,這樣就可以用 O( 區間長度 ) 的時間把兩邊都 sort 好了,詳細作法應該是參考 code 會比較好理解。最後再對它雙指標即可。

綜合以上,我們會需要預處理以下這些值:

1. x[ i ] : sigma ( j = 1 ~ i ) ( 第 j 塊內的逆序數對數 )

2. y[ i ][ j ] : sigma ( p = 1 ~ i ) sigma ( q = p+1 ~ j )  INV( p , q ) ,其中 INV( p , q ) 代表A_p 和 A_q 之間形成的逆序數對數。

3. z[ i ][ j ] : sigma ( p = 1 ~ j ) ( 第 i 個數對第 p 塊產生的逆序數對數 )
( 如果 i 在第 p 塊的話那那一項就是 0 ,但其實不會影響結果,因為之後查詢是查詢前綴和相減。 )

4. u[ i ] : 在 i 左邊且和 i 同塊且值小於 a[ i ] 的個數

5. v[ i ] : 在 i 右邊且和 i 同塊且值大於 a[ i ] 的個數

x 陣列的算法就是直接 merge sort ,並且分開記錄原始的 a 陣列和對每塊排序過後的新陣列,之後會用到。 y 的算法則是對兩塊排序好的陣列雙指標就好了,最後再把前綴和處理起來。 z 的算法比較神奇,作法是先枚舉每一塊,然後對於每個值 p ,去看看這一塊裡有幾個數大於 p 和小於 p ,有了這個資訊之後去看看值為 p 的位置,假設 a[ q ] = p 好了,就可以在「 q 對這一塊產生的逆序數對數」 的值加上這一塊所貢獻的值,最後再處理前綴和就好。至於 u[ i ] 和 v[ i ] 就是簡單的掃過去而已。

到這裡就成功解決了 TIOJ 版的問題 ( 建議先讀懂上面那個網站的 code 再繼續往下看 ),回到 TOJ 的,首先要先把數字離散化,並且在算 z 陣列的值的時候,會需要一個數字出現在哪些位置,所以需要用 n 個 vector 記錄每個數字分別出現在哪些位置。再來則是最後在 O( L ) sort 的部分,因為這時候一個數字可能有很多個位置,所以如果按照原本的方法只標記數值為 1 或是 2 的話會分不清楚,只好改成標記位置。我們需要知道到底應該要標記哪個位置的數,所以只好在 merge sort 的地方多維護一個 id 值,這樣才能知道在 sort 完的陣列裡面的每個數原本的 index 是多少,那麼取這個陣列的反函數就可以得到應該要標記哪個位置了。

code :

#include<bits/stdc++.h>
using namespace std;
const int maxn=30000+10,maxm=450 ;
 
int n,K,num ;
int a[maxn],s[maxn] ;
vector<int> pos[maxn],vec ;
 
struct P{int id,val;}a2[maxn],tmp[maxn] ;
int per[maxn] ;
 
void cal()
{
     sort(vec.begin(),vec.end()) ;
     vec.resize(unique(vec.begin(),vec.end())-vec.begin()) ;
     for(int i=1;i<=n;i++)
          a[i]= upper_bound(vec.begin(),vec.end(),a[i])-vec.begin() ,
          pos[a[i]].push_back(i) ,
          a2[i]=(P){i,a[i]} ;
}
 
int inv_cnt ;
void merge(int l,int r)
{
     if(l==r) return ;
     int mid=(l+r)/2 ;
     merge(l,mid) ; merge(mid+1,r) ;
     for(int i=l,j=mid+1,cnt=l ; i<=mid || j<=r ; )
     {
          if(j==r+1 || (i!=mid+1 && a2[i].val<=a2[j].val))
               tmp[cnt++]=a2[i++] ;
          else tmp[cnt++]=a2[j++] , inv_cnt+=mid+1-i ;
     }
 
     for(int i=l;i<=r;i++) a2[i]=tmp[i] ;
}
 
inline void get(int t,int &st,int &ed,int &id)
{
     id= (t-1)/K+1 ;
     st= K*(id-1)+1 ;
     ed= min(K*id,n) ;
}
 
int x[maxn],y[maxm][maxm],z[maxn][maxm] ;
int u[maxn],v[maxn] ;
 
int type[maxn] ;
int tmpl[maxn],tmpr[maxn] ;
 
main()
{
     scanf("%d",&n) ;
     for(int i=1;i<=n;i++) scanf("%d",&a[i]) , vec.push_back(a[i]) ;
     cal() ;
     int Q ; scanf("%d",&Q) ;
 
     K= (int)(n/sqrt(Q+0.5)) ;
     num= (n%K==0 ? n/K : n/K+1) ;
 
     for(int i=1;i<=num;i++)
     {
          int st=K*(i-1)+1 , ed=min(K*i,n) ;
          inv_cnt=0 ;
          merge(st,ed) ;
          for(int j=st;j<=ed;j++) s[j]=a2[j].val , per[a2[j].id]=j ;
          x[i]=x[i-1]+inv_cnt ;
     }
 
     for(int i=1;i<=num;i++) for(int j=i+1;j<=num;j++)
     {
          int cnt=0 , ed=min(j*K,n) ;
          for(int i2=(i-1)*K+1 , j2=(j-1)*K ; i2<=i*K ; i2++)
          {
               while(j2<ed && s[j2+1]<s[i2]) j2++ ;
               cnt+= j2-(j-1)*K ;
          }
          y[i][j]=y[i][j-1]+cnt ;
     }
     for(int i=1;i<=num;i++) for(int j=i+1;j<=num;j++)
          y[i][j]+=y[i-1][j] ;
 
     for(int i=1;i<=num;i++)
     {
          int now1=K*(i-1) , now2=now1+1 , ed=min(n,K*i) ;
          for(int j=1;j<=vec.size();j++)
          {
               while(now1<ed && s[now1+1]<j) now1++ ;
               while(now2<=ed && s[now2]<=j) now2++ ;
               for(auto k : pos[j])
               {
                    if(k<=ed && k> K*(i-1)) continue ;
                    if(k>ed) z[k][i]+=(ed-now2+1) ;
                    else z[k][i]+=(now1-K*(i-1)) ;
               }
          }
     }
     for(int i=1;i<=n;i++) for(int j=1;j<=num;j++)
          z[i][j]+=z[i][j-1] ;
 
     for(int i=1;i<=num;i++)
     {
          int st=K*(i-1)+1 , ed=min(n,K*i) ;
          for(int j=st;j<=ed;j++)
          {
               for(int k=st;k<j;k++) if(a[k]>a[j])
                    u[j]++ ;
               for(int k=j+1;k<=ed;k++) if(a[k]<a[j])
                    v[j]++ ;
          }
     }
 
     int L,R , ans ;
     for(int q0=1;q0<=Q;q0++)
     {
          if(q0==1) scanf("%d%d",&L,&R) ;
          else
          {
               ans %= n ;
               L=(ans+2217+q0)%n+1 ;
               R=(ans*2217+q0)%n+1 ;
               if(L>R) swap(L,R) ;
          }
          if(L==R) { printf("%d\n",ans=0) ; continue ; }
 
          int stl,edl,str,edr,idl,idr ;
          get(L,stl,edl,idl) ;
          get(R,str,edr,idr) ;
 
          ans=0 ;
          for(int i=stl;i<=edl;i++) type[i]=0 ;
          for(int i=str;i<=edr;i++) type[i]=0 ;
 
          ans+= x[idr]-x[idl-1] ;
          ans+= y[idr-1][idr]-y[idl-1][idr] ;
          for(int i=stl;i<L;i++)
               ans-=(z[i][idr]-z[i][idl]) ,
               ans-=v[i] , type[per[i]]=1 ;
          for(int i=R+1;i<=edr;i++)
               ans-=(z[i][idr-1]-z[i][idl-1]) ,
               ans-=u[i] , type[per[i]]=2 ;
 
          int nl=0 , nr=0 ;
          for(int i=stl;i<=edl;i++) if(type[i]==1)
               tmpl[nl++]=s[i] ;
          for(int i=str;i<=edr;i++) if(type[i]==2)
               tmpr[nr++]=s[i] ;
          for(int i=0 , j=-1;i<nl;i++)
          {
               while(j+1<nr && tmpr[j+1]<tmpl[i]) j++ ;
               ans+=j+1 ;
          }
          printf("%d\n",ans) ;
     }
}