升级版AHOI/HNOI 2017礼物
题目大意
有两个排数$A,B$,每一排都有小于$m$的$n$个数,你可以任意的对某一排整体$+1$,然后找到一个排列$P$,记$d(x,y)=min(|x-y|^2,(m-|x-y|)^2)$使得$sumlimits_{i=1}^{n}d(A_i,B_{P_i})$最小。
题解
把两排数放在两个相同数轴上,其中实心的格子表示这个位置有数。整体加一就可以变成将这个轴向右移,再将最右侧的格子补到左边。考虑一件事情,由于对于确定的$A_i,B_{P_i}$,在轴之间连一条线,若它们的值的计算方式不是以$|x-y|^2$计算,则需要跨越左右边界。
如果这样画图,那么在最优解中,线一定不会交叉。若存在两条交叉的线,交换他们的匹配,结果一定会更优。这意味着当我们确定$A_i$匹配$B_i+k$时,对于任意的$A_j$,一定匹配$B_{j+k}$(当$j+k>n$时匹配$B_{j+k-n}$)。也就是说,对于$B_i$若连向它的线是跨过边界的,那么对于所有这样$B_i$一定恰好是$B$的前缀。到这里,我们应该想到将下方的那排数倍长($B_{i+n}=B_i+m$)。
这样做还有一个好处,我们可以简化整体$+1$的操作。由于对于两个数组都进行一次$+1$的操作是没有意义的,不妨只对$A$进行操作。由于倍长了$B$,我们甚至可以不用取模。因为最多有意义的$+1$次数不超过$m$,而且每一种匹配的方式都可以在倍长$B$之后体现为$A_i ightarrow B_{i+k}$,即上图中出现了跨越边界可以视为在倍长的$B$数组中匹配。
然后问题就变成了求$minsumlimits_{i=1}^{n}(A_i+d-B_{i+k})^2$。其中$d$表示对$A$进行$+1$的次数,$k$表示匹配的编号错位的量。
你很容易发现$0leq k<n$,然后我们就考虑枚举$k$,就有
$$Ans=sumlimits_{i=1}^{n}(A_i+d-B_{i+k})^2$$
$$=sumlimits_{i=1}^{n}A_i^2+d^2-B_{i+k}^2+2(A_i-B_{i+k})d-2A_icdot B_{i+k}$$
$$Ans=t1+t2+t3$$
$$t1=sumlimits_{i=1}^{n}A_i^2-B_{i+k}^2$$
$$t2=nd^2+(2sumlimits_{i=1}^{n}A_i-B_{i+k})d$$
$$t3=sumlimits_{i=1}^{n}-2A_icdot B_{i+k}$$
不难发现$t1$是我们可以预处理出来的,$t3$是一个减法意义下的卷积,可以通过将一个数组反转再用$FFT$做加法卷积预处理,而$t2$只有一个未知数$d$,而这恰好又是关于$d$的二次函数$n>0$决定了函数有最小值,所以我们可以通过计算二次函数对称轴的方式来求得$d$(注意$d$必须要临近取整,因为$+1$操作只能进行整数次,但是可以为负数,因为这表示相对的对$B$进行操作),求得$d$后就直接计算就可以了。对于每一个枚举的$k$计算答案,最后取$min$即可。
复杂度为$O(T(n+nlog n))$。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define LL long long #define M 262170 using namespace std; int read(){ int nm=0,fh=1; char cw=getchar(); for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh; for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0'); return nm*fh; } const double PI=acos(-1); struct comp{ double r,k; comp(){} comp(double _r,double _k){r=_r,k=_k;} comp operator *(const comp&ot)const{return comp(r*ot.r-k*ot.k,r*ot.k+k*ot.r);} comp operator +(const comp&ot)const{return comp(r+ot.r,k+ot.k);} comp operator -(const comp&ot)const{return comp(r-ot.r,k-ot.k);} }A[M],B[M],C[M]; LL n,m,sumx[M],sumy[M],dt,X[M],Y[M],sx[M],sy[M],od[M],len,ans,nw; void FFT(comp *x,double kd){ for(int i=1;i<len;i++) if(i<od[i]) swap(x[i],x[od[i]]); for(int tt=1;tt<len;tt<<=1){ comp unit(cos(PI*kd/(tt*1.0)),sin(PI*kd/(tt*1.0))); for(int st=0;st<len;st+=(tt<<1)){ comp now(1.0,0.0); for(int pos=st;pos<st+tt;pos++,now=now*unit){ comp t1=x[pos],t2=x[pos+tt]*now; x[pos]=t1+t2,x[pos+tt]=t1-t2; } } } if(kd<0.0){for(int i=0;i<len;i++) x[i].r/=(len*1.0);} } int main(){ for(int Cs=read();Cs;Cs--){ m=read(),n=read(),memset(&ans,0x3f,sizeof(LL)); for(len=1,nw=-1;len<n*3;len<<=1,nw++); for(int i=0;i<len;i++) A[i]=B[i]=comp(0.0,0.0),od[i]=((od[i>>1]>>1)|((i&1)<<nw)); for(int i=1;i<=n;i++) X[i]=read(),sumx[i]=sumx[i-1]+X[i]; for(int i=1;i<=n;i++) Y[i]=read(),Y[i+n]=Y[i]+m,sumy[i]=sumy[i-1]+Y[i]; for(int i=1;i<=n;i++){ sumy[i+n]=sumy[i+n-1]+Y[i+n]; sx[i]=sx[i-1]+X[i]*X[i],sy[i]=sy[i-1]+Y[i]*Y[i]; } for(int i=n+1;i<=(n<<1);i++) sy[i]=sy[i-1]+Y[i]*Y[i]; for(int i=0;i<n;i++) A[i]=comp(X[i+1]*1.0,0.0); for(int i=0;i<(n<<1);i++) B[i]=comp(Y[(n<<1)-i]*1.0,0.0); FFT(A,1.0),FFT(B,1.0); for(int i=0;i<len;i++) C[i]=A[i]*B[i]; FFT(C,-1.0); for(int k=0;k<n;k++){ LL now=((LL)(C[(n<<1)-1-k].r+0.5)); now<<=1; LL dt=(LL)round((((sumy[n+k]-sumy[k])-sumx[n])*1.0)/(n*1.0)); LL tk=n*dt*dt+2*dt*(sumx[n]-(sumy[n+k]-sumy[k])); ans=min(ans,sx[n]+sy[n+k]-sy[k]+tk-now); } printf("%lld ",ans); } return 0; }