1014下午考试

1014下午考试

T1

​ 题目大意:

​ 有一个\(n*m\)的矩阵,矩阵的每个位置上可以放置一个数。对于第i行,第i行的差异定义为该行的最大数和最小数的差。一个矩阵的差异,定义为矩阵中每一行差异的最大值。现在给定k个数v[1..k],问:从这k个数中选\(n*m\)个数放入矩阵,能够得到的矩阵的差异最小值是多少。

​ n * m <= k <= 100000, n, m <= 1000,0<= v[i] <= 10^9

​ 二分。

​ 就是二分的板子题,先对每个数排序,然后二分矩阵差异最小值,贪心的判断是否可以选出n个长度为m的连续子段。

#include <bits/stdc++.h>

#define mid ((l + r) >> 1)

using namespace std;

inline long long read() {
    long long s = 0, f = 1; char ch;
    while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    return s * f;
}

const int N = 1e5 + 5, inf = 1e9;
int k, n, m, ans;
int a[N], vis[N];

int check(int s) {
    int res = 0;
    for(int i = 1;i <= k; i++) {
        int tag = 0, tmp = 1;
        for(int j = i + 1;j <= k; j++) {
            if(a[j] - a[i] > s) { tag = 1; break; }
            else { tmp ++; }
            if(tmp == m) { i = j; break; }
        }
        if(tag == 0 && tmp == m) res ++;
    }
    return res >= n;
}

int main() {

    k = read(); n = read(); m = read();
    for(int i = 1;i <= k; i++) a[i] = read();
    sort(a + 1, a + k + 1);
    int l = 0, r = inf;
    while(l <= r) {
        if(check(mid)) ans = mid, r = mid - 1;
        else l = mid + 1;
    }
    printf("%d", ans);

    fclose(stdin); fclose(stdout);
    return 0;
}

T2

​ 题目大意:

​ 给定一个长度为n的序列v[1..n],现在要将这个序列分成k段(每段都不能为空),定义每一段的权值为该段上的所有数的或和。定义整个序列的权值为每段权值的和。问:这个序列的最大权值为多少。

​ k <= n <= 2000,1<= v[i] <= 5 *10^5

​ 线性DP。

​ 考场上想出来60分的做法:

\(f[i][j]\)表示前\(i\)个数分成\(j\)段的最大权值,那么转移方程就是:\(f[i][j] = max(f[i][j], f[l][j - 1] + or[l + 1][i])\)

​ 复杂度\(O(n ^ 3)\)

​ 考虑优化一下,或和从0变到10^6大概需要20次,如果说没变,我们只需要取没变的这一段\(f\)值最大的一个就好了。因为\(or\)随着长度变小单调不升,\(f\)随着长度变大单调不降,所以这一段最后一个点就是断点。所以我们可以与处理出断点在哪,第三层循环直接枚举这些断点就好了。

​ 复杂度\(O(n^2logn)\)

#include <bits/stdc++.h>

using namespace std;

inline long long read() {
    long long s = 0, f = 1; char ch;
    while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    return s * f;
}

const int N = 2005;
int n, k;
long long a[N], aor[N][N], f[N][N];
vector <long long> v[N];

int main() {

    n = read(); k = read();
    for(int i = 1;i <= n; i++) a[i] = read();
    for(int l = 1;l <= n; l++) 
        for(int r = l;r <= n; r++) 
            aor[l][r] = aor[l][r - 1] | a[r];
    for(int r = 1;r <= n; r++) 
        for(int l = 1;l <= r; l++) 
            if(aor[l][r] != aor[l + 1][r]) v[r].push_back(l);
    for(int i = 1;i <= n; i++) f[i][1] = aor[i][1];
    for(int i = 2;i <= n; i++) 
        for(int j = 1;j <= min(i, k); j++) 
            for(int l = 0;l < (int)v[i].size(); l++) 
                f[i][j] = max(f[i][j], f[v[i][l] - 1][j - 1] + aor[v[i][l]][i]);
    printf("%lld", f[n][k]);

    fclose(stdin); fclose(stdout);
    return 0;
}

T3

​ 题目大意:

​ 有一棵k+1层的满二叉树,那么该树有2^k 个叶子节点。给定n个机器人(n=2^k),编号从1—n,编号为i的机器人的权值为v[i]。我们现在要将这n个机器人分别放置在这n个叶子节点上,每个叶子节点放且只能放一个机器人。叶子节点的权值为该叶子节点上的机器人的权值。非叶子节点的权值定义为该树中编号最大的机器人的权值。每个非叶子节点除了权值以外,还有一个魔法值,该点的魔法值为其左右儿子节点的权值的乘积。整棵树的魔法值定义为非叶子节点的魔法值的和。问:将这n个机器人随机地放在这n个叶子节点上,树的魔法值的期望为多少。

