省选测试50

A 矩形

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

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

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

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

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

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

  • 接下来正常的话就要设T=pd,然后继续推,不过这道题到这里就可以\(O(n\ln 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\n", 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\n", (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]表示这段放在这个点上的最小代价,转移点只在上个段

  • 发现有决策单调性,可以\(n\log 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\n", 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\n", ans);
    return 0;
}
posted @ 2021-03-28 19:58  Shawk  阅读(55)  评论(0编辑  收藏  举报