Dyd's Blog

He who has a strong enough why can bear almost any how.

luoguP7116 [NOIP2020] 微信步数

难点在推式子

微信步数

本人思路

然鹅卡死只有 $80pts$

首先发现随着步数的增加,每一维的取值范围在减小,所以暴力维护每一位取值范围,对每个步数进行计算即可, $O(nwk)$ ,实测 $35pts$ ,然后我看到 $w \le 10^9$ 猜要化式子去掉那个 $w$ ,但限于我 sb 的数学水平,并没有推出来

但想了个拆贡献的做法:一个点走了多少步后不合法一定是有一维限制了它,考虑处理出 $step[j][i]$ 表示“第 $j$ 维为 $i$ 时只考虑第 $j$ 维最多走多少步”(为了避免使用 long long ,具体存的时候可以用 pair ),然后枚举每一维的每一个值,查找其它维的 $step$ 比它大的数乘起来就是它的贡献,关于如何查找,考虑给 $step$ 排序,指针单调扫即可,时间 $O(k w \log w)$ ,卡常后可得 $80pts$

代码

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#include <bits/stdc++.h>
#define fi first
#define se second
#define IL inline
namespace FIO
{
const int L = (1 << 20) + 5;
char buf[L], out[L], *S, *E;
int l = 0;
#define gh() (S == E ? E = (S = buf) + fread(buf, 1, L, stdin), (S == E ? EOF : *S++) : *S++)
template<class T>
IL void rd(T &x)
{
char ch = gh(), t = 0;
for (; ch < '0' || ch > '9'; ch = gh()) t |= (ch == '-');
for (x = 0; ch >= '0' && ch <= '9'; ch = gh()) x = x * 10 + (ch ^ 48);
if (t) x = -x;
}
IL void flus(){ fwrite(out, 1, l, stdout), l = 0; }
IL void chk(){ if (l >= L - 5) flus(); }
IL void pc(char x){ out[l++] = x, chk(); }
template<class T>
IL void wt(T x)
{
if (x < 0) pc('-'), x = -x;
if (x > 9) wt(x / 10);
out[l++] = x % 10 + 48, chk();
}
}
using FIO::flus;
using FIO::rd;
using FIO::pc;
using FIO::wt;
using LL = long long;
using PII = std::pair<int, int>;
const int N = 5e5 + 100, P = 1e9 + 7, K = 10 + 5, INF = 0x3f3f3f3f, V = 1e6 + 100;
int n, k, w[K], ans = 0, mx[K][N], mn[K][N], dt[K][N], it[K];
PII a[N], step[K][V];
bool vis[K][V];
IL void adj(int &x){ x += (x >> 31) & P; }
IL int find(int j, int i)
{
int l = 0, r = n, mid, res = 0;
while (l <= r)
{
mid = (l + r) >> 1;
if (i + mn[j][mid - 1] >= 1 && i + mx[j][mid - 1] <= w[j]) res = mid, l = mid + 1;
else r = mid - 1;
}
return res;
}
IL PII get_step(int j, int i)
{
if (vis[j][i]) return step[j][i];
if (i + mx[j][n] <= w[j] && i + mn[j][n] >= 1)
{
if (dt[j][n]) step[j][i] = get_step(j, i + dt[j][n]), ++step[j][i].fi;
else step[j][i] = {INF, INF};
}
else step[j][i] = {0, find(j, i)};
vis[j][i] = true;
return step[j][i];
}
int main()
{
rd(n), rd(k);
for (int j = 1; j <= k; ++j) rd(w[j]);
for (int i = 1; i <= n; ++i) rd(a[i].fi), rd(a[i].se);
for (int i = 1; i <= n; ++i) dt[a[i].fi][i] = a[i].se;
for (int j = 1; j <= k; ++j)
{
mx[j][1] = mn[j][1] = dt[j][1];
for (int i = 2; i <= n; ++i)
{
dt[j][i] += dt[j][i - 1];
if (dt[j][i] > mx[j][i - 1]) mx[j][i] = dt[j][i];
else mx[j][i] = mx[j][i - 1];
if (dt[j][i] < mn[j][i - 1]) mn[j][i] = dt[j][i];
else mn[j][i] = mn[j][i - 1];
}
}
for (int j = 1; j <= k; ++j)
for (int i = w[j]; i; --i) if (!vis[j][i])
{
if (i + mx[j][n] <= w[j] && i + mn[j][n] >= 1)
{
if (dt[j][n]) step[j][i] = get_step(j, i + dt[j][n]), ++step[j][i].fi;
else step[j][i] = {INF, INF};
}
else step[j][i] = {0, find(j, i)};
vis[j][i] = true;
}
bool not_sol = true;
for (int j = 1; j <= k && not_sol; ++j)
{
not_sol = false;
for (int i = w[j]; i && !not_sol; --i) not_sol |= (step[j][i].fi == INF);
}
if (not_sol) pc('-'), pc('1');
else
{
for (int j = 1; j <= k; ++j) std::sort(step[j] + 1, step[j] + w[j] + 1);
for (int j = 1, as; j <= k; ++j)
{
for (int o = 1; o <= k; ++o) it[o] = w[o];
for (int i = w[j]; i; --i) if (step[j][i].fi != INF)
{
as = 1;
for (int o = 1; o <= k; ++o) if (o != j)
{
while (it[o] && (step[o][it[o]] > step[j][i]) || (step[o][it[o]] == step[j][i] && o > j) ) --it[o];
as = LL(w[o] - it[o]) * as % P;
}
adj(ans += (LL(step[j][i].fi) * n + step[j][i].se) % P * as % P - P);
}
}
wt(ans);
}
pc('\n');
return flus(), 0;
}

