A
如果 问是不是大于 , 回答是,就把 的数覆盖一遍,否则把 的数覆盖一遍,否则把 的覆盖一遍。
如果一个数被覆盖大于一次,就不可能是他
赢当且仅当只有一个数覆盖次数小于等于
发现大于 的可以舍去,有效状态只有一段 ,一段 ,再一段 .
设 表示依次有 个 , 个 , 个 。决策单调性可以优化到 。
发现答案是 级别,而且关于第三维单调。就可以把第三维和答案交换,优化到 。
#include <bits/stdc++.h> using namespace std; const int N = 2e3 + 9; int n, dp[24][N][N]; int main() { cin >> n; memset(dp, 0xc0, sizeof(dp)); dp[0][0][0] = 1; dp[0][0][1] = dp[0][1][0] = 0; for (int r = 1; r <= 18; ++r) { for (int i = 0; i <= n; ++i) { for (int j = 0; i + j <= n; ++j) { int L = min(i, (1 << (r - 1)) - j); dp[r][i][j] = max(dp[r][i][j], dp[r - 1][i - L][j]); dp[r][i][j] = max(dp[r][i][j], (1 << (r - 1)) - j + dp[r - 1][i][j]); } } for (int j = 0; j <= n; ++j) { int la = j; for (int i = 0; i + j <= n; ++i) { bool flag = false; for (int l = la; l >= 0; --l) { if (dp[r - 1][i][l] >= j - l) { flag = true; dp[r][i][j] = max(dp[r][i][j], dp[r - 1][l][j - l]); la = l; break; } } if (!flag) break; } } } for (int i = 1; i <= n; ++i) { int now = 0; while (dp[now][i - 1][n - i + 1] < 0) ++now; printf("%d ", now); } return 0; }
B
考虑如何刻画涂黑第三个点可以免费第四个点这个条件。
仔细思考一下,发现等价于选择一个点 就表示加入了第 行和第 列。对于任意一个矩形,他所涉及的行 ,和列 ,只需要其中任意三条边,例如: ,就能表示这四个点。把所有情况枚举出来,发现这四个点联通是充要条件。
那么用点来表示行和列,涂黑一个点 即选择一条边 。需要求如何花费最小的代价使图联通,可知用最小生成树算法。
#include <bits/stdc++.h> using namespace std; const int N = 5e3 + 9; vector<int>had[100005]; int fa[N * 2]; int n, m, a, a1, b, c, d, p, s; int get(int x) { return x == fa[x] ? x : fa[x] = get(fa[x]); } int main() { scanf("%d%d%d%d%d%d%d", &n, &m, &a, &b, &c, &d, &p); s = n * m; a1 = (1ll * a * a * b + 1ll * a * c + d) % p; for (int i = 0; i < s; i++) { had[a1].push_back(i); a1 = (1ll * a1 * a1 * b + 1ll * a1 * c + d) % p; } for (int i = 0; i < n + m; i++) fa[i] = i; long long ans = 0; for (int i = 0; i < p; i++) { for (auto z : had[i]) { int x = z / m, y = z % m; if (get(x) != get(n + y)) { ans += i; fa[get(n + y)] = get(x); } } } printf("%lld", ans); return 0; }
C
从大到小枚举填入数的大小 ,找出 的行和列。
对于这些选出的行列的交点,填一个 可以满足两个条件。对于其他的,填一个 只能满足一次条件。发现这是一个最大匹配的问题,用匈牙利算法即可解决。假设对于数 ,有 个满足条件的行列,最大匹配数为 ,那么代价就是 。
#include <bits/stdc++.h> #define int long long using namespace std; const int N = 2e3 + 9; int n, m, k, u, v, b[N], c[N], fa[N], ans = 0; vector<int> e[N]; bool used[N]; bool dfs(int u) { for (int i = 0; i < e[u].size(); ++i) { int v = e[u][i]; if (used[v]) continue; used[v] = true; if (!fa[v] || dfs(fa[v])) { fa[v] = u; return true; } } return false; } signed main() { scanf("%lld%lld%lld", &n, &m, &k); for (int i = 1; i <= n; ++i) scanf("%lld", &b[i]), ans += b[i]; for (int i = 1; i <= n; ++i) scanf("%lld", &c[i]), ans += c[i]; while (m--) { scanf("%lld%lld", &u, &v); if (b[u] == c[v]) e[u].push_back(v); } for (int i = 1; i <= n; ++i) { memset(used, 0, sizeof used); ans -= dfs(i) * b[i]; } printf("%lld", ans); return 0; }
D
因为 ,所以考虑容斥 个格子是否必选。然后看行列对角线是否不能选。然后统计剩余各自的答案。
假设最后留下了 行 列,那么可选的格子就是 。但是对角线的限制比较麻烦。可知如果第 行第 列可以选,但是主对角线不能选,那么可选格子数要 。 同理。可以设三维 背包,每次看是否选择 行,列。然后根据对角线的状态来背包。
时间复杂度
#include <bits/stdc++.h> using namespace std; const int N = 35; const int P = 1e4 + 7; int C[N * N][N * N]; int n, K, m; int cnt[1 << 7]; int x[7], y[7]; int vx[N], vy[N]; int f[N][N][N * 2], g[N][N][N * 2]; int solve(int f0, int f1, int ban) { f[0][0][0] = 1; int S = 0; for (int l = 1, r = n; l <= r; l++, r--) { memset(g, 0, sizeof(g)); for (int i = 0; i <= S; i++) for (int j = 0; j <= S; j++) for (int k = 0; k <= 2 * S; k++) { if (!f[i][j][k]) continue; if (l != r) { for (int s1 = vx[l]; s1 <= 1; s1++) for (int s2 = vy[l]; s2 <= 1; s2++) for (int s3 = vx[r]; s3 <= 1; s3++) for (int s4 = vy[r]; s4 <= 1; s4++) { int ti = i + s1 + s3, tj = j + s2 + s4, tk = k; if (s1 && s2 && f0) tk++; if (s3 && s4 && f0) tk++; if (s1 && s4 && f1) tk++; if (s2 && s3 && f1) tk++; g[ti][tj][tk] = (g[ti][tj][tk] + f[i][j][k]) % P; } } else { for (int s1 = vx[l]; s1 <= 1; s1++) for (int s2 = vy[l]; s2 <= 1; s2++) { int ti = i + s1, tj = j + s2, tk = k; if (s1 && s2 && f0) tk++; else if (s1 && s2 && f1) tk++; g[ti][tj][tk] = (g[ti][tj][tk] + f[i][j][k]) % P; } } } if (l != r) S += 2; else S++; for (int i = 0; i <= S; i++) for (int j = 0; j <= S; j++) for (int k = 0; k <= 2 * S; k++) f[i][j][k] = g[i][j][k]; } int res = 0; for (int i = 0; i <= S; i++) for (int j = 0; j <= S; j++) for (int k = 0; k <= 2 * S; k++) { if (!f[i][j][k]) continue; if ((i + j) % 2 == 0) res = (res + 1ll * f[i][j][k] * C[i * j - k - ban][m]) % P; else res = (res - 1ll * f[i][j][k] * C[i * j - k - ban][m] % P + P) % P; } return res; } int F(int S) { memset(vx, 0, sizeof(vx)); memset(vy, 0, sizeof(vy)); int f0 = 0, f1 = 0; for (int i = 0; i < K; i++) if (S & (1 << i)) { vx[x[i]] = 1; vy[y[i]] = 1; if (x[i] == y[i]) f0 = 1; if (x[i] + y[i] == n + 1) f1 = 1; } int res = 0; res = (res + solve(0, 0, cnt[S])) % P; if (!f0) res = (res - solve(1, 0, cnt[S]) + P) % P; if (!f1) res = (res - solve(0, 1, cnt[S]) + P) % P; if (!f0 && !f1) res = (res + solve(1, 1, cnt[S])) % P; return res; } int main() { for (int i = 0; i < N * N; i++) { C[i][0] = C[i][i] = 1; for (int j = 1; j < i; j++) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % P; } cin >> n >> K >> m; for (int i = 0; i < K; i++) cin >> x[i] >> y[i]; for (int i = 0; i < (1 << K); i++) cnt[i] = cnt[i >> 1] + (i & 1); int ans = 0; for (int i = 0; i < (1 << K); i++) { m -= cnt[i]; if (cnt[i] & 1) ans = (ans - F(i) + P) % P; else ans = (ans + F(i)) % P; m += cnt[i]; } cout << ans << endl; return 0; }
E
不得不说,对于这种题优先考虑打表是一个不错的选择。打表发现对于任意正整数 都满足。
设 整理得 。枚举 ,由韦达定理知
固定 ,计算上面答案对数。
若 满足条件, 也满足条件。因为 顺序不影响,所以 也合法。
发现这很符合打表找到的规律。每次得到 就可以得到 。利用打表得到的特例 递推下去,存在数组里排序二分求解。
#include <bits/stdc++.h> #define int long long using namespace std; const int lim = 1e18; int T, n, a[5000010]; signed main() { a[n++] = 1; for (int i = 2; i * i * i <= lim; i++) { int x = i, y = i * i * i; a[n++] = y; while (y <= (lim + x) / i / i) { x = i * i * y - x; swap(x, y); a[n++] = y; } } sort(a, a + n); scanf("%d", &T); while (T--) { int k; scanf("%lld", &k); printf("%lld\n", upper_bound(a, a + n, k) - a); } }
F
直接暴力枚举 即可。但是要保证得到数的方式一定涉及分数,即不整除。
#include <bits/stdc++.h> using namespace std; const double eps = 1e-5; int n, m, cnt; bool ok, no, used[4]; double a[4]; struct node { int a[4]; } v; vector<node>A; void dfs2(int c, bool f) { if (c == n && fabs(a[0] - m) < eps) { ok = 1; if (!f) no = 1; return ; } for (int i = 0; i < n; i++) if (fabs(a[i] - floor(a[i])) > eps) f = 1; for (int i = 0; i < n; i++) for (int j = i + 1; j < n; j++) { if (used[i] || used[j]) continue; double x = a[i], y = a[j]; used[j] = 1; a[i] = x + y, dfs2(c + 1, f); a[i] = x - y, dfs2(c + 1, f); a[i] = y - x, dfs2(c + 1, f); a[i] = x * y, dfs2(c + 1, f); if (fabs(y) > eps) a[i] = x / y, dfs2(c + 1, f); if (fabs(x) > eps) a[i] = y / x, dfs2(c + 1, f); a[i] = x, a[j] = y, used[j] = 0; } } void dfs1(int x, int last) { if (x == n) { ok = no = false; dfs2(1, false); if (ok && !no) { v.a[0] = (int)a[0]; v.a[1] = (int)a[1]; v.a[2] = (int)a[2]; v.a[3] = (int)a[3]; A.push_back(v); } return ; } for (int i = last; i <= 13; i++) a[x] = i, dfs1(x + 1, i); } int main() { scanf("%d%d", &n, &m); dfs1(0, 1); printf("%d\n", cnt = A.size()); for (int i = 0; i < cnt; i++) { for (int j = 0; j < n; j++) printf("%d ", A[i].a[j]); printf("\n"); } }
I
对于操作 ,利用差分可以将区间异或转为单点异或。具体的,作 的差分数组 ,有 ,则 。 每次区间 异或 改为 单点异或 ,最后还原即可。
而操作 乍一看很 ,但是按位考虑会发现很有规律。
如看 的第三位。依次是
发现每 位一个循环。那么不妨以 来分类,构造一个表格,第 行第 列表示 。会发现需要异或的是由一个矩阵加上剩下多出来的一个宽为 的矩阵。在二维差分数组上打标记,最后二维前缀和回去。最终 其中 为所有二进制位下,二位前缀和后的异或值。
时间复杂度 ,空间复杂度 ,当然可以用 bitset 优化到 的空间。
#include <bits/stdc++.h> using namespace std; const int N = 6e5 + 9; int n, q; int a[N], b[N]; int d[N][22]; int main() { scanf("%d%d", &n, &q); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); while (q--) { int op, l, r, x; scanf("%d%d%d%d", &op, &l, &r, &x); if (op == 0) b[l] ^= x, b[r + 1] ^= x; else { for (int i = 0; i <= 19; i++) if ((x & (1 << i)) && (l + (1 << i)) - 1 <= r) { d[l][i] ^= 1; b[l] ^= (x >> i) << i; b[l + (1 << i)] ^= (x >> i) << i; l += (1 << i); x += (1 << i); } for (int i = 19; i >= 0; i--) if ((l + (1 << i) - 1) <= r) { d[l][i] ^= 1; b[l] ^= (x >> i) << i; b[l + (1 << i)] ^= (x >> i) << i; l += (1 << i); x += (1 << i); } } } for (int i = 19; i >= 1; i--) for (int j = 1; j <= n; j++) { if (!d[j][i]) continue; d[j][i - 1] ^= 1; if (j + (1 << i - 1) <= n) { d[j + (1 << i - 1)][i - 1] ^= 1; b[j + (1 << i - 1)] ^= (1 << i - 1); if (j + (1 << i) <= n) b[j + (1 << i)] ^= (1 << i - 1); } } for (int i = 1; i <= n; i++) { b[i] ^= b[i - 1]; printf("%d ", a[i]^b[i]); } return 0; }
J
如果不存在限制,三角形数目是 ,那考虑容斥用总数减去不合法的个数。
因为两两之间都会有边,那么枚举一个顶点,他的黑边数乘上白边数就是包含他的不合法的三角形的个数。观察一个不合法的三角形,会有一条边和另外两条边颜色不一样。会在这一条边的两个端点都计算一次答案。所以最终计算的不合法的个数要除以 。最后输出 减去不合法的个数
#include <bits/stdc++.h> using namespace std; const int N = 8e3 + 9; namespace GenHelper { unsigned z1,z2,z3,z4,b,u; unsigned get() { b=((z1<<6)^z1)>>13; z1=((z1&4294967294U)<<18)^b; b=((z2<<2)^z2)>>27; z2=((z2&4294967288U)<<2)^b; b=((z3<<13)^z3)>>21; z3=((z3&4294967280U)<<7)^b; b=((z4<<3)^z4)>>12; z4=((z4&4294967168U)<<13)^b; return (z1^z2^z3^z4); } bool read() { while (!u) u = get(); bool res = u & 1; u >>= 1; return res; } void Srand(int x) { z1=x; z2=(~x)^0x233333333U; z3=x^0x1234598766U; z4=(~x)+51; u = 0; } } using namespace GenHelper; bitset<N>e[N]; int main() { int n, seed ; long long ans = 0, res = 0; cin >> n >> seed; ans = 1ll * n * (n - 1) * (n - 2); Srand(seed); for (int i = 0; i < n; i++) for (int j = i + 1; j < n; j++) e[j][i] = e[i][j] = read(); for (int i = 0; i < n; i++) { int a = e[i].count(); res += 1ll * a * (n - 1 - a); } printf("%lld\n", (ans / 6 - (res / 2))); return 0; }
全部评论
(0) 回帖