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

问题描述

image

解释

  • 相当于给定一个置换群,求 i=1nibi

题目分析

  • 本题这种传球的关系显然是存在循环节的,先考虑一个大小为m的环,显然我们可以用m2的时间来计算出来全部的m种答案,这样对于任意一个操作数k,就可以用k%m来得到,但是这显然是不行的,1010显然会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次操作的结果就相当于是卷积相乘之后,x3这一项的系数,而操作一次之后,我们想要的结果就是卷积相乘之后的x4这一项的系数,后续操作也一样,也就是我们用一次卷积操作,就求出了所有k次操作之后的结果!

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

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

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

代码

#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 @   ZzTzZ  阅读(552)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示