​ 假设答案为一个不可约分数P/Q,则输出在模1e9+7意义下的P* (Q^-1)模1e9+7的值。k <= 18。

​ 组合数 + 推式子

​ 我们假设当先要合并的点高度为d,左子树权值为x,右子树权值为y,x > y;

​ 那么贡献是这个:\(C(y - 1, 2^d - 1) * C(x - 2 ^ d - 1, 2^d - 1) * v[x] * v[y] * (n - 2^{d + 1})! * 2 * 2^{k - d} * 2^d! * 2^d!\)

​ 解释一下:

​ 高度为d的二叉树节点的子树中有\(2 ^ d\)个节点,要从比\(y\)小的数里面选出\(2^d - 1\)个数,顺序不同方案也不同,所以是:\(C(y - 1, 2 ^ d - 1) * 2 ^ d!\)

​ 除去\(y\)的子树中的节点后,要从比\(x\)小的数里面选出\(2^d - 1\)个数,顺序不同方案也不同,所以是:\(C(x - 2^d - 1, 2 ^ d - 1) * 2 ^ d!\)

​ 除去\(x, y\)的所有节点后,剩下的数随便排,所以是:\((n - 2 ^ d - 2^d)! = (n - 2^{d + 1})!\)

\(x, y\)的顺序可以交换,所以再乘2。

​ 当前高度为d,那么层数就是\(k - d\)层,二叉树这一层会有\(2 ^ {(k - d)}\)个节点,都可以在这上面合并,所以再乘\(2 ^ {k - d}\)

​ 最后再乘上\(x, y\)的权值\(v[x], v[y]\)

​ 复杂度\(O(kn ^ 2)\)

​ 可以后缀和优化一下,首先原式子可以写成:

\(\displaystyle \sum_{d} \sum_{x} \sum_{y} * A(y - 1, 2^d - 1) * A(x - 2^d - 2, 2^d - 1) * (n - 2^{d + 1})! * 2^{k - d + 1}\)

​ 还可以写成:

\(\displaystyle \sum_{d} (n - 2^{d + 1})! * 2^{k - d + 1} \sum_{y} A(y - 1, 2^d - 1) \sum_{x} A(x - 2^d - 2, 2^d - 1)\)

​ 预处理出后面那两坨sigema的后缀和就好了,复杂度\(O(kn)\)

#include <bits/stdc++.h>

#define ls(o) (o << 1)
#define rs(o) (o << 1 | 1)
#define mid ((l + r) >> 1)

using namespace std;

inline long long read() {
    long long s = 0, f = 1; char ch;
    while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    return s * f;
}

const int N = 3e5 + 5, mod = 1e9 + 7;
int k, n;
int ans, a[N];
long long fac[N], inv[N], bit[N], sum[N];

void make_pre() {
    fac[0] = fac[1] = inv[0] = inv[1] = bit[0] = 1;
    for(int i = 2;i < N; i++) fac[i] = 1ll * fac[i - 1] * i % mod;
    for(int i = 2;i < N; i++) inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
    for(int i = 2;i < N; i++) inv[i] = 1ll * inv[i - 1] * inv[i] % mod;
    for(int i = 1;i < N; i++) bit[i] = bit[i - 1] * 2;
}

int C(int x, int y) {
    if(x < y) return 0;
    return 1ll * fac[x] * inv[y] % mod * inv[x - y] % mod;
}

int main() {

    make_pre();
    k = read(); n = bit[k];
    for(int i = 1;i <= n; i++) a[i] = read();
    for(int d = 1;d <= k; d++) {
        for(int x = n;x >= bit[d]; x--) 
            sum[x] = (sum[x + 1] + 1ll * C(x - bit[d - 1] - 1, bit[d - 1] - 1) * a[x] % mod) % mod;
        long long tmp = 0;
        for(int y = n;y >= bit[d - 1]; y--) 
            tmp = (tmp + 1ll * C(y - 1, bit[d - 1] - 1) * a[y] % mod * sum[max(bit[d], (long long) y + 1)] % mod) % mod;
        ans = (ans + 1ll * tmp * fac[bit[d - 1]] % mod * fac[bit[d - 1]] % mod * fac[n - bit[d]] % mod * 2 % mod * bit[k - d] % mod) % mod;
    }
    printf("%d", 1ll * ans * inv[n] % mod);

    fclose(stdin); fclose(stdout);
    return 0;
}
posted @ 2020-10-15 08:53  C锥  阅读(92)  评论(0编辑  收藏  举报