JLOI2016 简要题解

「JLOI2016」侦查守卫

题意

有一个 n 个点的树,有 m 个关键点需要被监视。可以在其中一些点上插眼,在 i 号点上放眼需要花费 wi 的代价,可以监视距离 i 不超过 d 的所有点。

问将所有关键点都被监视所需要花费的最小代价。

mn5×105,d20,wi1000

题解

d 很小,不难想到 O(nd)dp

fi,ji 向下 j 层有未被监视的点的最小代价。

gi,ji 向上 j 层都能被监视的最小代价。

状态很容易想。。但是转移就很恶心了。。

  • 如果点 u 一定要被监视,那么令 fu,0=gu,0=wu ,表示这个点被监视的代价。

然后从它的儿子 v 转移状态上来。

那么对于 g 有如下转移:

gu,i=min{gu,i+fv,i,gv,i+1+fu,i+1}

前者意味着对于子树 v 向上 j 层都被此处覆盖了,后者就是考虑有一个儿子 v 能向上延伸 j+1 层。

然后记得后缀 chkmin

对于 f 你就前缀 chkmin 并且注意 f[u][0] = g[u][0]

总结

对于有些最优化 dp ,可以记一下前缀 or 后缀 min 的答案,转移会简单很多。。

代码

#include <bits/stdc++.h> #define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl using namespace std; template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; } inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("2024.in", "r", stdin); freopen ("2024.out", "w", stdout); #endif } const int N = 500100, inf = 0x3f3f3f3f; int n, d, f[N][25], g[N][25]; vector<int> G[N]; int w[N]; bool App[N]; void Dp(int u, int fa) { if (App[u]) f[u][0] = g[u][0] = w[u]; For (i, 1, d) g[u][i] = w[u]; g[u][d + 1] = inf; for (int v : G[u]) if (v != fa) { Dp(v, u); Fordown (i, d, 0) g[u][i] = min(g[u][i + 1], min(g[u][i] + f[v][i], g[v][i + 1] + f[u][i + 1])); f[u][0] = g[u][0]; For (i, 1, d) f[u][i] = min(f[u][i] + f[v][i - 1], f[u][i - 1]); } } int main () { File(); n = read(); d = read(); For (i, 1, n) w[i] = read(); For (i, 1, read()) App[read()] = true; For (i, 1, n - 1) { int u = read(), v = read(); G[u].push_back(v); G[v].push_back(u); } Dp(1, 0); printf ("%d\n", f[1][0]); return 0; }

「JLOI2016」方

题意

给你 n×m 的方格图,有 (n+1)×(m+1) 个格点,禁止其中 k 个格点作为端点,问剩下的图有多少个格点正方形。(斜的也算)

n,m106,k2×103

题解

竟然还有这种 shit 题。

k=0 是我原来在 NOIp 模拟赛里面搬的一道水题。。。

然后有禁止的限制,不难想到容斥,然后后面的细节就贼烦了。

具体可以看看 zzq 的博客 ,因为太麻烦了,不想讲了。

前面那些优化并不是重点,重点在于如何容斥。。

你可以考虑对于单个正方形会被统计几次,然后依次凑系数就行啦。

zzq 那个按顺序枚举的方法十分优秀,不需要开 hash 表存状态,直接最后除掉一个正方形被算进去的次数就行啦。

总结

容斥考虑对于一个点会被计算几次,然后凑系数就行了。

代码

#include <bits/stdc++.h> #define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl #define epb emplace_back using namespace std; template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; } inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("2025.in", "r", stdin); freopen ("2025.out", "w", stdout); #endif } const int Mod = 1e8 + 7; using ll = long long; using PII = pair<int, int>; const int N = 2010; int n, m, k, x[N], y[N]; inline int Count(int l, int r, int d) { if (!l || !r || !d) return 0; int res = 0, upp = min(l + r, d), pos[3] = {l + 1, r + 1, upp}, cl = 1; sort(pos, pos + 3); Rep (i, 3) { int cr = pos[i]; if (cr > upp) break; if (cr < 2 || cl == cr) continue; ++ cl; int vl = min(r, cl - 1) - max(cl - l, 1) + 1, vr = min(r, cr - 1) - max(cr - l, 1) + 1; res = (res + 1ll * (vl + vr) * (cr - cl + 1) / 2) % Mod; cl = cr; } return res; } inline int Calc(int u, int d, int l, int r) { return (Count(l, r, d) + Count(l, r, u) + Count(u, d, l) + Count(u, d, r) + min(l, d) + min(d, r) + min(r, u) + min(u, l)) % Mod; } const double eps = 1e-9; inline bool check(double x, double y) { if (fabs(x - int(x + 0.5)) >= eps || fabs(y - int(y + 0.5)) >= eps) return false; int nx = int(x + 0.5), ny = int(y + 0.5); return 0 <= nx && nx <= n && 0 <= ny && y <= m; } inline int check(PII S) { return 0 <= S.first && S.first <= n && 0 <= S.second && S.second <= m; } set<PII> T; struct Node { int x, y; } P[N]; int main () { File(); n = read(); m = read(); k = read(); int ans = 0; For (i, 1, min(n, m)) ans = (ans + 1ll * i * (n - i + 1) % Mod * (m - i + 1)) % Mod; For (i, 1, k) { P[i].x = x[i] = read(), P[i].y = y[i] = read(); T.insert(make_pair(x[i], y[i])); ans = (ans - Calc(x[i], n - x[i], y[i], m - y[i]) + Mod) % Mod; } sort(P + 1, P + k + 1, [&](Node a, Node b) { return a.x != b.x ? a.x < b.x : a.y < b.y; } ); For (i, 1, k) x[i] = P[i].x, y[i] = P[i].y; int cnt3 = 0, cnt4 = 0; For (i, 1, k) For (j, i + 1, k) { { double midx = (x[i] + x[j]) / 2.0, midy = (y[i] + y[j]) / 2.0, gapx = x[i] - midx, gapy = y[i] - midy; if (check(midx - gapy, midy + gapx) && check(midx + gapy, midy - gapx)) { ++ ans; } } for (int dir = -1; dir <= 1; dir += 2) { int tx = x[i] - x[j], ty = y[i] - y[j]; PII T1 = make_pair(x[i] - ty * dir, y[i] + tx * dir); PII T2 = make_pair(x[j] - ty * dir, y[j] + tx * dir); if (!check(T1) || !check(T2)) continue; int cnt = 0; ++ ans; if (T.find(T1) != T.end()) ++ cnt; if (T.find(T2) != T.end()) ++ cnt; cnt3 += (cnt >= 1); cnt4 += (cnt >= 2); } } ans -= cnt3 / 2 + cnt4 / 4; printf ("%d\n", (ans % Mod + Mod) % Mod); return 0; }

