NOIP模拟测试13
C题 60分扔扔扔,太遗憾了:(
Problem A:矩阵游戏
啥也不说了,先打了个40暴力。
然后看到数据范围,想到肯定可以颓柿子。开始颓颓颓。
一个点$(i, j)$的数字是$(i - 1)m + j$。最后的答案是$\sum \limits_{i=1}^n \sum \limits_{j=1}^m R_i S_j [(i-1)m + j]$.
继续化简:
原式 = $\sum \limits_{i=1}^n R_i \sum \limits_{j=1}^m S_j [(i-1)m + j]$ = $\sum \limits_{i=1}^n R_i ((i-1) \sum \limits_{j=1}^m mS_j + \sum \limits_{j=1}^m jS_j)$
$\sum \limits_{j=1}^m mS_j $ 和 $ \sum \limits_{j=1}^m jS_j$都可以$O(m)$线性求,最后再$O(n)$把答案求出来,得到一个$O(n + m)$的吼算法。
1 #include <bits/stdc++.h> 2 #define ll long long 3 4 const int N = 1000000 + 233, MOD = 1000000007; 5 ll n, m, k, R[N], S[N], sum_s1, sum_s2, ans; 6 7 signed main() { 8 scanf("%lld%lld%lld", &n, &m, &k); 9 for (int i = 1; i <= n; i++) R[i] = 1ll; 10 for (int i = 1; i <= m; i++) S[i] = 1ll; 11 for (int i = 1; i <= k; i++) { 12 char op[10]; ll x, y; 13 scanf("%s%lld%lld", op, &x, &y); 14 if (op[0] == 'R') { 15 (R[x] *= y) %= MOD; 16 } else { 17 (S[x] *= y) %= MOD; 18 } 19 } 20 for (int i = 1; i <= m; i++) { 21 sum_s1 = (sum_s1 + i * S[i]) % MOD; 22 sum_s2 = (sum_s2 + m * S[i]) % MOD; 23 } 24 for (int i = 1; i <= n; i++) { 25 ans = (ans + R[i] * ((sum_s1 + ((ll) i - 1ll) * sum_s2) % MOD) % MOD) % MOD; 26 } 27 return !printf("%lld\n", ans); 28 }
Problem B:跳房子
考试的时候直接码了个一步一步挪的暴力。
其实看出来有循环节了,但写起来细节太多,真心码不动Orz
TKJ的这种方法很易于理解,而且很好码。
我们只看每一列,每次移动等价于一次置换。
开个线段树去维护置换,把区间+重载一下更方便。
其他部分都按普通线段树写就行。
1 #include <bits/stdc++.h> 2 3 const int N = 2005; 4 int n, m, q, val[N][N], x = 1, y = 1; 5 6 class SegTree { 7 private: 8 struct Node { 9 int nxt[N]; 10 friend Node operator *(Node a, Node b) { 11 Node ret; 12 for (int i = 1; i <= n; i++) 13 ret.nxt[i] = b.nxt[a.nxt[i]]; 14 return ret; 15 } 16 } t[N << 2]; 17 void build(int p, int l, int r) { 18 if (l == r) { 19 for (int i = 1; i <= n; i++) { 20 int yy = l == m ? 1 : l + 1; 21 int x_1 = i == n ? 1 : i + 1; 22 int x_2 = i == 1 ? n : i - 1; 23 int x_3 = i; 24 t[p].nxt[i] = val[x_1][yy] > val[x_2][yy] && 25 val[x_1][yy] > val[x_3][yy] ? x_1 : 26 val[x_2][yy] > val[x_3][yy] ? x_2 : x_3; 27 } 28 return; 29 } 30 int mid = (l + r) >> 1; 31 build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r); 32 t[p] = t[p << 1] * t[p << 1 | 1]; 33 } 34 void _change(int p, int l, int r, int x) { 35 if (l == r) { 36 for (int i = 1; i <= n; i++) { 37 int yy = l == m ? 1 : l + 1; 38 int x_1 = i == n ? 1 : i + 1; 39 int x_2 = i == 1 ? n : i - 1; 40 int x_3 = i; 41 t[p].nxt[i] = val[x_1][yy] > val[x_2][yy] && 42 val[x_1][yy] > val[x_3][yy] ? x_1 : 43 val[x_2][yy] > val[x_3][yy] ? x_2 : x_3; 44 } 45 return; 46 } 47 int mid = (l + r) >> 1; 48 if (x <= mid) _change(p << 1, l, mid, x); 49 else _change(p << 1 | 1, mid + 1, r, x); 50 t[p] = t[p << 1] * t[p << 1 | 1]; 51 } 52 Node _query(int p, int l, int r, int L, int R) { 53 if (l == L && r == R) return t[p]; 54 int mid = (L + R) >> 1; 55 if (r <= mid) return _query(p << 1, l, r, L, mid); 56 else if (l > mid) return _query(p << 1 | 1, l, r, mid + 1, R); 57 else return _query(p << 1, l, mid, L, mid) * 58 _query(p << 1 | 1, mid + 1, r, mid + 1, R); 59 } 60 Node _qpow(Node x, int b) { 61 Node ret; 62 for (int i = 1; i <= n; i++) ret.nxt[i] = i; 63 for (; b; b >>= 1, x = x * x) 64 if (b & 1) ret = ret * x; 65 return ret; 66 } 67 public: 68 SegTree() { 69 build(1, 1, m); 70 } 71 Node query(int l, int r) { 72 return _query(1, l, r, 1, m); 73 } 74 void change(int x) { 75 _change(1, 1, m, x); 76 } 77 Node qpow(int b) { 78 return _qpow(t[1], b); 79 } 80 }; 81 82 signed main() { 83 scanf("%d%d", &n, &m); 84 for (int i = 1; i <= n; i++) 85 for (int j = 1; j <= m; j++) 86 scanf("%d", &val[i][j]); 87 static SegTree *Gekoo = new SegTree(); 88 scanf("%d", &q); 89 for (int i = 1; i <= q; i++) { 90 char op[10]; int k, a, b, e; 91 scanf("%s", op); 92 if (op[0] == 'm') { 93 scanf("%d", &k); 94 if (!k) { 95 printf("%d %d\n", x, y); 96 continue; 97 } 98 if (y + k - 1 <= m) { 99 x = Gekoo->query(y, y + k - 1).nxt[x]; 100 y = (y + k - 1 == m) ? 1 : y + k; 101 } else { 102 k -= m - y + 1; 103 x = Gekoo->query(y, m).nxt[x], y = 1; 104 if (k / m) 105 x = Gekoo->qpow(k / m).nxt[x]; 106 if (k % m) 107 x = Gekoo->query(y, y + k % m - 1).nxt[x], 108 y += k % m; 109 } 110 printf("%d %d\n", x, y); 111 } else { 112 scanf("%d%d%d", &a, &b, &e); 113 val[a][b] = e; 114 Gekoo->change(b == 1 ? m : b - 1); 115 } 116 } 117 return 0; 118 }
Problem C:优美序列
这题很像奇袭,一个区间是优美的,等价于$r - l = max_{[l, r]} - min_{[l, r]}$.
可以用ST表暴力做:查询出询问区间的max和min,把它们作为端点继续查,直到不再改变,可以得84pt,不详谈,直接贴代码。
1 #include <bits/stdc++.h> 2 3 const int N = 100005; 4 int n, m, pos[N], ori[N]; 5 int posmn[N][20], posmx[N][20], orimn[N][20], orimx[N][20], lg[N]; 6 7 inline int read() { 8 int a = 0; char c = getchar(); 9 while (!isdigit(c)) c = getchar(); 10 while (isdigit(c)) a = a * 10 + c - '0', c = getchar(); 11 return a; 12 } 13 14 inline int query_orimx(int l, int r) { 15 int t = lg[r - l + 1]; 16 return std::max(orimx[l][t], orimx[r-(1<<t)+1][t]); 17 } 18 19 inline int query_orimn(int l, int r) { 20 int t = lg[r - l + 1]; 21 return std::min(orimn[l][t], orimn[r-(1<<t)+1][t]); 22 } 23 24 inline int query_posmx(int l, int r) { 25 int t = lg[r - l + 1]; 26 return std::max(posmx[l][t], posmx[r-(1<<t)+1][t]); 27 } 28 29 inline int query_posmn(int l, int r) { 30 int t = lg[r - l + 1]; 31 return std::min(posmn[l][t], posmn[r-(1<<t)+1][t]); 32 } 33 34 signed main() { 35 n = read(); 36 for (int i = 1; i <= n; i++) { 37 scanf("%d", ori + i); 38 pos[ori[i]] = i; 39 } 40 for (int i = 1; i <= n; i++) 41 posmx[i][0] = posmn[i][0] = pos[i], 42 orimx[i][0] = orimn[i][0] = ori[i]; 43 for (register int j = 1; (1 << j) <= n; j++) 44 for (register int i = 1; i <= n - (1 << j) + 1; i++) 45 posmx[i][j] = std::max(posmx[i][j-1], posmx[i+(1<<(j-1))][j-1]), 46 posmn[i][j] = std::min(posmn[i][j-1], posmn[i+(1<<(j-1))][j-1]), 47 orimx[i][j] = std::max(orimx[i][j-1], orimx[i+(1<<(j-1))][j-1]), 48 orimn[i][j] = std::min(orimn[i][j-1], orimn[i+(1<<(j-1))][j-1]); 49 for (int i = 0, t = 0; i <= n; i++) { 50 if (i >= (1 << (t + 1))) t++; 51 lg[i] = t; 52 } 53 m = read(); 54 while (m--) { 55 int l, r, pl = 0, pr = 0, omx, omn; 56 l = read(), r = read(); 57 bool flg = 0; 58 while (l != pl || r != pr) { 59 if (flg) l = pl, r = pr; 60 omx = query_orimx(l, r), omn = query_orimn(l, r); 61 pl = query_posmn(omn, omx), pr = query_posmx(omn, omx); 62 flg = 1; 63 } 64 printf("%d %d\n", pl, pr); 65 } 66 return 0; 67 }
正解是分治,不提了,这里写的是DKY的线段树优化建图方法。
由上式,相邻两个数i, i + 1可以加入优美区间,仅当区间中出现了$[val_i, val_{i + 1}]$的所有值。
用边连接节点,表示限制关系,用点向区间连边显然可以线段树优化。
Tarjan缩点求SCC,DFS求出存在每个点所需要的区间。
用线段树可以维护。原因不说了,显然。
最后的答案就是区间内所有点对应区间的并。
1 #include <bits/stdc++.h> 2 3 const int INF = 0x3f3f3f3f, N = 400000 + 233; 4 int pos[N], n, val[N], m; 5 std::vector<int> G1[N], G2[N]; 6 bool vis[N]; 7 8 struct Node { 9 int l, r; 10 Node() {l = INF, r = -1;} 11 Node(int ll, int rr) {l = ll, r = rr;} 12 friend Node operator +(Node x, Node y) { 13 return Node(std::min(x.l, y.l), std::max(x.r, y.r)); 14 } 15 }; 16 17 class SegTree { 18 private: 19 struct Tree { 20 Node t[N]; 21 void _change(int p, int L, int R, int x, Node v) { 22 if (L == R) return (void) (t[p] = v); 23 int mid = (L + R) >> 1; 24 if (x <= mid) _change(p << 1, L, mid, x, v); 25 else _change(p << 1 | 1, mid + 1, R, x, v); 26 t[p] = t[p<<1] + t[p<<1|1]; 27 } 28 Node _query(int p, int L, int R, int l, int r) { 29 if (l <= L && r >= R) return t[p]; 30 int mid = (L + R) >> 1; 31 if (r <= mid) return _query(p << 1, L, mid, l, r); 32 else if (l > mid) return _query(p << 1 | 1, mid + 1, R, l, r); 33 else return _query(p << 1, L, mid, l, mid) + 34 _query(p << 1 | 1, mid + 1, R, mid + 1, r); 35 } 36 } seg[2]; 37 void _build(int p, int L, int R) { 38 if (L == R) return (void) (pos[L] = p); 39 int mid = (L + R) >> 1; 40 _build(p << 1, L, mid), _build(p << 1 | 1, mid + 1, R); 41 G1[p].push_back(p << 1), G1[p].push_back(p << 1 | 1); 42 } 43 void _addedge(int p, int L, int R, int l, int r, int q) { 44 if (l <= L && r >= R) return (void) (G1[q].push_back(p)); 45 int mid = (L + R) >> 1; 46 if (l <= mid) _addedge(p << 1, L, mid, l, r, q); 47 if (r > mid) _addedge(p << 1 | 1, mid + 1, R, l, r, q); 48 } 49 public: 50 SegTree() { 51 _build(1, 1, n); 52 } 53 Node query(int id, int l, int r) { 54 return seg[id]._query(1, 1, n, l, r); 55 } 56 void addedge(int l, int r, int q) { 57 _addedge(1, 1, n, l, r, q); 58 } 59 void change(int id, int x, Node v) { 60 seg[id]._change(1, 1, n, x, v); 61 } 62 }; 63 64 int dfn[N], low[N], stk[N], num, tp, scc[N], tot; 65 bool ins[N]; 66 67 void Tarjan(int x) { 68 dfn[x] = low[x] = ++num; 69 ins[stk[++tp] = x] = 1; 70 for (auto y : G1[x]) { 71 if (!dfn[y]) { 72 Tarjan(y); 73 low[x] = std::min(low[x], low[y]); 74 } else if (ins[y]) 75 low[x] = std::min(low[x], dfn[y]); 76 } 77 if (low[x] == dfn[x]) { 78 int y; ++tot; 79 do { 80 ins[y = stk[tp--]] = 0, scc[y] = tot; 81 } while (x != y); 82 } 83 } 84 85 Node t1[N], t2[N]; 86 87 void dfs(int x) { 88 if (vis[x]) return; 89 vis[x] = 1; 90 for (auto y : G2[x]) { 91 dfs(y); 92 t2[x] = t2[x] + t2[y]; 93 } 94 } 95 96 signed main() { 97 scanf("%d", &n); 98 for (int i = 1; i <= n; i++) 99 scanf("%d", &val[i]); 100 static SegTree *Gekoo = new SegTree(); 101 for (int i = 1; i <= n; i++) 102 Gekoo->change(0, val[i], {i, i}); 103 for (int i = 2; i <= n; i++) { 104 int l = std::min(val[i - 1], val[i]), r = std::max(val[i - 1], val[i]); 105 t1[pos[i]] = Gekoo->query(0, l, r); 106 Gekoo->addedge(t1[pos[i]].l + 1, t1[pos[i]].r, pos[i]); 107 } 108 for (int i = 1; i < N; i++) 109 if (!dfn[i]) Tarjan(i); 110 for (int x = 1; x < N; x++) 111 for (auto y : G1[x]) 112 if (scc[x] != scc[y]) G2[scc[x]].push_back(scc[y]); 113 for (int i = 1; i < N; i++) 114 t2[scc[i]] = t2[scc[i]] + t1[i]; 115 for (int i = 1; i <= tot; i++) dfs(i); 116 for (int i = 2; i <= n; i++) 117 Gekoo->change(1, i, t2[scc[pos[i]]]); 118 scanf("%d", &m); 119 for (int i = 1, x, y; i <= m; i++) { 120 scanf("%d%d", &x, &y); 121 if (x == y) { 122 printf("%d %d\n", x, y); 123 } else { 124 Node ans = Gekoo->query(1, x + 1, y); 125 printf("%d %d\n", ans.l, ans.r); 126 } 127 } 128 return 0; 129 }