JLOI2016 簡要題解

「JLOI2016」偵查守衛

題意

有一個 \(n\) 個點的樹,有 \(m\) 個關鍵點須要被監視。能夠在其中一些點上插眼,在 \(i\) 號點上放眼須要花費 \(w_i\) 的代價,能夠監視距離 \(i\) 不超過 \(d\) 的全部點。html

問將全部關鍵點都被監視所須要花費的最小代價。c++

\(m \le n \le 5 \times 10^5, d \le 20, w_i \le 1000\)git

題解

\(d\) 很小,不難想到 \(\mathcal O(nd)\)\(dp\)優化

\(f_{i, j}\)\(i\) 向下 \(j\) 層有未被監視的點的最小代價。spa

\(g_{i, j}\)\(i\) 向上 \(j\) 層都能被監視的最小代價。debug

狀態很容易想。。可是轉移就很噁心了。。code

  • 若是點 \(u\) 必定要被監視,那麼令 \(f_{u, 0} = g_{u, 0} = w_u\) ,表示這個點被監視的代價。

而後從它的兒子 \(v\) 轉移狀態上來。htm

那麼對於 \(g\) 有以下轉移:blog

\[ g_{u, i} = \min\{g_{u, i} + f_{v, i}, g_{v, i + 1} + f_{u, i + 1}\} \]get

前者意味着對於子樹 \(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 \times m\) 的方格圖,有 \((n + 1) \times (m + 1)\) 個格點,禁止其中 \(k\) 個格點做爲端點,問剩下的圖有多少個格點正方形。(斜的也算)

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

題解

居然還有這種 shit 題。

\(k = 0\) 是我原來在 \(\text{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\) 門的分數爲不大於 \(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\n", (ans + Mod) % Mod);

    return 0;

}
相關文章
相關標籤/搜索