zoukankan      html  css  js  c++  java
  • [BZOJ 4827][Hnoi2017]礼物

    4827: [Hnoi2017]礼物

    Time Limit: 20 Sec  Memory Limit: 512 MB
    Submit: 1091  Solved: 748
    [Submit][Status][Discuss]

    Description

    我的室友最近喜欢上了一个可爱的小女生。马上就要到她的生日了,他决定买一对情侣手 环,一个留给自己,一
    个送给她。每个手环上各有 n 个装饰物,并且每个装饰物都有一定的亮度。但是在她生日的前一天,我的室友突
    然发现他好像拿错了一个手环,而且已经没时间去更换它了!他只能使用一种特殊的方法,将其中一个手环中所有
    装饰物的亮度增加一个相同的自然数 c(即非负整数)。并且由于这个手环是一个圆,可以以任意的角度旋转它,
    但是由于上面 装饰物的方向是固定的,所以手环不能翻转。需要在经过亮度改造和旋转之后,使得两个手环的差
    异值最小。在将两个手环旋转且装饰物对齐了之后,从对齐的某个位置开始逆时针方向对装饰物编号 1,2,…,n,
    其中 n 为每个手环的装饰物个数,第 1 个手环的 i 号位置装饰物亮度为 xi,第 2 个手 环的 i 号位置装饰物
    亮度为 yi,两个手环之间的差异值为(参见输入输出样例和样例解释): sum_{i=1}^{n}(x_i-y_i)^2麻烦你帮他
    计算一下,进行调整(亮度改造和旋转),使得两个手环之间的差异值最小, 这个最小值是多少呢?

    Input

    输入数据的第一行有两个数n, m,代表每条手环的装饰物的数量为n,每个装饰物的初始 亮度小于等于m。
    接下来两行,每行各有n个数,分别代表第一条手环和第二条手环上从某个位置开始逆时 针方向上各装饰物的亮度。
    1≤n≤50000, 1≤m≤100, 1≤ai≤m

    Output

    输出一个数,表示两个手环能产生的最小差异值。
    注意在将手环改造之后,装饰物的亮度 可以大于 m。

    Sample Input

    5 6
    1 2 3 4 5
    6 3 3 4 5

    Sample Output

    1
    【样例解释】
    需要将第一个手环的亮度增加1,第一个手环的亮度变为: 2 3 4 5 6 旋转一下第二个手环。对于该样例,是将第
    二个手环的亮度6 3 3 4 5向左循环移动 2017-04-15 第 6 页,共 6 页 一个位置,使得第二手环的最终的亮度为
    :3 3 4 5 6。 此时两个手环的亮度差异值为1。

    题解

    环上的东西我们按照套路倍长解决.

    然后我们来看那个表达式: $sumlimits_{i=1}^n(x_i-y_i)^2$...

    好像在哪见过? 是哪呢?

    卷积与字符串

    没错就是你! 字符串匹配!

    那么好了我们翻转其中一个串然后拆拆式子看看能得到什么:

    $$
    egin{aligned}
    c_k&=sum_{i=0}^k(a_i-b_{k-i})^2 \
    &=sum_{i=0}^ka_i^2-2a_ib_{k-i}+b_{k-i}^2 \
    &=sum_{i=0}^ka_i^2+sum_{i=0}^kb_{k-i}^2-2sum_{i=0}^ka_ib_{k-i}\
    &=sum_{i=0}^ka_i^2+sum_{i=0}^kb_i^2-2sum_{i=0}^ka_ib_{k-i}
    end{aligned}
    $$

    而$sumlimits_{i=0}^ka_ib_{k-i}$ 显然是个卷积, 剩下两个和式是前缀和可以预处理.

    那么我们只要有了 $a$ 和 $b$ , 我们就可以在 $O(nlog n)$ 时间内算出所有位置的差异值了...吗?

    然而并不是这样的.

    我们在字符串匹配问题中, 高位补 $0$ 相当于补的是通配符, 不会对答案作出贡献. 但这次 $0$ 可就是字符了, 如果按照上面的式子算的话会把前 $k$ 个字符全都对位计算贡献. ($(a-0)^2$可是有贡献的).

    我们设倍长并翻转的串是 $a$, 另一个串是 $b$, 那么实际上的式子应该是:

    $$
    egin{aligned}
    c_k&=sum_{i=0}^{n-1}(a_{k-i}-b_i)^2 \
    &=sum_{i=0}^{n-1}a_{k-i}^2-2a_{k-i}b_i+b_i^2 \
    &=sum_{i=0}^{n-1}a_{k-i}^2+sum_{i=0}^{n-1}b_i^2-2sum_{i=0}^{n-1}a_{k-i}b_i
    end{aligned}
    $$

    前面两个式子依然是前缀和即可处理, 后面的卷积式就可以通过高位补 $0$ 来避免多余计算了.

    可是这 $a$ 和 $b$ 可能会变...这就比较蠢...直接 $O(mnlog n)$ 跑铁定是过不去的...

    我们再来拆式子.

    那么我们首先来考虑给 $a$ 上加东西的情况. 设我们给 $a$ 整体加一个 $d$ , 则:

    $$
    egin{aligned}
    c_k&=sum_{i=0}^{n-1}((a_{k-i}+d)-b_i)^2 \
    &=sum_{i=0}^{n-1}(a_{k-i}+d)^2-2a_{k-i}b_i+b_i^2 \
    &=sum_{i=0}^{n-1}(a_{k-i}+d)^2+sum_{i=0}^{n-1}b_i^2-2sum_{i=0}^{n-1}(a_{k-i}+d)b_i\
    &=sum_{i=0}^{n-1}(a_{k-i}+d)^2+sum_{i=0}^{n-1}b_i^2-2sum_{i=0}^{n-1}a_{k-i}b_i+sum_{i=0}^{n-1}db_i \
    &=sum_{i=0}^{n-1}(a_{k-i}+d)^2+sum_{i=0}^{n-1}b_i^2-2sum_{i=0}^{n-1}a_{k-i}b_i+dsum_{i=0}^{n-1}b_i
    end{aligned}
    $$

    于是原来的卷积式还在, 剩下三个前缀和式都可以 $O(n)$ 随便重新算, 直接枚举 $d$ 然后暴力就可以了

    给 $b$ 上加东西的情况比较辣手, 因为直接给所有 $b$ 都加值是假的...这样会把防止产生贡献的系数 $0$ 变成非 $0$ 值, 然后就会爆炸...

    所以我们再搞一搞式子

    egin{aligned}
    c_k&=sum_{i=0}^{n-1}(a_{k-i}-(b_i+d))^2 \
    &=sum_{i=0}^{n-1}a_{k-i}^2-2a_{k-i}(b_i+d)+(b_i+d)^2 \
    &=sum_{i=0}^{n-1}a_{k-i}^2+sum_{i=0}^{n-1}(b_i+d)^2-2sum_{i=0}^{n-1}a_{k-i}(b_i+d)\
    &=sum_{i=0}^{n-1}a_{k-i}^2+sum_{i=0}^{n-1}(b_i+d)^2-2sum_{i=0}^{n-1}a_{k-i}b_i+da_{k-i}\
    &=sum_{i=0}^{n-1}a_{k-i}^2+sum_{i=0}^{n-1}(b_i+d)^2-2sum_{i=0}^{n-1}a_{k-i}b_i+dsum_{i=0}^{n-1}a_{k-i}\
    end{aligned}

    好了, 又是三个前缀和一个和 $d$ 无关的卷积.

    FFT/NTT直接跑就行了

    参考代码

      1 #include <bits/stdc++.h>
      2 
      3 const int G=3;
      4 const int DFT=1;
      5 const int IDFT=-1;
      6 const int MAXN=270000;
      7 const int MOD=998244353;
      8 
      9 int n;
     10 int m;
     11 int bct;
     12 int bln=1;
     13 int a[MAXN];
     14 int b[MAXN];
     15 int c[MAXN];
     16 int na[MAXN];
     17 int nb[MAXN];
     18 int sa[MAXN];
     19 int sb[MAXN];
     20 int sa2[MAXN];
     21 int sb2[MAXN];
     22 int rev[MAXN];
     23 
     24 int Pow(int,int,int);
     25 void NTT(int*,int,int);
     26 
     27 int main(){
     28     scanf("%d%d",&n,&m);
     29     for(int i=0;i<n;i++)
     30         scanf("%d",a+i);
     31     for(int i=n;i<2*n;i++)
     32         a[i]=a[i-n];
     33     std::reverse(a,a+2*n);
     34     for(int i=0;i<2*n;i++){
     35         na[i]=a[i];
     36         sa[i]=(i>0?sa[i-1]:0)+a[i];
     37         sa2[i]=(i>0?sa2[i-1]:0)+a[i]*a[i];
     38     }
     39     for(int i=0;i<n;i++){
     40         scanf("%d",b+i);
     41         nb[i]=b[i];
     42         sb[i]=(i>0?sb[i-1]:0)+b[i];
     43         sb2[i]=(i>0?sb2[i-1]:0)+b[i]*b[i];
     44     }
     45     for(int i=n;i<2*n;i++){
     46         sb[i]=sb[i-1];
     47         sb2[i]=sb2[i-1];
     48     }
     49     while(bln<3*n){
     50         bln<<=1;
     51         ++bct;
     52     }
     53     //printf("bln=%d
    ",bln);
     54     for(int i=0;i<bln;i++)
     55         rev[i]=(rev[i>>1]>>1)|((i&1)<<(bct-1));
     56     NTT(na,bln,DFT);
     57     NTT(nb,bln,DFT);
     58     for(int i=0;i<bln;i++)
     59         c[i]=1ll*na[i]*nb[i]%MOD;
     60     NTT(c,bln,IDFT);
     61     int ans=INT_MAX;/*
     62     for(int i=0;i<2*n;i++)
     63         printf("c[%d]=%d
    ",i,c[i]);*/
     64     for(int d=0;d<=m;d++){ // Change A
     65         for(int i=0;i<2*n;i++){
     66             sa[i]=(i>0?sa[i-1]:0)+a[i]+d;
     67             sa2[i]=(i>0?sa2[i-1]:0)+(a[i]+d)*(a[i]+d);
     68         }
     69         for(int i=n-1;i<2*n-1;i++){
     70             int sum=sa2[i]+sb2[i];
     71             sum-=2*(c[i]+d*sb[i]);
     72             if(i>=n)
     73                 sum-=sa2[i-n];
     74             //printf("d=%d pos %d: sa2=%d sb2=%d c=%d sum=%d
    ",d,i,sa2[i],sb2[i],c[i],sum);
     75             ans=std::min(ans,sum);
     76         }
     77     }
     78     //printf("%d
    ",ans);
     79     for(int i=0;i<2*n;i++){
     80         sa[i]=(i>0?sa[i-1]:0)+a[i];
     81         sa2[i]=(i>0?sa2[i-1]:0)+a[i]*a[i];
     82     }
     83     for(int d=0;d<=m;d++){ // Change B
     84         for(int i=0;i<n;i++){
     85             sb[i]=(i>0?sb[i-1]:0)+b[i]+d;
     86             sb2[i]=(i>0?sb2[i-1]:0)+(b[i]+d)*(b[i]+d);
     87         }
     88         for(int i=n;i<2*n;i++){
     89             sb[i]=sb[i-1];
     90             sb2[i]=sb2[i-1];
     91         }
     92         for(int i=n-1;i<2*n-1;i++){
     93             int sum=sa2[i]+sb2[i];
     94             sum-=2*(c[i]+d*(sa[i]-(i>=n?sa[i-n]:0)));
     95             if(i>=n)
     96                 sum-=sa2[i-n];
     97             ans=std::min(ans,sum);
     98         }
     99     }
    100     printf("%d
    ",ans);
    101     return 0;
    102 }
    103 
    104 void NTT(int* a,int len,int opt){
    105     for(int i=0;i<len;i++)
    106         if(rev[i]>i)
    107             std::swap(a[rev[i]],a[i]);
    108     for(int i=1;i<len;i<<=1){
    109         int step=i<<1;
    110         int wn=Pow(G,(MOD-1+opt*(MOD-1)/step)%(MOD-1),MOD);
    111         for(int j=0;j<len;j+=step){
    112             int w=1;
    113             for(int k=0;k<i;k++,w=1ll*w*wn%MOD){
    114                 int x=a[j+k];
    115                 int y=1ll*w*a[j+k+i]%MOD;
    116                 a[j+k]=(x+y)%MOD;
    117                 a[j+k+i]=(x-y+MOD)%MOD;
    118             }
    119         }
    120     }
    121     if(opt==IDFT){
    122         int inv=Pow(len,MOD-2,MOD);
    123         for(int i=0;i<len;i++)
    124             a[i]=1ll*a[i]*inv%MOD;
    125     }
    126 }
    127 
    128 inline int Pow(int a,int n,int p){
    129     int ans=1;
    130     while(n>0){
    131         if(n&1)
    132             ans=1ll*a*ans%p;
    133         a=1ll*a*a%p;
    134         n>>=1;
    135     }
    136     return ans;
    137 }
    BZOJ 4827

    日常图包

  • 相关阅读:
    计算某一日期是在一年中第几周
    动态生成web表-asp.net table
    sql server 小技巧(7) 导出完整sql server 数据库成一个sql文件,包含表结构及数据
    循环取月的三位英语名 Jan Feb
    Python面向对象编程
    算法
    UDP Sockets in C#
    C++ 11
    GNU Make
    C++ 11
  • 原文地址:https://www.cnblogs.com/rvalue/p/10220159.html
Copyright © 2011-2022 走看看