最小步数
题意
\(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\) 上界,总 \(O(255^3)\)
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\times 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);
}