正解

我们不难发现 $80pts$ 的思路没有前途,因为 $w \le 10^9$ ,所以 瞟一眼题解 回到 $O(nwk)$ 的思路,考虑搞掉那个 $w$

为了推式子,我们先把 $O(n w k)$ 的式子写一下,我们维护 $up[j], dw[j]$ 表示“第 $j$ 维仍在当前范围内的值的上/下界”,并记 $len[j] = up[j] - dw[j] + 1$ ,设当前步改变了第 $c_i$ 维,不难发现 $uo[j], dw[j]$ 最多只减少一个数,也就只有第 $j$ 维为那个数的点会在此步被淘汰,所以:
$$
\begin{aligned}
Ans = \sum_{i}^{n * w} (i * \prod_{j \ne c_i} len[j])
\end{aligned}
$$
打表找规律 不难发现,设 $n$ 步为 $1$ 轮,则除第二轮外,每一轮到前一轮的每一维变化量相等,形式化的,记 $dt[j]$ 表示第 $j$ 维一轮的变化量,有 $第 i 轮的 len[j] = 第 2 轮的 len[j] - (i - 2) * dt[j]$ (这里第一轮特殊的原因是它每有前一轮给它的偏移量),那么就可以把步数按 $\mod n$ 分开,所以答案变成:
$$
\begin{aligned}
Ans = \sum_{i = n + 1}^{2n} \sum_{k} ((i + (k - 2)n) \prod_{j \ne c_i} (len[j] - (k - 2) dt[j]))
\end{aligned}
$$
解释一下这个式子:第一次枚举 $i$ 表示“第二轮的每一步(所以 $n + 1 \le i \le 2n$ )”,它们代表 $\mod n$ 意义下的一类步数;然后枚举 $k$ 表示“其实是在第 $k$ 轮(注意这里和题目的 $k$ 不一样)”,这样就确定了现在是第 $i + (k - 2)n$ 步,然后乘贡献即可

不难发现,我们消去了 $w$ ,但引入了一层新的枚举,而且我们不知道 $k$ 会枚举到那里,明显会挂,继续推式子
$$
\begin{aligned}
Ans
& = \sum_{i = n + 1}^{2n} \sum_{k} ((i + (k - 2)n) \prod_{j \ne c_i} (len[j] - (k - 2) dt[j])) \\
令 x & = k - 2, 有: \\
Ans
& = \sum_{i = n + 1}^{2n} \sum_{x} ((i + x * n) \prod_{j \ne c_i} (len[j] - x * dt[j])) \\
& = \sum_{x} \sum_{i = n + 1}^{2n} ((i + x * n) \prod_{j \ne c_i} (len[j] - x * dt[j])) \\
再令 f(x) &= ((i + x * n) \prod_{j \ne c_i} (len[j] - x * dt[j])) \\
&= \sum_{i = 0}^{k} a_i x^i \\
就是要求 Ans & = \sum_{i = n + 1}^{2n} \sum_{x} f(x) \\
& = \sum_{i = n + 1}^{2n} \sum_{x} \sum_{i = 0}^{k} a_i x^i \\
& = \sum_{i = n + 1}^{2n} \sum_{i = 0}^{k} a_i \sum_{x} x^i
\end{aligned}
$$
关于上面的变形, $f(x)$ 那步需要暴力求一下多项式的卷积(用 $O(k^2)$ ),而 $x$ 的范围可以直接检查每个 $len$ 得到,然后发现 $\sum_{x} x^i$ 可以直接拉格朗日插值,总时间为 $O(n k^2)$

