LOJ#2020. 「AHOI / HNOI2017」礼物-FFT

说在前面

me的代码警告怕是假的
本地AC提交WA
最后发现是有个地方没有赋初值?还是me开了O2才发现了这条警告…
-Wuninitialized 和 -Wmaybe-uninitialized 要你们有何用!!


题目

LOJ#2020传送门
看题可戳传送门


解法

由题,因为可以对 a a b串加一个数,所以可以看作是 b b 不变,而a随意变化

然后,假设我们已经确定了位置匹配,那么差异值就应该是 ni=1(x+aibpi)2 ∑ i = 1 n ( x + a i − b p i ) 2
其中 pi=((i+k1)modN)+1   (k[0,N1]) p i = ( ( i + k − 1 ) mod N ) + 1       ( k ∈ [ 0 , N − 1 ] ) x x a串的变化量,记 δi=aibpi δ i = a i − b p i

这个差异值是一堆二次函数相加,那么加起来还是一个二次函数,这个二次函数长这样: nx2+2xδi+(δi2) n x 2 + 2 x ∑ δ i + ( ∑ δ i 2 )

可以发现, δ ∑ δ 是固定的,它的值就是 aibi ∑ a i − ∑ b i ,所以 x x 的最优位置就确定了,当x=δn时,上式取最小值

然后唯一的不确定因素就是 (δi2) ( ∑ δ i 2 ) ,我们把它拆开,得到 ai22aibpi+bpi2 ∑ a i 2 − 2 a i b p i + b p i 2 。于是当 aibpi ∑ a i b p i 取到最大值的时候,差异也就最小了,这也就是答案

于是求 aibpi ∑ a i b p i 的最大值,显然可以把一个串反转,然后把另一个串复制一遍接在后面,就是一个卷积形式
比如样例:
1 2 3 4 5 1 2 3 4 5
5 4 3 3 6 0 0 0 0 0
直接跑一遍FFT即可


下面是代码

me用了一些myy论文里提到的技巧……
所以有些地方可能会比较奇怪

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

const double PI2 = 3.1415926535897932384626 * 2 ;
int N , l0[50005] , l1[50005] ;
int expos[270000] , len ;
typedef struct Complex cpx ;
struct Complex{
    double r , i ;
    Complex( double r_ = 0 , double i_ = 0 ): r(r_) , i(i_) {} ;
    cpx conj() { return cpx( r , -i ) ; }
    cpx operator + ( const cpx &A ) const { return cpx( r + A.r , i + A.i ) ; }
    cpx operator - ( const cpx &A ) const { return cpx( r - A.r , i - A.i ) ; }
    cpx operator * ( const cpx &A ) const { return cpx( r * A.r - i * A.i , r * A.i + i * A.r ) ; }
    cpx operator * ( double p ) const { return cpx( r * p , i * p ) ; }
    cpx operator / ( double p ) const { return cpx( r / p , i / p ) ; }
} a[270000] , b[270000] , nil , _one = cpx( 1 , 0 ) ;

void preWork(){
    for( len = 1 ; len <= N * 2 ; len <<= 1 ) ;
    for( int i = 1 , now = 0 , t ; i < len ; i ++ ){
        t = len >> 1 ;
        while( now&t ) now ^= t , t >>= 1 ; now ^= t ;
        expos[i] = now ;
    }
}

void FFT( cpx *a , int len , int rev ){
    for( int i = 1 ; i < len ; i ++ )
        if( expos[i] > i ) swap( a[ expos[i] ] , a[i] ) ;
    cpx wid , w , t0 , t1 ;
    for( int i = 2 ; i <= len ; i <<= 1 ){
        wid = cpx( cos( PI2 / i ) , sin( PI2 * rev / i ) ) ;
        for( int j = 0 ; j < len ; j += i ){
            w = _one ;
            for( int k = j ; k < j + ( i >> 1 ) ; k ++ ){
                t0 = a[k] , t1 = a[k+(i>>1)] * w ;
                a[k] = t0 + t1 ;
                a[k+(i>>1)] = t0 - t1 ;
                w = w * wid ;
            }
        }
    } if( rev == -1 ) for( int i = 0 ; i < len ; i ++ )
        a[i] = a[i] / len ;
}

void solve(){
    for( int i = 1 ; i <= N ; i ++ ) a[i].r = a[i+N].r = l0[i] ;
    for( int i = 1 ; i <= N ; i ++ ) a[i].i = l1[N-i+1] ;
    FFT( a , len , 1 ) ; b[0] = a[0].conj() ;
    for( int i = 1 ; i < len ; i ++ ) b[i] = a[len-i].conj() ;
    cpx t0 , t1 ;
    for( int i = 0 ; i < len ; i ++ ){
        t0 = a[i] , t1 = b[i] ;
        a[i] = ( t0 + t1 ) / 2 ;
        b[i] = cpx( 0 , -1 ) * ( t0 - t1 ) / 2 ;
        a[i] = a[i] * b[i] ;
    } FFT( a , len , -1 ) ;

    int max_pos = N + 1 ;
    for( int i = 1 ; i <= N ; i ++ )
        if( a[i+N].r > a[ max_pos ].r ) max_pos = i + N ;
    max_pos -= N + 1 ;

    long long delta = 0 , ans = 0 , tmp , x ;
    for( int i = 1 ; i <= N ; i ++ ){
        tmp = l0[ (max_pos+i-1)%N+1 ] - l1[i] ;
        delta += tmp ; ans += tmp * tmp ;
    } if( delta > 0 ) x = -1.0 * delta / N - 0.5 ;
      else x = -1.0 * delta / N + 0.5 ;
    printf( "%lld" , x * x * N + 2LL * delta * x + ans ) ;
}

inline int read_(){
    int rt = 0 ;
    register char ch = getchar() ;
    while( ch < '0' || ch > '9' ) ch = getchar() ;
    while( ch >='0' && ch <='9' ) rt = ( rt << 1 ) + ( rt << 3 ) + ch - '0' , ch = getchar() ;
    return rt ;
}

int main(){
    N = read_() ; read_() ;
    for( int i = 1 ; i <= N ; i ++ ) l0[i] = read_() ;
    for( int i = 1 ; i <= N ; i ++ ) l1[i] = read_() ;
    preWork() ; solve() ;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值