抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

题目传送门

题意

给定两个长度为\(n\)的正整数序列\(a_0,a_1,a_2,\cdots,a_{n-1}\)\(b_0,b_1,b_2,\cdots,b_{n-1}\),满足\(a_i,b_i\le m\)

\[\sum_{i=0}^{n-1} ((a_i+c_0)-(b_{(i+k)\bmod n}+c_1))^2\]的最小值,其中\(k,c_0,c_1\)是非负整数。

\(n\le 5\times 10^4,m\le 100\)

题解

观察上式,可以发现我们并不关心\(c_0,c_1\)具体的值,只关心\(c_0-c_1\)的值。记\(c=c_0-c_1\),显然有\(-m\le c\le m\)。于是变成了求\[\sum_{i=0}^{n-1} (a_i-b_{(i+k)\bmod n}+c)^2\]的最小值,其中\(0\le k\le n-1,-m\le c\le m\)

最朴素的做法是直接枚举\(k,c\)计算答案,时间复杂度\(O(n^2m)\)

这个和的平方的形式很难优化,考虑把它展开:

\[\begin{aligned} &\quad \sum_{i=0}^{n-1}(a_i-b_{(i+k)\bmod n}+c)^2 \\ &=\sum_{i=0}^{n-1}(a_i^2+b_{(i+k)\bmod n}^2+c^2-2a_ib_{(i+k)\bmod n}+2a_ic-2b_{(i+k)\bmod n}c) \\ &=-2\sum a_ib_{(i+k)\bmod n}+\sum a_i^2+\sum b_i^2+2c\sum (a_i-b_i)+nc^2 \end{aligned}\]

然后我们发现,\(k,c\)并没有同时影响答案中的某一项。第一项为与\(k\)有关的式子,显然后面部分为关于\(c\)的一个二次函数。后面部分可以直接用二次函数的顶点公式计算(注意\(c\)是整数,需要讨论两种情况),也可以直接枚举\(c\)进行计算。

如果前面部分也直接枚举\(k\)进行计算的话,时间复杂度为\(\mathcal O(n^2)\),仍不能通过。

先不考虑系数。\(\bmod\)很难处理,考虑把\(b\)倍长,原式变成了\(\sum a_ib_{i+k}\)。联系卷积的基本形式\(c_i=\sum_{j=0}^{i} a_jb_{i-j}\),可以发现需要满足\(a,b\)的下标之和为\(c\)的下标,即一个与\(j\)无关的值。那么我们考虑把\(a\)翻转,原式变成了\(\sum a_{n-i-1}b_{i+k}\),此时\(n-i-1+i+k=n+k-1\),是一个与\(i\)无关的值。于是就可以FFT了。由于\(0\le k\le n-1\),所以对应的答案(假设\(c=a*b\))就是\(c_{n-1},c_{n},c_{n+1},\cdots,c_{2n-2}\)

啥?FFT精度爆了?因为\(m\le 100\),所以\(0\le c_i\le nm^2\le 5\times 10^8< 998244353\),直接用NTT即可。

时间复杂度\(O(n\log n)\)

代码

评测记录,开O2,总时间170ms,目前在第一页。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
int read(){
register int x = 0;
register char f = 1, ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f ^= 1;
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ '0');
return f ? x : -x;
}
#define N 300005
#define INF 0x3f3f3f3f3f3f3f3fll
int n, m, a[N], b[N], c[N];
namespace Polynomial{
#define P 998244353
int omega[N], rev[N];
int qpow(int a, int b){
int s = 1;
for (; b; b >>= 1, a = 1ll * a * a % P) if (b & 1) s = 1ll * s * a % P;
return s;
}
void init(int n){
int k = 0;
while ((1 << k) < n) ++k;
for (register int i = 0; i < n; ++i) rev[i] = rev[i >> 1] >> 1 | (i & 1) << k >> 1;
}
void add(int &x, int y){ (x += y) >= P ? x -= P : 0; }
int sub(int x, int y){ return (x -= y) < 0 ? x + P : x; }
void NTT(int n, int *a, int o = 1){
for (register int i = 0; i < n; ++i) if (i < rev[i]) std :: swap(a[i], a[rev[i]]);
for (register int m = 1, l; m < n; m = l){
l = m << 1, omega[0] = 1, omega[1] = qpow(~o ? 3 : 332748118, (P - 1) / l);
for (register int i = 2; i < m; ++i) omega[i] = 1ll * omega[i - 1] * omega[1] % P;
for (register int *p = a, t; p < a + n; p += l)
for (register int k = 0; k < m; ++k)
t = 1ll * p[k + m] * omega[k] % P, p[k + m] = sub(p[k], t), add(p[k], t);
}
if (o == -1)
for (register int _n = qpow(n, P - 2), i = 0; i < n; ++i) a[i] = 1ll * a[i] * _n % P;
}
void Multiply(int na, int *a, int nb, int *b, int nc, int *c){
int n = 1;
while (n < nc) n <<= 1;
init(n), NTT(n, a), NTT(n, b);
for (register int i = 0; i < n; ++i) c[i] = 1ll * a[i] * b[i] % P;
NTT(n, c, -1);
}
}
int main(){
long long s1 = 0, s2 = 0, s = INF, ans = INF;
n = read(), m = read();
for (register int i = 0; i < n; ++i) a[n - i - 1] = read();
for (register int i = 0; i < n; ++i) b[i + n] = b[i] = read();
for (register int i = 0; i < n; ++i)
s1 += 1ll * a[i] * a[i] + 1ll * b[i] * b[i], s2 += (a[i] << 1) - (b[i] << 1);
Polynomial :: Multiply(n, a, n << 1, b, n << 1, c);
for (register int i = n - 1; i < (n << 1) - 1; ++i) s = std :: min(s, -2ll * c[i]);
for (register int i = -m; i <= m; ++i)
ans = std :: min(ans, 1ll * n * i * i + s2 * i + s1);
printf("%lld\n", ans + s);
}

评论