【题解】The 2021 ICPC Asia Macau Regional Contest - E Pass The Ball

问题描述

image

解释

  • 相当于给定一个置换群,求 \(\sum\limits_{i=1}^{n}i*b_{i}\)

题目分析

  • 本题这种传球的关系显然是存在循环节的,先考虑一个大小为\(m\)的环,显然我们可以用\(m^2\)的时间来计算出来全部的\(m\)种答案,这样对于任意一个操作数\(k\),就可以用\(k \% m\)来得到,但是这显然是不行的,\(10^{10}\)显然会tle。

  • 然后我们考虑简化一下问题(对于时间复杂度没有简化),对于样例,我们可以构造这样的循环结构(置换群)

    3 4 2 1
    1 2 4 3 1 2 4 3

  • 我们发现,现在中心对称交叉相乘,得到的就是初始状态的答案,即为3*3+4*4+2*2+1*1=30

  • 之后进行第一次交换,相当于把上面的整体向后移一位,之后还是交叉相乘,得到的结果即为第一次交换之后的结果3*1+4*3+2*4+1*2=25
    3 4 2 1
    1 2 4 3 1 2 4 3

  • 同理,之后的每次操作即为每次把上面的数组向后移动一位,然后求交叉相乘

  • 交叉相乘,有没有想到什么呢? 没错! FFT(NTT) !

  • 我们把初始状态的每一位看作多项式从低次到高次的每一项,那么我们所求的0次操作的结果就相当于是卷积相乘之后,\(x^3\)这一项的系数,而操作一次之后,我们想要的结果就是卷积相乘之后的\(x^4\)这一项的系数,后续操作也一样,也就是我们用一次卷积操作,就求出了所有\(k\)次操作之后的结果!

  • 那么数列如何构造呢?也非常简单,就把1号放在第一位,然后我们看把p[1]放在第二位,之后把p[p[1]]放在第三位,由此递归下去,这刚好也符合我们递归找环的过程,于是我们可以在递归找环的时候顺便把刚刚的数列构造出来,接下来套入NTT,就可以求解得到多次操作的结果

  • 那么如何计算结果对于答案的贡献呢?

  • 我们可以用一个vector来存储不同大小的环的答案总数,因为所有的环总长度加起来也不会超过\(10^5\),然后我们记录每种长度的环,对于一个大小为\(cnt\)的环,我们记录cnt个操作次数之后对于答案的贡献(因为大小为\(cnt\)的环,循环节一定是\(cnt\))。如果\(cnt\)之前已经出现过,那么我们就直接把新的\(cnt\)答案分别对应着加入到原本的\(cnt\)中,还需要开一个数组记录出现过环的大小的种类数,因为环长度的种类数一定不会超过\(\sqrt{n}\),所以复杂度\(O(\sqrt{n}*q)\)

代码

#include<bits/stdc++.h>
#define re register
#define ll long long
#define inc(i,j,k) for(re int i=j;i<=k;i++)
#define dec(i,j,k) for(re int i=j;i>=k;i--)
using namespace std;
const int maxn = 1e6+10;
const ll P = 4179340454199820289, G = 3, Inv_G = 1393113484733273430;
inline int read(){
	re int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
	while(ch>='0'&&ch<='9') {x=x*10+(ch^48); ch=getchar();}
	return x*f;
}
int n,q;
int p[maxn];
int r[maxn];
bool vis[maxn];
int limit=1,L;
ll qpow(ll a,ll b){
	ll base=1;
	while(b){
		if(b&1) base=(__int128)base*a%P;
		a=(__int128)a*a%P;
		b>>=1;
	}
	return base%P;
}
void NTT(ll *A,int type)
{
    for(int i=0;i<limit;i++) 
        if(i<r[i]) swap(A[i],A[r[i]]);
    for(int mid=1;mid<limit;mid<<=1)
    {
    	ll Wn=qpow( (type==1) ? G:Inv_G , (P-1)/(mid<<1) );
        for(int R=mid<<1,j=0;j<limit;j+=R)
        {
            ll w=1;
            for(int k=0;k<mid;k++,w=(__int128)w*Wn%P)
            {
                ll x=A[j+k],y=(__int128)w*A[j+mid+k]%P;
                A[j+k]=(x+y)%P;
                A[j+mid+k]=(x-y+P)%P;
            }
        }
    }
}
ll a[maxn],b[maxn];

int exist[maxn],cnte=0;
vector <ll> c[maxn];
int main(){
	n=read(); q=read();
	inc(i,1,n){
		p[i]=read();
	}
	inc(zzt,1,n){
		if(vis[zzt]) continue;
		int cnt = 0;
		for(int j=zzt;!vis[j];j=p[j]){
			vis[j]=1;
			a[cnt] = b[cnt] = j;
			cnt++;
		}
		int N=cnt; int M=N<<1;
		limit=1; L=0;
		reverse(b,b+N);
		inc(i,0,N-1){
			b[i+N]=b[i];
		}
		while(limit<=N+M){
			limit<<=1;
			L++;
		}
		inc(i,0,limit-1){
			r[i]=(r[i>>1]>>1) | ((i&1)<<(L-1));
		}
		inc(i,N,limit){
			a[i]=0;
		}
		inc(i,M,limit){
			b[i]=0;
		}
		NTT(a,1); NTT(b,1);
		inc(i,0,limit-1){
			a[i]=(__int128)a[i]*b[i]%P;
		}
		NTT(a,-1);
		ll inv_limit = qpow(limit,P-2);
		inc(i,0,limit-1){
			a[i] = (__int128) a[i] * inv_limit % P;
		}
		if(!c[cnt].size()){
			exist[++cnte] = cnt;
			inc(i,cnt-1,cnt+cnt-2){
				c[cnt].push_back(a[i]);
			}
		}else{
			int tmpp=cnt-1;
			inc(i,0,cnt-1){
				c[cnt][i]+=a[tmpp];
				c[cnt][i]%=P;
				tmpp++;
			}
		}
	}
	inc(i,1,q){
		ll ans = 0;
		int opt = read();
		inc(j,1,cnte){
			re int op = opt%exist[j];
			ans += c[exist[j]][op];
		}
		printf("%lld\n",ans);
	}
}

posted @ 2022-10-23 19:23  ZzTzZ  阅读(610)  评论(0)    收藏  举报