【题解】The 2021 ICPC Asia Macau Regional Contest - E Pass The Ball
问题描述
解释
- 相当于给定一个置换群,求
题目分析
-
本题这种传球的关系显然是存在循环节的,先考虑一个大小为的环,显然我们可以用的时间来计算出来全部的种答案,这样对于任意一个操作数,就可以用来得到,但是这显然是不行的,显然会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次操作的结果就相当于是卷积相乘之后,这一项的系数,而操作一次之后,我们想要的结果就是卷积相乘之后的这一项的系数,后续操作也一样,也就是我们用一次卷积操作,就求出了所有次操作之后的结果!
-
那么数列如何构造呢?也非常简单,就把1号放在第一位,然后我们看把p[1]放在第二位,之后把p[p[1]]放在第三位,由此递归下去,这刚好也符合我们递归找环的过程,于是我们可以在递归找环的时候顺便把刚刚的数列构造出来,接下来套入NTT,就可以求解得到多次操作的结果
-
那么如何计算结果对于答案的贡献呢?
-
我们可以用一个vector来存储不同大小的环的答案总数,因为所有的环总长度加起来也不会超过,然后我们记录每种长度的环,对于一个大小为的环,我们记录cnt个操作次数之后对于答案的贡献(因为大小为的环,循环节一定是)。如果之前已经出现过,那么我们就直接把新的答案分别对应着加入到原本的中,还需要开一个数组记录出现过环的大小的种类数,因为环长度的种类数一定不会超过,所以复杂度
代码
#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);
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库