「JLOI2016」成绩比较

题意

n 个人 m 门学科,第 i 门的分数为不大于 ui 的一个正整数。

定义 A 碾压 B 当且仅当 A 的每门学科的分数都不低于 B 的该门学科的分数。

已知第一个人第 i 门学科的排名为 ri ,即这门学科不低于 nri 人的分数,但一定低于 ri1 人的分数。

求有多少种方案使得第一个人恰好碾压了 k 个人。

两种方案不同当且仅当存在两个人的分数不同。

n100,m100,ui109

题解

原来听 h10 讲广义容斥的时候讲过。。现在不会做了。。

gx 为第一个人至少碾压了 x 个人的方案数。

其实就是

gx=(n1x)i=1m(n1xri1)Ai

其中 Ai 为对于第 i 门来说,所有人从小到大排分数合法的方案数。其余的意义就十分明显了,首先是选人,然后选剩下的人填这门比第一个人高的方案数。

其实就是

Ai=j=1ui(uij)ri1jnri

这个显然是一个 n 次多项式,求出 n+1 个点值,利用拉格朗日插值即可在 O(n2) 的时间内插出来,可以利用连续点值的性质优化到 O(n)

最后不优化复杂度是 O(n2m) 的,已经完全足够了。

总结

计数题还是有很多式子可以形式化地写出来的。对于 至少 的方案数进行计算的时候,只需要考虑把不合法的位置全都用别的塞上去,那么剩下的就一定合法了。

代码

#include <bits/stdc++.h> #define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl using namespace std; template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; } inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("2026.in", "r", stdin); freopen ("2026.out", "w", stdout); #endif } const int N = 110, Mod = 1e9 + 7; int n, m, k, u[N], r[N]; inline int fpm(int x, int power) { int res = 1; for (; power; power >>= 1, x = 1ll * x * x % Mod) if (power & 1) res = 1ll * res * x % Mod; return res; } int x[N], y[N]; int Inter(int maxn, int p) { int res = 0; For (i, 1, maxn) { int cur = 1, coef = 1; For (j, 1, maxn) if (i != j) { cur = 1ll * cur * (p - x[j]) % Mod; coef = 1ll * coef * (x[i] - x[j]) % Mod; } res = (res + 1ll * y[i] * cur % Mod * fpm(coef, Mod - 2)) % Mod; } return res; } inline int Calc(int p) { For (i, 1, n + 1) x[i] = i, y[i] = (y[i - 1] + 1ll * fpm(u[p] - i, r[p] - 1) * fpm(i, n - r[p])) % Mod; return Inter(n + 1, u[p]); } int g[N], A[N], Comb[N][N]; int main () { File(); n = read(); m = read(); k = read(); For (i, 0, n) { Comb[i][0] = 1; For (j, 1, i) Comb[i][j] = (Comb[i - 1][j - 1] + Comb[i - 1][j]) % Mod; } For (i, 1, m) u[i] = read(); For (i, 1, m) r[i] = read(); For (i, 1, m) A[i] = Calc(i); For (i, 1, n - 1) { g[i] = Comb[n - 1][i]; For (j, 1, m) g[i] = 1ll * g[i] * Comb[n - 1 - i][r[j] - 1] % Mod * A[j] % Mod; } int ans = 0; For (i, k, n - 1) ans = (ans + ((i - k) & 1 ? -1ll : 1ll) * Comb[i][k] * g[i]) % Mod; printf ("%d\n", (ans + Mod) % Mod); return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/10363240.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(328)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示