• 省选测试50


    A 矩形

    题目大意 : 从n×m点中选k个点在一条直线上的方案数

    • 莫比乌斯反演,把步骤记下来,不然老是忘(我太菜了)

    • 横着和竖着的都好算,斜着的可以分为左斜和右斜,枚举这k个点所在线段所在的矩形,对于一个长宽i,j的矩形,对角线上的点数是gcd(i,j)+1,把两端确定,中间选k-2就不会重复,所以可以得到一个n2的式子(假设n<=m)

    [ans=ninom{m}{k}+minom{n}{k}+2sum_{i=1}^{n}sum_{i=1}^{m}inom{gcd(i,j)-1}{k-2}(n-i)(m-j) ]

    • 对后面的两个求和的那个式子进行莫比乌斯反演

    [egin{align*} ans'&=sum_{i=1}^{n}sum_{j=1}^{m}inom{gcd(i,j)-1}{k-2}(n-i)(m-j) \&= sum_{p=1}^{n}inom{p-1}{k-2}sum_{i=1}^{n}sum_{j=1}^{m}[gcd(i,j)=p](n-i)(m-j) \&= sum_{p=1}^{n}inom{p-1}{k-2}sum_{i=1}^{left lfloor frac{n}{p} ight floor}sum_{j=1}^{left lfloor frac{m}{p} ight floor}[gcd(i,j)=1](n-pi)(m-pj) \&= sum_{p=1}^{n}inom{p-1}{k-2}sum_{i=1}^{left lfloor frac{n}{p} ight floor}sum_{j=1}^{left lfloor frac{m}{p} ight floor}sum_{d|gcd(i,j)}mu (d)(n-pi)(m-pj) \&= sum_{p=1}^{n}inom{p-1}{k-2}sum_{d=1}^{left lfloor frac{n}{p} ight floor}mu (d)sum_{d|i}^{left lfloor frac{n}{p} ight floor}sum_{d|j}^{left lfloor frac{m}{p} ight floor}(n-pi)(m-pj) \&= sum_{p=1}^{n}inom{p-1}{k-2}sum_{d=1}^{left lfloor frac{n}{p} ight floor}mu (d)sum_{i=1}^{left lfloor frac{n}{pd} ight floor}(n-pdi)sum_{j=1}^{left lfloor frac{m}{pd} ight floor}(m-pdj) end{align*}]

    • 接下来正常的话就要设T=pd,然后继续推,不过这道题到这里就可以(O(nln n))做出来了,因为后面两个求和是显然可以用等差数列求和公式优化到O(1)

    • 预处理组合数和莫比乌斯函数(mu (d)),当x有相同质因子,函数值为0,否则是-1的质因子个数次方

    Code

    Show Code
    #include <cstdio>
    #include <algorithm>
    
    using namespace std;
    const int N = 1e5 + 5, M = 323232323;
    
    int read(int x = 0, int f = 1, char c = getchar()) {
        for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
        for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
        return x * f;
    }
    
    bool v[N];
    int n, m, k, pri[N], tot, mu[N], fac[N], inv[N], ans;
    
    int Pow(int a, int k, int ans = 1) {
        for (; k; k >>= 1, a = 1ll * a * a % M)
            if (k & 1) ans = 1ll * ans * a % M;
        return ans;
    }
    
    void Init(int n) {
        mu[1] = fac[0] = 1;
        for (int i = 2; i < n; ++i) {
            if (!v[i]) pri[++tot] = i, mu[i] = M-1;
            for (int j = 1; j <= tot && i * pri[j] <= n; ++j) {
                v[i*pri[j]] = 1;
                if (i % pri[j] == 0) break;
                mu[i*pri[j]] = M-mu[i];
            }
        }
        for (int i = 1; i <= n; ++i)
            fac[i] = 1ll * fac[i-1] * i % M;
        inv[n] = Pow(fac[n], M - 2);
        for (int i = n; i >= 1; --i)
            inv[i-1] = 1ll * inv[i] * i % M;
    }
    
    int C(int n, int m) {
        return 1ll * fac[n] * inv[m] % M * inv[n-m] % M;
    }
    
    int Cal(int n, int pd) {
        return (-1ll * (n / pd) * (n / pd + 1) / 2 * pd % M + M + 1ll * n / pd * n) % M;
    }
    
    int main() {
        freopen("a.in", "r", stdin);
        freopen("a.out", "w", stdout);
        n = read(); m = read(); k = read();
        if (k == 1) return printf("%lld
    ", 1ll * n * m % M), 0;
        if (n > m) swap(n, m); Init(m);
        for (int p = k-1; p <= n; ++p) {
            int sum = 0;
            for (int d = n / p; d >= 1; --d)
                if ((sum += 1ll * Cal(n, p * d) * Cal(m, p * d) % M * mu[d] % M) >= M) sum -= M;
            if ((ans += 1ll * C(p - 1, k - 2) * sum % M) >= M) ans -= M;
        }
        printf("%lld
    ", (1ll * n * C(m, k) + 1ll * m * C(m, k) + 2 * ans) % M);
        return 0;
    }
    

    B 覆盖

    题目大意 : 一个点覆盖一个区间的代价是到区间两端的距离之差,问在用点最少的情况下覆盖所有线段的最小代价

    • 最少用点好求,按右端点排序,每次给在第一个没选的区间的右端点放一个点

    • 设p[i]表示上述贪心第i个点所放的位置,而在最小代价的情况下第i个点一定会放在[p[i-1]+1,p[i]],于是就可以dp了

    • 设对于每段,f[i]表示这段放在这个点上的最小代价,转移点只在上个段

    • 发现有决策单调性,可以(nlog n)解决

    Code

    Show Code
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    typedef long long ll;
    const int N = 1e6 + 5;
    
    int read(int x = 0, int f = 1, char c = getchar()) {
        for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
        for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
        return x * f;
    }
    
    ll s[N], f[N], ans = 1e18;
    int n, b[N], c[N], cnt, p[N];
    
    struct Node {
        int l, r;
    }a[N];
    
    bool operator < (const Node &a, const Node &b) {
        return a.r < b.r;
    }
    
    ll Cal(int l, int r) {
        if (l < b[r-1]) return 1e18;
        int mid = l + r >> 1;
        return (s[mid] - s[l]) - 1ll * (c[mid] - c[l]) * l + 1ll * (c[r] - c[mid]) * r - (s[r] - s[mid]) + f[l];
    }
    
    void Solve(int pl, int pr, int l, int r) {
        if (pl > pr || l > r) return;
        int mid = l + r >> 1, p = pl;
        for (int i = pl; i <= pr; ++i) {
            ll tmp = Cal(i, mid);
            if (tmp < f[mid]) f[mid] = tmp, p = i;
        }
        Solve(pl, p, l, mid-1); Solve(p, pr, mid+1, r); 
    }
    
    int main() {
        freopen("b.in", "r", stdin);
        freopen("b.out", "w", stdout);
        n = read();
        for (int i = 1; i <= n; ++i) {
            int l = read() * 2, r = read() * 2, mid = l + r >> 1;
            a[i] = (Node) {l, r}; b[r] = max(b[r], l);
            s[mid] += mid; c[mid]++;
        }
        sort(a + 1, a + n + 1);
        for (int i = 1; i <= n; ++i)
            if (a[i].l > p[cnt]) p[++cnt] = a[i].r;
        memset(f + 1, 0x3f, (n *= 2) * 8);
        for (int i = 1; i <= n; ++i) {
            b[i] = max(b[i], b[i-1]);
            s[i] += s[i-1]; c[i] += c[i-1];
        }
        for (int i = 1; i <= p[1]; ++i) 
            f[i] = 1ll * c[i] * i - s[i];
        for (int i = 2; i <= cnt; ++i)
            Solve(p[i-2] + 1, p[i-1], p[i-1] + 1, p[i]);
        for (int i = max(p[cnt-1]+1, b[n]); i <= p[cnt]; ++i)
            ans = min(ans, f[i] + (s[n] - s[i]) - 1ll * (c[n] - c[i]) * i);
        printf("%d %lld
    ", cnt, ans);
        return 0;
    }
    

    C 强壮

    题目大意 : 限制dfn序为i的点子树不小于a[i]

    • 题意可真是难懂,dfn不和a[i]放一起说,气死了

    • f[x][i]表示以x为根的子树dfn序最大的链的长度为i的方案数

    • 按dfn从小到大考虑可以接到i子树里的点,一定只能接在最右边的链上,

    • 所以f[x][i]×f[y][j]会对f[x][j+1~i+j-1]有贡献,暴力转移的话是n3

    • 可以看一个值会由谁转移来,发现f[y][j]固定后,f[x][i+j+1]只会由f[x][i~sz[x]]转移,所以做个后缀和就可以优化到n2

    Code

    Show Code
    #include <cstdio>
    #include <vector>
    
    using namespace std;
    const int N = 1e4 + 5, M = 323232323;
    
    int read(int x = 0, int f = 1, char c = getchar()) {
        for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
        for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
        return x * f;
    }
    
    vector<int> to[N];
    int n, a[N], stk[N], tp, f[N][N], sz[N], ans, g[N];
    
    void Dfs(int x) {
        sz[x] = 1; f[x][0] = 1;
        for (int k = 0, y; k < to[x].size(); ++k) {
            Dfs(y = to[x][k]);
            for (int i = sz[x] - 2; i >= 0; --i)
                if ((f[x][i] += f[x][i+1]) >= M) f[x][i] -= M;
            for (int i = 0; i < sz[x]; ++i)
                for (int j = 0; j < sz[y]; ++j)
                    if ((g[i+j+1] += 1ll * f[x][i] * f[y][j] % M) >= M) g[i+j+1] -= M;
            sz[x] += sz[y];
            for (int i = 0; i < sz[x]; ++i)
                f[x][i] = g[i], g[i] = 0;
        }
    }
    
    int main() {
        freopen("c.in", "r", stdin);
        freopen("c.out", "w", stdout);
        n = read(); read(); a[1] = n;
        for (int i = 2; i <= n; ++i) 
            a[i] = i + read() - 1;
        for (int i = n; i >= 1; --i) {
            if (a[i] > n) return puts("0"), 0;
            while (tp && stk[tp] <= a[i]) to[i].push_back(stk[tp--]);
            stk[++tp] = i;
        }
        Dfs(1);
        for (int i = 0; i < n; ++i)
            if ((ans += f[1][i]) >= M) ans -= M;
        printf("%d
    ", ans);
        return 0;
    }
    
  • 相关阅读:
    js 平坦化控制流
    js变量名混淆
    ERR_CERT_INVALID
    ERR_CERT_AUTHORITY_INVALID
    @babel/preset-env
    @babel/plugin-transform-runtime
    terminal
    @babel/plugin-proposal-class-properties
    Zotero
    随记 日后整理
  • 原文地址:https://www.cnblogs.com/shawk/p/14588813.html
Copyright © 2020-2023  润新知