T1 [JZOJ6314] Balancing Inversions
题目描述
Bessie 和 Elsie 在一个长为 2N 的布尔数组 A 上玩游戏。
Bessie 的分数为 A 的前一半的逆序对数量,Elsie 的分数为 A 的后一半的逆序对数量。
逆序对指的是满足 A[i]=1 以及 A[j]=0 的一对元素,其中i<j。例如,一段 0 之后接着一段 1 的数组没有逆序对,一段 X 个 1 之后接着一段 Y 个 0 的数组有 XY 个逆序对。
Farmer John 偶然看见了这一棋盘,他好奇于可以使得游戏看起来成为平局所需要交换相邻元素的最小次数。请帮助 Farmer John 求出这个问题的答案。
数据范围
前 $4$ 组数据 $N$ 分别为 $5,20,100,1000$
对于 $100\%$ 的数据,$1 leq N leq 10^5$
分析
考场上一直在想这题,绕了好多弯子,到最后十分钟差不多想清楚,但是没码完
显然,当不交换中间两元素时,最小交换次数为前后两半的逆序对数差的绝对值
然后可以先枚举将左边 $k$ 个 $1$ 移到右边时的所有情况,同理再枚举移 $0$ 的情况
以移 $1$ 为例,我们可以用两个指针分别指向前半段的尾部和后半段的首部,然后依次向前查找 $1$ 和向后查找 $0$,这样总时间复杂度就是 $O(n)$ 的了
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <vector> #include <queue> using namespace std; #define ll long long #define inf 0x3f3f3f3f #define N 100005 int n, cnt1, cnt2, pos1, pos2; ll an, bn, ans, len1, len2; int a[N], b[N]; int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", a + i); if (a[i]) cnt1++; else an += cnt1; } for (int i = 1; i <= n; i++) { scanf("%d", b + i); if (b[i]) cnt2++; else bn += cnt2; } ans = abs(an - bn); pos1 = n; pos2 = 1; len1 = len2 = 0; for (int i = 1; i <= cnt1 && i <= n - cnt2; i++) { while (!a[pos1]) pos1--; len1 += n - pos1; pos1--; while (b[pos2]) pos2++; len2 += pos2 - 1; pos2++; ll sum1 = an - len1 + i * (cnt1 - i); ll sum2 = bn - len2 + i * (n - cnt2 - i); ll now = len1 + len2 + i + abs(sum1 - sum2); ans = min(ans, now); } pos1 = n; pos2 = 1; len1 = len2 = 0; for (int i = 1; i <= n - cnt1 && i <= cnt2; i++) { while (a[pos1]) pos1--; len1 += n - pos1; pos1--; while (!b[pos2]) pos2++; len2 += pos2 - 1; pos2++; ll sum1 = an + len1 - i * cnt1; ll sum2 = bn + len2 - i * (n - cnt2); ll now = len1 + len2 + i + abs(sum1 - sum2); ans = min(ans, now); } printf("%lld", ans); return 0; }
T2 [JZOJ6317] 树
题目描述
数据范围
分析
如果只考虑对一个点求方案数,那么就是一个很显然的树形 $dp$
设 $f[x]$ 表示点 $x$ 的子树能组成的方案总数
若点 $x$ 与其子节点之间的边染黑,则该子节点的子树不能存在边被染黑;若该边不被染黑,则可以算上子节点的所有方案
于是得到 $f[x]=prod (f[son]+1)$
但对于每个点都求一遍的话,当然会超时
实际上经过一条边的一个方向,得到子树的方案数是一定的,所以可以记忆化搜索
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <vector> #include <queue> using namespace std; #define ll long long #define inf 0x3f3f3f3f #define N 100005 int n, from, tot, p = 1e9 + 7; int to[N << 1], nxt[N << 1], head[N]; int book[N << 1]; void add(int u, int v) { to[++tot] = v; nxt[tot] = head[u]; head[u] = tot; } int dp(int x, int fa) { int now = 1; for (int i = head[x]; i; i = nxt[i]) { if (to[i] == fa) continue; if (!book[i]) book[i] = dp(to[i], x); now = (ll)now * (book[i] + 1) % p; } return now % p; } int main() { scanf("%d", &n); for (int i = 2; i <= n; i++) { scanf("%d", &from); add(from, i); add(i, from); } for (int i = 1; i <= n; i++) printf("%d ", dp(i, 0) % p); return 0; }
T3 [JZOJ6292] 序列
题目描述
数据范围
分析
记旋转 $i$ 次后的答案为 $ans_i$,我们再单独考虑序列中每一个元素
对于所有 $i in [1,n]$,若 $s_i < i$,则:
(1) 对 $ans_1,ans_2,···,ans_{i-s_i-1},ans_{i-s_i}$ 分别产生 $i-s_i-1,i-s_i-2,···,1,0$ 的贡献
(2) 对 $ans_{i-s_i+1},ans_{i-s_i+2},···,ans_{i-2},ans_{i-1}$ 分别产生 $1,2,···,s_i-2,s_i-1$ 的贡献
(3) 对 $ans_i,ans_{i+1},···,ans_{n-1},ans_n$ 分别产生 $n-s_i,n-s_i-1,···,i-s_i+1,i-s_i$ 的贡献
$i leq s_i < n$ 和 $s_i geq n$ 的情况类似处理
所以只需要支持区间递增/减加和最后查询每个点的值的操作即可
这里用差分就可以实现,不过感觉我的写法很麻烦
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <queue> using namespace std; #define ll long long #define inf 0x3f3f3f3f #define N 2000005 inline int read() { int x = 0, f = 1; char c = getchar(); while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();} while ( isdigit(c)) {x = x * 10 + c - '0'; c = getchar();} return x *= f; } int n, x; ll minans = 4e12; ll ans[N], pre[N], suf[N], p[N], s[N]; int main() { freopen("a.in", "r", stdin); freopen("a.out", "w", stdout); n = read(); for (int i = 1; i <= n; i++) { x = read(); if (x < i) { suf[i - x - 1]++; pre[i - x + 1]++; pre[i]--; p[i] -= x - 1; suf[i - 1]--; suf[n - 1]++; s[i - 1] -= n - i; ans[i] += i - x; } else if (x < n) { pre[1]++; pre[i]--; p[i] -= i - 1; ans[1] += x - i; ans[i] -= x - i; suf[i - 1]--; suf[n + i - x - 1]++; s[i - 1] -= n - x; pre[n + i - x + 1]++; } else { pre[1]++; pre[i]--; p[i] -= i - 1; ans[1] += x - i; ans[i] -= x - i; pre[i + 1]++; ans[i] += x - n; } } for (int i = 1; i <= n; i++) pre[i] += pre[i - 1]; for (int i = 1; i <= n; i++) pre[i] += pre[i - 1] + p[i]; for (int i = n; i >= 1; i--) suf[i] += suf[i + 1]; for (int i = n; i >= 1; i--) suf[i] += suf[i + 1] + s[i]; for (int i = 1; i <= n; i++) minans = min(minans, (ans[i] += ans[i - 1]) + pre[i] + suf[i]); printf("%lld ", minans); return 0; }