#define debug(x) cout << #x << ":\t" << (x) << endl; usingnamespace std; #define ll long long #define ull unsigned ll constint N = 2e5 + 10; constint INF = 0x3f3f3f3f; const ll inf = 0x3f3f3f3f3f3f3f3f; const ll mod = 1e9 + 7; const ll inv2 = (mod + 1) / 2; int n, q; int a[N]; int vis[N], tot, prime[N];
voidpre(int n){ for (int i = 2; i <= n; i++) { if (!vis[i])prime[tot++] = i; int d; for (int j = 0; j < tot && (d = i * prime[j]) <= n; j++) { vis[d] = 1; if (i % prime[j] == 0)break; } } }
voidbuild(int l, int r, int rt){ if (l == r) { tr[rt] = gt(a[l]); return; } build(lson); build(rson); up(rt); }
voidupd(int q, int l, int r, int rt){ if (l == r) { tr[rt] = gt(a[l]); return; } if (q <= mid)upd(q, lson); elseupd(q, rson); up(rt); }
bits qry(int ql, int qr, int l, int r, int rt){ if (ql <= l && qr >= r) { return tr[rt]; } bits res = 0; if (ql <= mid)res |= qry(ql, qr, lson); if (qr > mid)res |= qry(ql, qr, rson); return res; }
intmain(){ pre(100000); scanf("%d%d", &n, &q); for (int i = 1; i <= n; i++)scanf("%d", &a[i]); build(1, n, 1); while (q--) { int op, l, r; scanf("%d%d%d", &op, &l, &r); if (op == 1) { a[l] = r; upd(l, 1, n, 1); } else { bits ans = qry(l, r, 1, n, 1); printf("%d\n", ans.count()); } } return0; }
E - 音游家的谱面(Easy version)
题意:n 根轨道,m 个音符,初始左手在1号轨道,右手在 n 号轨道,每秒两只手都可以向任意方向移动一格或不动,按照时间顺序给出了 m 个音符出现在几号轨道,要求输出每个音符的出现时间,满足能够全部接到且完成时间最短。1≤n,m≤100
int n, m; int a[N], dp[110][110][110]; typedef pair<int, int> pii; pii f[110][110][110];
voidpr(int u, int v, int k){ if (k == 1) { printf("%d ", dp[u][v][k]); return; } pr(f[u][v][k].first, f[u][v][k].second, k - 1); printf("%d ", dp[u][v][k]); }
intmain(){ scanf("%d%d", &n, &m); for (int i = 1; i <= m; i++)scanf("%d", &a[i]); memset(dp, 0x3f, sizeof(dp)); for (int i = 1; i <= n; i++) { dp[a[1]][i][1] = max(a[1] - 1, n - i); dp[i][a[1]][1] = max(i - 1, n - a[1]); } for (int k = 2; k <= m; k++) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (dp[a[k]][i][k] > dp[a[k - 1]][j][k - 1] + max(abs(a[k] - a[k - 1]), abs(i - j))) { dp[a[k]][i][k] = dp[a[k - 1]][j][k - 1] + max(abs(a[k] - a[k - 1]), abs(i - j)); f[a[k]][i][k] = {a[k - 1], j}; } if (dp[a[k]][i][k] > dp[j][a[k - 1]][k - 1] + max(abs(a[k] - j), abs(i - a[k - 1]))) { dp[a[k]][i][k] = dp[j][a[k - 1]][k - 1] + max(abs(a[k] - j), abs(i - a[k - 1])); f[a[k]][i][k] = {j, a[k - 1]}; } if (dp[i][a[k]][k] > dp[a[k - 1]][j][k - 1] + max(abs(i - a[k - 1]), abs(a[k] - j))) { dp[i][a[k]][k] = dp[a[k - 1]][j][k - 1] + max(abs(i - a[k - 1]), abs(a[k] - j)); f[i][a[k]][k] = {a[k - 1], j}; } if (dp[i][a[k]][k] > dp[j][a[k - 1]][k - 1] + max(abs(i - j), abs(a[k] - a[k - 1]))) { dp[i][a[k]][k] = dp[j][a[k - 1]][k - 1] + max(abs(i - j), abs(a[k] - a[k - 1])); f[i][a[k]][k] = {j, a[k - 1]}; } } } } int ans = INF; for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++)ans = min(ans, dp[i][j][m]); for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) { if (dp[i][j][m] == ans) { pr(i, j, m); return0; } } return0; }
voidbuild(int l, int r, int rt){ tr[rt] = {INF, -1}; if (l == r) { return; } build(lson); build(rson); }
voidupd(int ql, int qr, pii x, int l, int r, int rt){ if (ql <= l && qr >= r) { tr[rt] = min(tr[rt], x); return; } if (ql <= mid)upd(ql, qr, x, lson); if (qr > mid)upd(ql, qr, x, rson); }
pii qry(int q, int l, int r, int rt){ if (l == r) { return tr[rt]; } if (q <= mid)returnmin(tr[rt], qry(q, lson)); elsereturnmin(tr[rt], qry(q, rson)); }
intmain(){ scanf("%d%d", &n, &m); for (int i = 1; i <= m; i++)scanf("%d", &p[i]); p[0] = 1; memset(dp, 0x3f, sizeof(dp)); dp[n][0] = 0; for (int k = 1; k <= m; k++) { build(1, n, 1); for (int j = 1; j <= n; j++) { int tL = abs(p[k] - p[k - 1]); int L = max(1, j - tL), R = min(n, j + tL); upd(L, R, {dp[j][k - 1] + tL, j}, 1, n, 1); tL = abs(p[k] - j); L = max(1, p[k - 1] - tL), R = min(n, p[k - 1] + tL); upd(L, R, {dp[j][k - 1] + tL, j}, 1, n, 1); } for (int i = 1; i <= n; i++) { pii t = qry(i, 1, n, 1); dp[i][k] = t.first; f[i][k] = t.second; } } int ans = INF; for (int i = 1; i <= n; i++)ans = min(ans, dp[i][m]); for (int i = 1; i <= n; i++) { if (dp[i][m] == ans) { pr(i, m); return0; } } return0; }
单调队列+前缀和
更新时用线段树还是不行,必须用线性复杂度的方法。还是分两种情况考虑更新:
假设位于 j 的手移动到 p[k],位于 p[k−1] 的手移动到 i。对于满足 ∣i−p[k−1]∣≤∣p[k]−j∣ 的 i,转移方程为 dp[i][k]=dp[j][k−1]+∣p[k]−j∣。设 ∣p[k]−j∣=tL,那么也就是要更新 i∈[p[k−1]−tL,p[k−1]+tL]。对于所有的 j,这个区间都包含 p[k−1],所以可以用 p[k−1] 把区间分成两块,左部分求前缀最小值,右部分求后缀最小值。即对于一个 j,在 p[k−1]−tL 和 p[k−1]+tL 处记下 dp[j][k−1]+tL,再枚举 i 求 dp[i][k],若 i 位于 p[k−1] 左侧,则等于 i 处的前缀最小值,否则等于 i 处的后缀最小值。
假设位于 j 的手移动到 i,位于 p[k−1] 的手移动到 p[k],之前是用 j 去更新 dp[i][k],现在反过来考虑 i 会被哪些 j 更新到。∣p[k]−p[k−1]∣ 是一个常数,设为 C,则 i 会被 [i−C,i+C] 这个范围内的 j 更新到。这就是一个固定大小的窗口。要对每一个 i 求它所在窗口的最小值,就是典型的单调队列。维护一个递增的单调队列,满足首尾到 i 的距离不超过 C,队首就是窗口中的最小值。