题意:给定一个序列 ,定义 , 次给定 ,问 经过多少次 函数后会变为 ,或者报告不可能。,1.5s,1024 MB。
首先看这个多少次,也许可以想到倍增,但是当前的 一共有 个,无法计算。
后面的表示可能不够严谨,可以认为 表示上面的函数,会返回一个区间。
下面给出一个结论:
。
证明:设右边的为 。首先证明 。容易发现每一个的 都是 的,所以 。然后考虑证明 。容易发现在最大值和最小值之间的区间,跨度是连续的,也就是每一个 都可以在这段区间中找到一个位置 使得 ,那么 。综上,。
有了上面的结论,我们就可以发现只需要记录所有的 即可。
然后考虑倍增,记录所有的 的答案,那么当 转向 时,我们相当于是求 。里面一层的我们已经知道了,现在要求外面一层的,那么就需要把 区间内部的所有区间都并起来,这个可以使用线段树 / ST 表实现,预处理时间复杂度 。
现在开始询问。考虑如何判断 可不可以变成 。容易发现如果超过 个都还没有出现 的话,肯定出现了循环节,但是一出现 就一定一直是 了(最开始是 除外),既然没有出现 ,那么就不可能出现 了。剩下的直接倍增查找即可,时间复杂度 (ST 表,空间复杂度 )或者 (线段树,空间复杂度 )。
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
| void prework(int id) { for (int i = 2; i <= n; ++ i) lg[i] = lg[i >> 1] + 1; for (int j = 1; (1 << j) <= n; ++ j) for (int i = 1; i + (1 << j) - 1 <= n; ++ i) st[id][i][j] = st[id][i][j - 1] | st[id][i + (1 << (j - 1))][j - 1]; }
Segment query(int id, int l, int r) { if (l > r) return {}; int k = lg[r - l + 1]; return st[id][l][k] | st[id][r - (1 << k) + 1][k]; }
int main() { std::cin >> n >> Q; for (int i = 1; i <= n; ++ i) scanf("%d", a + i); for (int i = 1; i < n; ++ i) pw[0][i] = {std::min(a[i], a[i + 1]), std::max(a[i], a[i + 1])}; for (int k = 0; k < L - 1; ++ k) { for (int i = 1; i < n; ++ i) st[k][i][0] = pw[k][i]; prework(k); for (int i = 1; i < n; ++ i) pw[k + 1][i] = query(k, pw[k][i].l, pw[k][i].r - 1); } int l, r; while (Q --) { scanf("%d %d", &l, &r); if (l == 1 && r == n) { puts("0"); continue; } if (l == r) { puts("-1"); continue; } if (query(L - 2, l, r - 1) != Segment(1, n)) { puts("-1"); continue; } long long res = 0; Segment cur(l, r); for (int i = L - 2; ~i; -- i) if (query(i, cur.l, cur.r - 1) != Segment(1, n)) res |= 1LL << i, cur = query(i, cur.l, cur.r - 1); printf("%lld\n", res + 1); } return 0; }
|
Related Issues not found
Please contact @mydcwfy to initialize the comment