[BJOI2020] 主音识别

本文介绍了一种使用快速傅立叶变换(FFT)优化的算法,解决了一个关于求解最小g(x)的问题。该问题涉及到集合和数列的模运算,并通过建立多项式进行卷积计算来找到最优解。

咕咕咕的北京省选题

description

给定参数kkk,一个大小为mmm的集合{b1,b2,b3...bm}\{b_1,b_2,b_3...b_m\}{b1,b2,b3...bm},一个长度为nnn的数列{ai}\{a_i\}{ai}

定义f(x,ai)=[ai≡x( mod k)]+[∃1≤j≤m,ai≡x+bj( mod k)]f(x,a_i)=[a_i\equiv x(\bmod k)]+[\exist1\leq j\leq m,a_i\equiv x+b_j(\bmod k)]f(x,ai)=[aix(modk)]+[1jm,aix+bj(modk)]

通俗来讲:
xxxaia_iaikkk 同余,则f(x,ai)f(x,a_i)f(x,ai)222
否则,若 BBB 中存在一个元素 bjb_jbj ,使得 x+bjx+b_jx+bjaia_iaikkk 同余,则f(x,ai)f(x,a_i)f(x,ai)111
否则,f(x,ai)f(x,a_i)f(x,ai)为 0。

定义g(x)=∑i=1nf(x,ai)g(x)=\sum_{i=1}^n f(x,a_i)g(x)=i=1nf(x,ai)

求在[0,k)[0,k)[0,k)上使得g(x)g(x)g(x)取得最小值的xxx,如有多解输出多个

1≤m≤k≤1.2×105,1≤n≤1.2×105,0=b1<b2<b3⋯<bm<k,0≤ai≤1.2×1051\leq m\leq k\leq 1.2\times 10^5,1\leq n\leq 1.2\times 10^5,0=b_1<b_2<b_3\cdots<b_m<k,0\leq a_i\leq 1.2\times 10^51mk1.2×105,1n1.2×105,0=b1<b2<b3<bm<k,0ai1.2×105

solution

首先可以想到707070分的暴力做法

对于每个aia_iai模上kkk,开一个桶去维护

枚举xxxO(m)O(m)O(m)计算答案为cnt[i]+∑j=1mcnt[bj+i]cnt[i]+\sum_{j=1}^m cnt[b_j+i]cnt[i]+j=1mcnt[bj+i]

复杂度O(km)O(km)O(km)

考虑优化,枚举的复杂度基本无法优化,考虑计算答案的优化

观察式子

ai≡x+bj( mod k)ai−bj+k≡x( mod k) \begin{aligned} a_i&\equiv x+b_j(\bmod k)\\ a_i-b_j+k&\equiv x(\bmod k) \end{aligned} aiaibj+kx+bj(modk)x(modk)

我们考虑建立两个多项式A(x),B(x)A(x),B(x)A(x),B(x)

其中

A(x)=∑i=0k−1cntaixiB(x)=2+∑i=1k−1cntbk−ixiA(x)=\sum_{i=0}^{k-1}cnta_ix^i \\ B(x)=2+\sum_{i=1}^{k-1}cntb_{k-i}x^i A(x)=i=0k1cntaixiB(x)=2+i=1k1cntbkixi

那么当我们枚举到xxx的时候,其实就是A(x),B(x)A(x),B(x)A(x),B(x)的卷积的xxx次项系数和x+kx+kx+k次项系数之和

注意忽略掉b1=0b_1=0b1=0的情况,要不然会出问题

利用FFT\text{FFT}FFT优化即可

复杂度O(klog⁡k)O(k\log k)O(klogk)

#include <bits/stdc++.h>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)

typedef long long ll;

const int N=1e6+5;
const double pi=acos(-1.0);

template<typename T> void read(T &x){
   x=0;int f=1;
   char c=getchar();
   for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
   for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

int k,m,n;
int A[N],B[N],C[N];
int pos[N];
int cnt[N];
int ans,out[N];

struct cp{
	double real,imag;
	cp(double a=0.0,double b=0.0){real=a,imag=b;}	
}a[N],b[N],c[N];

cp operator + (cp a,cp b){return cp(a.real+b.real,a.imag+b.imag);}
cp operator - (cp a,cp b){return cp(a.real-b.real,a.imag-b.imag);}
cp operator * (cp a,cp b){return cp(a.real*b.real-a.imag*b.imag,a.real*b.imag+a.imag*b.real);}

void fft(int limit,cp *a,int opt){
	for(int i=0;i<limit;i++)c[i]=a[pos[i]];
	for(int i=0;i<limit;i++)a[i]=c[i];
	for(int i=1;i<limit;i<<=1){
		int len=i<<1;
		for(int j=0;j<limit;j+=len){
			cp z0(cos(pi/i),opt*sin(pi/i)),z(1.0,0.0);
			for(int k=0;k<i;k++,z=z*z0){
				cp x=a[j+k],y=z*a[j+k+i];
				c[j+k]=x+y;
				c[j+k+i]=x-y;	
			}
		}
		for(int j=0;j<limit;j++)a[j]=c[j];
	}
}

int main()
{
	freopen("tonic.in","r",stdin);
	freopen("tonic.out","w",stdout);
	read(k),read(m),read(n);
	Rep(i,1,m)read(B[i]),b[k-B[i]].real++;
	Rep(i,1,n)read(A[i]),A[i]%=k,a[A[i]].real++;
	b[0].real=2,b[k].real=0;
	int limit=1;
	while(limit<=2*k)limit<<=1;
	for(int i=1;i<limit;i<<=1)for(int j=0;j<i;j++)pos[i+j]=pos[j]+limit/i/2;
	fft(limit,a,1),fft(limit,b,1);
	for(int i=0;i<limit;i++)a[i]=a[i]*b[i];
	fft(limit,a,-1);
	for(int i=0;i<limit;i++)C[i]=(int)(a[i].real/limit+0.5);
	for(int i=0;i<k;i++){
		int tot=C[i]+C[i+k];
		if(tot>ans){
			ans=tot;
			out[0]=0;
			out[++out[0]]=i;
		}
		else if(tot==ans)out[++out[0]]=i;
	}
	printf("%d\n",out[0]);
	Rep(i,1,out[0])printf("%d ",out[i]);
	puts("");
	return 0;
}
/*
12 7 8
0 2 4 5 7 9 11
1 3 5 6 8 10 12 1
*/
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值