[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≤mOutput
输出一个数,表示两个手环能产生的最小差异值。注意在将手环改造之后,装饰物的亮度 可以大于 m。Sample Input
5 6
1 2 3 4 5
6 3 3 4 5Sample Output
1
【样例解释】
需要将第一个手环的亮度增加1,第一个手环的亮度变为: 2 3 4 5 6 旋转一下第二个手环。对于该样例,是将第
二个手环的亮度6 3 3 4 5向左循环移动 2017-04-15 第 6 页,共 6 页 一个位置,使得第二手环的最终的亮度为
:3 3 4 5 6。 此时两个手环的亮度差异值为1。
题解
环上的东西我们按照套路倍长解决.
然后我们来看那个表达式: $\sum\limits_{i=1}^n(x_i-y_i)^2$...
好像在哪见过? 是哪呢?
没错就是你! 字符串匹配!
那么好了我们翻转其中一个串然后拆拆式子看看能得到什么:
$$
\begin{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-2\sum_{i=0}^ka_ib_{k-i}\\
&=\sum_{i=0}^ka_i^2+\sum_{i=0}^kb_i^2-2\sum_{i=0}^ka_ib_{k-i}
\end{aligned}
$$
而$\sum\limits_{i=0}^ka_ib_{k-i}$ 显然是个卷积, 剩下两个和式是前缀和可以预处理.
那么我们只要有了 $a$ 和 $b$ , 我们就可以在 $O(n\log n)$ 时间内算出所有位置的差异值了...吗?
然而并不是这样的.
我们在字符串匹配问题中, 高位补 $0$ 相当于补的是通配符, 不会对答案作出贡献. 但这次 $0$ 可就是字符了, 如果按照上面的式子算的话会把前 $k$ 个字符全都对位计算贡献. ($(a-0)^2$可是有贡献的).
我们设倍长并翻转的串是 $a$, 另一个串是 $b$, 那么实际上的式子应该是:
$$
\begin{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-2\sum_{i=0}^{n-1}a_{k-i}b_i
\end{aligned}
$$
前面两个式子依然是前缀和即可处理, 后面的卷积式就可以通过高位补 $0$ 来避免多余计算了.
可是这 $a$ 和 $b$ 可能会变...这就比较蠢...直接 $O(mn\log n)$ 跑铁定是过不去的...
我们再来拆式子.
那么我们首先来考虑给 $a$ 上加东西的情况. 设我们给 $a$ 整体加一个 $d$ , 则:
$$
\begin{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-2\sum_{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-2\sum_{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-2\sum_{i=0}^{n-1}a_{k-i}b_i+d\sum_{i=0}^{n-1}b_i
\end{aligned}
$$
于是原来的卷积式还在, 剩下三个前缀和式都可以 $O(n)$ 随便重新算, 直接枚举 $d$ 然后暴力就可以了
给 $b$ 上加东西的情况比较辣手, 因为直接给所有 $b$ 都加值是假的...这样会把防止产生贡献的系数 $0$ 变成非 $0$ 值, 然后就会爆炸...
所以我们再搞一搞式子
\begin{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-2\sum_{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-2\sum_{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-2\sum_{i=0}^{n-1}a_{k-i}b_i+d\sum_{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\n",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\n",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\n",d,i,sa2[i],sb2[i],c[i],sum); 75 ans=std::min(ans,sum); 76 } 77 } 78 //printf("%d\n",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\n",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 }
本博客已弃用, 新个人主页: https://rvalue.moe, 新博客: https://blog.rvalue.moe