省选测试38

A 选拔赛

题目大意 : 给a数组中的每一个数在b数组中选一个与其他数选的不同的数加上,问有多少种选法可以使加之前是前k大的数,加之后还是前k大

  • 将a数组前k大的称为A组,剩下的称为B组。

  • (dp_{i,j}) 表示A组都大于等于i,B组都小于等于j的方案数,

  • (dp_{i,i}-dp-{i+1,i}) 就是A组最小的是i,B组的都小于等于i的方案数

  • 然后枚举最小的和i就可以求出答案。

  • 然后考虑如何求一个 (dp_{L,R})

  • a和b都按从大到小排序

  • 对于b的第i个数,x[i] 表示A组内有 x[i] 个数加上 b[i] 大于等于 L,因为b[i]是单调变小的,所以x[i]是单调变小的

  • 设将 b[u[1]],b[u[2]],...,b[u[k]] 给了A组,由于 x[i] 单调变小,就从后往前分配,u[k] 有 x[u[k]] 种放法,u[k-1] 有 x[u[k-1]] - 1 种放法

  • 所以第i个分配的数贡献是 x[u[i]] - (k - i)

  • 对于b的第i个数,y[i] 表示B组内有 y[i] 个数加上 b[i] 小于等于 R,因为b[i]是单调变小的,所以y[i]是单调变大的

  • 设将 b[v[1]],b[v[2]],...,b[u[n-k]] 给了b组,由于 y[i] 单调变小,就从前往后分配,v[1] 有 y[u[1]] 种放法,v[2] 有 y[u[2]] - 1 种放法

  • 所以第i个分配的数贡献是 y[v[i]] - (i - 1)

  • 然后就可以dp了。 设 f[i][j] 前i个有j个b给了前k大的a的方案数, 转移的时候枚举是第j个放在A组还是第i-j个放在了B组,然后根据上边的式子求系数就行了

Code

Show Code
#include <cstdio>
#include <algorithm>

using namespace std;
const int N = 105, M = 1e9 + 7;

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;
}

int n, k, a[N], b[N], s[N*N], cnt, x[N], y[N], f[N][N], ans;
//f[i][j]:前i个有j个b给了前k大的a的方案数

bool Cmp(const int &x, const int &y) {
    return x > y;
}

int Cal(int l, int r) {
    for (int i = 1; i <= n; ++i)
        for (x[i] = x[i-1]; b[i] + a[x[i]] < l; --x[i]);
    for (int i = 1; i <= n; ++i)
        for (y[i] = y[i-1]; b[i] + a[n-y[i]] <= r && y[i] + 1 <= n - k; ++y[i]);
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j <= i && j <= k; ++j) {
            f[i][j] = 0;
            if (j && x[i] > k - j) f[i][j] = (f[i][j] + 1ll * f[i-1][j-1] * (x[i] - k + j)) % M;
            if (y[i] > i - j - 1) f[i][j] = (f[i][j] + 1ll * f[i-1][j] * (y[i] - i + j + 1)) % M;
        }
    }
    return f[n][k];
}

int main() {
    freopen("select.in", "r", stdin);
    freopen("select.out", "w", stdout);
    n = read(); x[0] = k = read(); f[0][0] = 1;
    for (int i = 1; i <= n; ++i) a[i] = read();
    for (int i = 1; i <= n; ++i) b[i] = read();
    sort(a + 1, a + n + 1, Cmp);
    sort(b + 1, b + n + 1, Cmp);
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
            s[++cnt] = a[i] + b[j];
    sort(s + 1, s + cnt + 1);
    for (int i = 1; i <= cnt; ++i)
        if (s[i] != s[i-1]) ans = (1ll * ans + Cal(s[i], s[i]) - Cal(s[i] + 1, s[i]) + M) % M;
    printf("%d
", ans);
    return 0;
}

B 跳跃

题目大意 : 再第i个柱子可以往左或往右跳a[i]个柱子(可以小于a[i]),求最大的两点间最小步数

  • l[i][j][x],r[i][j][x]分别表示从x到x+2j-1这些点开始跳2i步最左最右可以跳到哪里,利用ST表的思想很容易在nlogn2的时间里预处理出来

  • 然后二分答案,nlogn判断,总复杂度 (O(nlog^2 n))

Code

Show Code
#include <cstdio>
#include <algorithm>

using namespace std;
const int N = 2e5 + 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;
}

int n, L[18][18][N], R[18][18][N], lg[N], al, ar, l[N], r[N], b[N], s[N];

void Ask(int l, int r, int i) {
    int k = lg[r-l+1]; r = r - (1 << k) + 1;
    al = min(L[i][k][l], L[i][k][r]);
    ar = max(R[i][k][l], R[i][k][r]);
}

bool Judge(int mid) {
    for (int x = 1; x <= n; ++x) {
        al = ar = x;
        for (int i = lg[mid]; i >= 0; --i) 
            if (mid >> i & 1) Ask(al, ar, i);
        l[x] = al; r[x] = ar;
    }
    for (int i = 1; i <= n; ++i)
        s[i] = min(s[i-1], r[i]);
    for (int i = n; i >= 1; --i)
        b[i] = max(b[i+1], l[i]);
    for (int i = 1; i <= n; ++i)
        if (s[l[i]-1] < i || b[r[i]+1] > i) return 0;
    return 1;
}

int main() {
    freopen("jump.in", "r", stdin);
    freopen("jump.out", "w", stdout);
    n = read();
    for (int i = 2; i <= n; ++i)
        lg[i] = lg[i>>1] + 1;
    for (int x = 1; x <= n; ++x) {
        int l = read();
        L[0][0][x] = max(x - l, 1);
        R[0][0][x] = min(x + l, n);
    }
    for (int j = 0; j < lg[n]; ++j) {
        for (int p = 1 << j, x = n - (1 << j + 1) + 1; x >= 1; --x) {
            L[0][j+1][x] = min(L[0][j][x], L[0][j][x+p]);
            R[0][j+1][x] = max(R[0][j][x], R[0][j][x+p]);
        }
    }
    for (int i = 1; i <= lg[n]; ++i) {
        for (int x = 1; x <= n; ++x) {
            Ask(L[i-1][0][x], R[i-1][0][x], i-1);
            L[i][0][x] = al, R[i][0][x] = ar;
        }
        for (int j = 0; j < lg[n]; ++j) {
            for (int p = 1 << j, x = n - (1 << j + 1) + 1; x >= 1; --x) {
                L[i][j+1][x] = min(L[i][j][x], L[i][j][x+p]);
                R[i][j+1][x] = max(R[i][j][x], R[i][j][x+p]);
            }
        }
    }
    int l = 0, r = n; s[0] = 1e9;
    while (l < r) {
        int mid = l + r >> 1;
        if (Judge(mid)) r = mid;
        else l = mid + 1;
    }
    printf("%d
", l);
    return 0;
}

C 切蛋糕 (Unaccepted)

题目大意 : 求一个半平面交和圆的交的周长

  • 大力平面几何

Code

Show Code
原文地址:https://www.cnblogs.com/shawk/p/14559191.html