zoukankan      html  css  js  c++  java
  • JLOI2016 简要题解

    「JLOI2016」侦查守卫

    题意

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

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

    (m le n le 5 imes 10^5, d le 20, w_i le 1000)

    题解

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

    (f_{i, j})(i) 向下 (j) 层有未被监视的点的最小代价。

    (g_{i, j})(i) 向上 (j) 层都能被监视的最小代价。

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

    • 如果点 (u) 一定要被监视,那么令 (f_{u, 0} = g_{u, 0} = w_u) ,表示这个点被监视的代价。

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

    那么对于 (g) 有如下转移:

    [g_{u, i} = min{g_{u, i} + f_{v, i}, g_{v, i + 1} + f_{u, 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
    ", f[1][0]);
    
    	return 0;
    
    }
    

    「JLOI2016」方

    题意

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

    (n, m le 10^6, k le 2 imes 10^3)

    题解

    竟然还有这种 shit 题。

    (k = 0) 是我原来在 ( ext{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
    ", (ans % Mod + Mod) % Mod);
    
    	return 0;
    
    }
    

    「JLOI2016」成绩比较

    题意

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

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

    已知第一个人第 (i) 门学科的排名为 (r_i) ,即这门学科不低于 (n − r_i) 人的分数,但一定低于 (r_i−1) 人的分数。

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

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

    (n le 100, m le 100, u_i le 10^9)

    题解

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

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

    其实就是

    [g_x = {n - 1 choose x} prod_{i = 1}^{m} {n - 1 - x choose r_i - 1} A_i ]

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

    其实就是

    [A_i = sum_{j = 1}^{u_i} (u_i - j)^{r_i - 1} j^{n - r_i} ]

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

    最后不优化复杂度是 (mathcal O(n^2m)) 的,已经完全足够了。

    总结

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

    代码

    #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
    ", (ans + Mod) % Mod);
    
    	return 0;
    
    }
    
  • 相关阅读:
    hdu 3951(博弈规律)
    hdu 3537(博弈,翻硬币)
    hdu 3032(博弈sg函数)
    hdu 2897(威佐夫博奕变形)
    hdu 1527(威佐夫博奕)
    hdu 2516(斐波拉切博弈)
    FZU 2171(线段树的延迟标记)
    二叉数的遍历
    树和二叉树的互相转换
    树的存储
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/10363240.html
Copyright © 2011-2022 走看看