代码

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#include <bits/stdc++.h>
#define fi first
#define se second
#define STC static
using PII = std::pair<int, int>;
using LL = long long;
const int N = 5e5 + 100, K = 10 + 5, P = 1e9 + 7, INF = 0x3f3f3f3f;
int n, k, w[K], up[K], dw[K], len[K], dt[K], ans, f[K], g[K], lf, dtt[K];
PII a[N];
void adj(int &x){ x += (x >> 31) & P; }
int qpow(int x, int y)
{
int res = 1;
for (; y; y >>= 1, x = LL(x) * x % P) if (y & 1) res = LL(res) * x % P;
return res;
}
void polymul(int x[], int y[], int lx, int ly)
{
STC int tmp[K];
memset(tmp, 0, sizeof tmp);
for (int i = 0; i < lx; ++i)
for (int j = 0; j < ly; ++j) adj(tmp[i + j] += LL(x[i]) * y[j] % P - P);
for (int i = lx + ly - 1; ~i; --i) x[i] = tmp[i];
}
namespace La // 插值
{
int pl[K][K];
void rdy(int o) //ready
{
STC int tp[K], tp2[K], v[K];
int *p = pl[o];
++o; //o+1次多项式
memset(v, 0, sizeof v);
v[0] = 1;
for (int i = 1; i <= o; ++i) adj(v[i] += v[i - 1] + qpow(i + 1, o - 1) - P);
for (int i = 0, b; i <= o; ++i)
{
b = v[i], tp[0] = 1;
for (int j = 0, l = 1, t; j <= o; ++j) if (i != j)
{
adj(t = i - j);
adj(tp2[0] = -(j + 1)), tp2[1] = 1;
polymul(tp, tp2, l, 2), ++l;
b = LL(b) * qpow(t, P - 2) % P;
}
for (int j = 0; j <= o; ++j) tp[j] = LL(tp[j]) * b % P;
for (int j = 0; j <= o; ++j) adj(p[j] += tp[j] - P);
}
}
int calc(int o, int x)
{
int res = 0, *p = pl[o];
++o;
for (int i = 1, tx = x; i <= o; ++i, tx = LL(tx) * x % P) adj(res += LL(p[i]) * tx % P - P); //肯定没有常数项
return res;
}
}
int main()
{
scanf("%d %d", &n, &k);
for (int i = 1; i <= k; ++i) scanf("%d", &w[i]), up[i] = w[i], dw[i] = 1, len[i] = w[i];
for (int i = 1, t; i <= n; ++i)
{
scanf("%d %d", &a[i].fi, &a[i].se);
if ((dt[t = a[i].fi] += a[i].se) >= 0) up[t] = std::min(up[t], w[t] - dt[t]);
else dw[t] = std::max(dw[t], -dt[t] + 1);
}
bool fail = true;
for (int i = 1; i <= k && fail; ++i) fail &= (up[i] >= dw[i]);
for (int i = 1; i <= k && fail; ++i) fail &= (!dt[i]);
if (fail) puts("-1");
else
{
for (int i = 0; i <= k; ++i) La::rdy(i);
for (int i = 1; i <= k; ++i) up[i] = w[i], dw[i] = 1, len[i] = w[i];
for (int i = 1, ll, t, as; i <= n; ++i) //第一轮
{
if ((dtt[t = a[i].fi] += a[i].se) >= 0) up[t] = std::min(up[t], w[t] - dtt[t]);
else dw[t] = std::max(dw[t], -dtt[t] + 1);
if ((ll = std::max(0, up[t] - dw[t] + 1)) < len[t])
{
as = i;
for (int j = 1; j <= k; ++j) if (j != t)
{
if (!len[j]){ as = 0; break; }
as = LL(as) * len[j] % P;
}
adj(ans += as - P), len[t] = ll;
}
}
for (int i = 1, ll, t, as, x; i <= n; ++i) //二轮及以后
{
if ((dtt[t = a[i].fi] += a[i].se) >= 0) up[t] = std::min(up[t], w[t] - dtt[t]);
else dw[t] = std::max(dw[t], -dtt[t] + 1);
if ((ll = std::max(0, up[t] - dw[t] + 1)) < len[t])
{
as = i + n;
for (int j = 1; j <= k; ++j) if (j != t)
{
if (!len[j]){ as = 0; break; }
as = LL(as) * len[j] % P;
}
adj(ans += as - P);
f[0] = i + n, f[1] = n, lf = 2; //求f
for (int j = 1; j <= k; ++j) if (j != t)
{
g[0] = len[j], adj(g[1] = -abs(dt[j]));
polymul(f, g, lf, 2), ++lf;
}
x = INF; //求x
for (int j = 1; j <= k; ++j)
if (!len[j]) x = 0;
else if (dt[j]) x = std::min(x, (len[j] - 1) / abs(dt[j]));
for (int j = 0; j <= k; ++j) adj(ans += LL(f[j]) * La::calc(j, x) % P - P);
len[t] = ll;
}
}
printf("%d\n", ans);
}
return 0;
}