四月 雜題題解
最小步數
題意
\(n+1\) 行 \(m\) 列的表格,從最第一行任意點開始,每次向右或向下走一格
對於 \(1\le i\le n\) 有區間 \([A_i,B_i]\) 在這些各自上時,不能向下移動。
對於 \(2\le K\le H+1\) ,求出從第一行到第 \(K\) 行的最少步數
sol
線段樹,若對於區間 \([L,R]\) 不能向下走,則
-
區間 \([1,L-1],[R+1,m]\) 全部加 1
-
\(i\in[L,R]\) 改為 \(dis(L-1)+i-L+1\) 因為必須從 \(L-1\) 走來
具體的:記錄當前區間左端點的值,以及是否需要下傳,每次懶標記就更新這個值
-
求最小值
兩個修改一個查詢
code
#include <bits/stdc++.h> using namespace std; const int N = 2e5 + 5; const int INF = 1e8; int n, m, a[N], b[N], R, st, fl[N << 2], ad[N << 2]; int mn[N << 2], le[N << 2], ans[N]; struct Seg { int l, r; } T[N << 2]; #define ls (rt << 1) #define rs (rt << 1 | 1) inline void Up(int rt) { mn[rt] = min(mn[ls], mn[rs]); le[rt] = le[ls]; } inline void down(int rt) { if (fl[rt]) { register int L = le[rt]; fl[ls] = 1, ad[ls] = 0, mn[ls] = le[ls] = L; fl[rs] = 1, ad[rs] = 0, mn[rs] = le[rs] = L + T[rs].l - T[rt].l; } else if (ad[rt]) { ad[ls] += ad[rt], mn[ls] += ad[rt], le[ls] += ad[rt]; ad[rs] += ad[rt], mn[rs] += ad[rt], le[rs] += ad[rt]; } ad[rt] = fl[rt] = 0; } void bui(int l, int r, int rt) { T[rt].l = l, T[rt].r = r; if (l == r) return; register int mid = l + r >> 1; bui(l, mid, ls), bui(mid + 1, r, rs); } void add(int ql, int qr, int rt) { if (ql <= T[rt].l && T[rt].r <= qr) { ++mn[rt], ++ad[rt], ++le[rt]; return; } down(rt); if (ql <= T[ls].r) add(ql, qr, ls); if (qr >= T[rs].l) add(ql, qr, rs); Up(rt); } void upd(int ql, int qr, int L, int rt) { if (ql <= T[rt].l && T[rt].r <= qr) { le[rt] = mn[rt] = L + T[rt].l - ql + 1; fl[rt] = 1; return; } down(rt); if (ql <= T[ls].r) upd(ql, qr, L, ls); if (qr >= T[rs].l) upd(ql, qr, L, rs); Up(rt); } int ask(int p, int rt) { if (T[rt].l == T[rt].r) return mn[rt]; down(rt); return p <= T[ls].r ? ask(p, ls) : ask(p, rs); } #undef ls #undef rs int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) scanf("%d%d", &a[i], &b[i]); bui(1, m, 1); for (int i = 1; i <= n; i++) ans[i] = INF; for (int i = 1, L; i <= n; i++) { if (a[i] > 1) add(1, a[i] - 1, 1); if (b[i] < m) add(b[i] + 1, m, 1); if (a[i] > 1) L = ask(a[i] - 1, 1); else L = INF; upd(a[i], b[i], L, 1); ans[i] = min(ans[i], mn[1]); if (ans[i] == INF) break; } for (int i = 1; i <= n; i++) printf("%d\n", ans[i] < INF ? ans[i] : -1); }
彩色蠟筆
題意
\(n\) 只由 \(R_i,G_i,B_i\) 表示的彩色蠟筆,
\(i,j\) 兩隻蠟筆的色彩差異度為 \(\max(|R_i-R_j|,|G_i-G_j|,|B_i-B_j|)\)
選出 \(K\) 只蠟筆組成的子序列(不用連續),使差異度最小
\(R_i,G_i,B_i\le255,n\le 10^5\)
sol
三維字首和,值域小,可將問題轉為判斷性問題。
求三維空間內的點是否 \(\ge K\)
- 二分答案,列舉 \(R,G,B\) 上界,\(O(1)\) 判斷,總 \(O(255^3\log 255)\)
- 對一維,如 \(R\) 使用雙指標,列舉 \(G,B\)
code
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int M = 260;
int n, K, s[M][M][M], ans = 1e9, L, R;
inline bool chk() {
register int x = R - L + 1;
for (int i = x; i <= 256; i++)
for (int j = x; j <= 256; j++)
if (s[R][i][j] - s[L - 1][i][j] - s[R][i - x][j] - s[R][i][j - x] + s[L - 1][i - x][j] +
s[L - 1][i][j - x] + s[R][i - x][j - x] - s[L - 1][i - x][j - x] >=
K)
return 1;
return 0;
}
int main() {
scanf("%d%d", &n, &K);
for (int i = 1, x, y, z; i <= n; i++) scanf("%d%d%d", &x, &y, &z), s[x + 1][y + 1][z + 1]++;
for (int i = 1; i <= 256; i++)
for (int j = 1; j <= 256; j++)
for (int k = 1; k <= 256; k++)
s[i][j][k] += s[i - 1][j][k] + s[i][j - 1][k] + s[i][j][k - 1] - s[i - 1][j - 1][k] -
s[i - 1][j][k - 1] - s[i][j - 1][k - 1] + s[i - 1][j - 1][k - 1];
for (L = 1, R = 1; R <= 256; R = max(R, ++L)) {
while (R <= 256 && !chk()) R++;
if (R <= 256)
ans = min(ans, R - L);
}
printf("%d", ans);
}
圖的數量
題意
\(n\) 個點, \(m\) 條未標號邊,且滿足下列條件:
- 沒有自環
- 每個點度最大為 2
- 最大連通塊剛好 \(L\) 個點
求圖的數量, \(\mod 10^9+7\)
sol
-
度不超過 2,即不是環就是鏈
-
最大連通塊數量恰好為 \(K\) ,不好求,考慮字首和做差
若 \(F_k\) 為最大連通塊大小不超過 \(k\) 的數量,則答案為 \(F_k-F_{k-1}\)
設 \(f_{i,j}\) 為用了 \(i\) 個點, \(j\) 條邊的滿足條件的圖的數量
-
\(f_{i,j}\rightarrow f_{i+1,j}\) ,將 \(i\) 單獨做連通塊
-
若點 \(i\) 在一條長度為 \(k(2\le k\le L)\) 的鏈上,
則 \(f_{i,j}\times C_{n-i-1}^{k-1}\times \dfrac{k!}{2}\rightarrow f_{i+k,j+k-1}\)
即該點必選,再從 \(n-i-1\) 中選 \(k-1\) 個點,選出的點組成 \(\dfrac{k!}{2}\) 條鏈
-
點 \(i\) 在長度為 2 的鏈上,則 \(f_{i,j}\times(n-i-1)\rightarrow f_{i+2,j+2}\)
-
該點在長度為 \(k(3\le k\le L)\) ,則
\(f_{i,j}\cdot C_{n-i-1}^{k-1}\cdot\frac{(k-1)!}{2}\rightarrow f_{i+k,j+k}\)
即可求出 \(F_k\) ,同理求出 \(F_{k-1}\)
記得逆元
code
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const LL P = 1e9 + 7;
const LL inv2 = 500000004;
int n, m, MX;
LL C[1005][1005], f[305][305], fac[1005], fR, fL;
inline int get(int L) {
if (L == 1)
return 0;
memset(f, 0, sizeof(f));
f[0][0] = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j <= m; j++) {
(f[i + 1][j] += f[i][j]) %= P;
if (i + 2 <= n && j + 2 <= m)
(f[i + 2][j + 2] += f[i][j] * (n - i - 1) % P) %= P;
for (int k = 2; k <= L && i + k <= n; k++) {
if (j + k - 1 <= m)
(f[i + k][j + k - 1] += f[i][j] * C[n - i - 1][k - 1] % P * fac[k] % P * inv2 % P) %= P;
if (k > 2 && j + k <= m)
(f[i + k][j + k] += f[i][j] * C[n - i - 1][k - 1] % P * fac[k - 1] % P * inv2 % P) %= P;
}
}
}
return f[n][m];
}
int main() {
C[0][0] = fac[0] = 1;
for (int i = 1; i <= 1000; i++) {
C[i][0] = 1, fac[i] = fac[i - 1] * i % P;
for (int j = 1; j <= i; j++) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % P;
}
scanf("%d%d%d", &n, &m, &MX);
if (MX == 1)
return puts("0"), 0;
// cout << get(MX) << ' ' << get(MX - 1) << endl;
printf("%lld", (get(MX) - get(MX - 1) + P) % P);
}
糾結的數
題意
求第 \(n\) 小的正整數 \(X\) ,滿足 \(X\) 的最小素因子為 \(P\) ,若 \(X\ge 10^9\) ,輸出 0
sol
- \(n=1\) 則直接輸出 \(p\) ,否則答案最小為 \(p^2\) ,若不為 0 則 \(p\le \sqrt{10^9}\) ,範圍縮小
運用到了拼盤的思路
-
\(p\ge100\) 可以用小於 \(P\) 的質數暴力標記 \(P\) 的倍數,
複雜度為 \(\dfrac{10^9}{2p}+\dfrac{10^9}{3p}+\dfrac{10^9}{5p}\cdots\) ,略小於 \(\ln \dfrac{10^9}{p}\)
-
\(p< 100\) 時,暴力的複雜度不可取,但是 100 以內只有 25 個質數
可以二分 \(p\) 的倍數 \(X\) ,求出能被小於 \(p\) 的素數整除的數,可以容斥解決
複雜度 \(O(2^{25}\log 10^9)\)
code
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int INF = 1e9;
const int SQ = 31623;
int n, P, mx, ans, tmp, mid;
bitset<20000000> mark;
int pr[SQ + 5], cnt, vis[SQ + 5];
void dfs(int i, int s, int op) {
if (pr[i] == P) {
tmp += op * mid / s;
return;
}
dfs(i + 1, s, op);
if (s <= mid / pr[i])
dfs(i + 1, s * pr[i], -op);
}
int main() {
for (int i = 2; i <= SQ; i++) {
if (!vis[i])
pr[++cnt] = i;
for (int j = 1; j <= cnt && i * pr[j] <= SQ; j++) {
vis[i * pr[j]] = 1;
if (i % pr[j] == 0)
break;
}
}
scanf("%d%d", &n, &P);
if (n == 1)
return printf("%d", P), 0;
mx = INF / P;
if (P > mx)
return puts("0"), 0;
if (P >= 100) {
for (int i = 1; i <= cnt && pr[i] < P; i++)
for (int j = pr[i]; j <= mx; j += pr[i]) mark[j] = 1;
for (int i = 1, tc = 0; i <= mx; i++) {
if (!mark[i])
++tc;
if (tc == n) {
ans = i * P;
break;
}
}
printf("%d", ans);
} else {
int l = 2, r = mx + 1, res;
while (l <= r) {
mid = l + r >> 1;
tmp = 0, dfs(1, 1, 1);
if (tmp >= n)
r = mid - 1, res = mid;
else
l = mid + 1;
}
printf("%d", res > mx ? 0 : res * P);
}
}
刪牌遊戲
題意
\(3\times n\) 張牌,牌上數字為 \(A_i\) ,刪 \(n-1\) 次牌
每次從左邊 5 張任意刪去 3 張,若這 3 張牌數字相同可以得 1 分
最後 3 張牌數字相同可以再得 1 分,求最大得分
\(n\le 2000\)
sol
設 \(dp_{i,x,y}\) 為第 \(i\) 輪時之前剩下 \(x,y\) 的最大得分,此時若三、四、五張牌為 \(A,B,C\)
則 \(f_{i+1,x',y'}=\max f_{i,x,y}+eq\) ,其中 \(eq\) 表示剩下 3 張是否相等, \(x',y'\) 表示在 \(x,y,A,B,C\) 中任選 2 張
一次轉移 \(C_5^2\) ,總 \(O(C_5^2 n^3)\) ,光榮 TLE
可以壓掉 \(i\) ,設狀態為 \(f_{x,y}\) 表示最左邊剩下 \(x,y\) 時的最大得分
狀態無法壓縮,需要分類討論,減少列舉的狀態數
-
\(x',y'\) 就是 \(x,y\) ,若 \(A=B=C\) ,答案整體加 1,可用一個變數 \(plus\) 記錄
-
\(x',y'\) 有一個是 \(A,B,C\) 中的值,假設 \(x'=x,y'=A\) (最多 6 種情況)
\(f_{x',y'}=f_{x',A}=\max(f_{x',y}+eq)\) ,只需列舉 \(y\) 。預處理 \(\max dp_{x',y}\) ,複雜度 \(O(n)\)
-
\(x',y'\) 都是 \(A,B,C\) 中的值,假設 \(x'=A,y'=B\) (最多 3 種情況)
\(f_{x',y'}=f_{A,B}=\max (f_{C,C}+1,f_{x,y})\),複雜度為 \(O(1)\)
-
可用一個數組 \(g\) 輔助更新,每次 \(g_{x,y}\) 改變就存下 \((x,y)\) 之後用於改 \(f_{x,y}\)
因為 \(g\) 每次改動最多 \(3\cp 3n\) 次,這樣可以避免列舉,使複雜度降到 \(O(n^2)\)
答案直接列舉最後兩個數,最後加上 \(plus\) 即可
總共 \(O(n^2)\)
code
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 2005;
int n, vl[N * 3], pls, f[N][N], g[N][N], mx[N], tx[N], tt, le, xx[N * 9], yy[N * 9], ans = -1e9;
inline void px(int &A, int &B, int &C) {
if (A > B) A ^= B ^= A ^= B;
if (A > C) A ^= C ^= A ^= C;
if (B > C) B ^= C ^= B ^= C;
}
inline void push(int x, int y) {
if (x > y) x ^= y ^= x ^= y;
tx[x] = max(tx[x], g[x][y]);
tx[y] = max(tx[y], g[x][y]);
xx[++le] = x, yy[le] = y;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= 3 * n; i++) scanf("%d", &vl[i]);
for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) f[i][j] = g[i][j] = -1e9;
f[vl[1]][vl[2]] = f[vl[2]][vl[1]] = 0;
mx[vl[1]] = mx[vl[2]] = 0;
for (int o = 1, a, b, c; o < n; o++) {
a = vl[3 * o], b = vl[3 * o + 1], c = vl[3 * o + 2];
le = 0;
if (a == b && b == c) { ++pls; continue; }
px(a, b, c);
for (int i = 1; i <= n; i++) tx[i] = mx[i];
for (int i = 1, x; i < 3 * o; i++) {
x = vl[i];
g[x][a] = g[a][x] = max(mx[x], f[x][b] + (b == c)), push(x, a);
g[x][b] = g[b][x] = max(mx[x], f[x][c] + (a == c)), push(x, b);
g[x][c] = g[c][x] = max(mx[x], f[x][a] + (a == b)), push(x, c);
}
tt = -1e9;
for (int i = 1; i <= n; i++) tt = max(tt, mx[i]);
g[a][b] = max(tt, f[c][c] + 1), push(a, b);
g[a][c] = max(tt, f[b][b] + 1), push(a, c);
g[b][c] = max(tt, f[a][a] + 1), push(b, c);
for (int i = 1; i <= n; i++) mx[i] = tx[i];
for (int i = 1, x, y; i <= le; i++) {
x = xx[i], y = yy[i];
f[x][y] = f[y][x] = max(f[x][y], g[x][y]);
}
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) ans = max(ans, f[i][j] + (i == j && j == vl[3 * n]));
printf("%d", ans + pls);
}