【题录】Atcoder ARC#104

C.Fair Elevator

每一站都必须要有人上车/下车,则如果把上车标记为1,下车标记为2,最后的合法序列一定是形如x个1,x个2这样的若干个段拼在一起的。所以我们暴力枚举分段点及段的长度判断是否有合法解。

#include <bits/stdc++.h>
using namespace std;
#define maxn 1000000
int n, a[maxn], b[maxn], r[maxn], rec[maxn];
bool mark[maxn];

int read() {
    int x = 0, k = 1;
    char c; c = getchar();
    while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * k;
}

int main() {
    n = read();
    for(int i = 1; i <= n; i ++) {
        a[i] = read(), b[i] = read();
        if(a[i] != -1 && b[i] != -1 && a[i] >= b[i]) { printf("No\n"); return 0; } 
        if(a[i] != -1) {
            if(r[a[i]]) { printf("No\n"); return 0; } 
            r[a[i]] = 1; rec[a[i]] = i;
        }
        if(b[i] != -1) {
            if(r[b[i]]) { printf("No\n"); return 0; }
            r[b[i]] = 2; rec[b[i]] = i;
        }
    }
    mark[1] = 1;
    for(int i = 1; i <= 2 * n; i ++) {
        if(!mark[i]) continue;
        for(int L = 1; L <= n; L ++) {
            if(i + 2 * L - 1 > 2 * n) break;
            bool flag = 1;
            for(int j = i; j < i + L; j ++) {
                if(r[j] == 2) { flag = 0; break; }
                if(r[j + L] == 1) { flag = 0; break; }
                if(r[j] && rec[j + L] && (rec[j + L] != rec[j])) { 
                    flag = 0; break; 
                }
            }
            if(flag) mark[i + 2 * L] = 1;
        }
    }
    if(mark[2 * n + 1]) printf("Yes\n");
    else printf("No\n");
    return 0;
}

D.Multiset Mean

如果最后的平均值是x,则序列中\(\sum (a_{i} - x) = 0\),将元素分为大于x的和小于x的两部分,即为\(\sum (a_{i} - x) = \sum(x - b_{i})\)。小于x的取值情况:1到x-1,每种物品最多拿K个,大于x的取值情况:1到n-x,每种物品最多拿K个。由此我们预处理dp[i][j]表示价值为1-i的物品每个最多拿K个的情况下总价值为j的方案数。这里使用无限背包转多重背包即为\(O(nm)\)的复杂度。然后对每一个x统计方案数得到答案。

#include <bits/stdc++.h>
using namespace std;
#define N 105
#define maxn 600005
int n, K, mod, cnt, num[N], dp[N][maxn];

int read() {
    int x = 0, k = 1;
    char c; c = getchar();
    while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * k;
}

void Up(int &x, int y) {
    x += y; if(x >= mod) x -= mod;
}

int add(int x, int y) {
    x += y; if(x >= mod) x -= mod; return x;
}
int sub(int x, int y) {
    x -= y; if(x < 0) x += mod; return x;
}
int mul(int x, int y) {
    return 1ll * x * y % mod;
}

int main() {
    n = read(), K = read(), mod = read();
    int lim = (K * n * (n - 1)) >> 1;
    dp[0][0] = 1; 
    for(int i = 1; i <= n; i ++) {
        for(int j = 0; j <= lim; j ++) dp[i][j] = dp[i - 1][j]; 
        for(int j = i; j <= lim; j ++)
            Up(dp[i][j], dp[i][j - i]);
        for(int j = lim; j >= 0; j --) 
            if(j >= (K + 1) * i) 
                dp[i][j] = sub(dp[i][j], dp[i][j - (K + 1) * i]);
    }
    for(int i = 1; i <= n; i ++) {
        int ans = 0;
        for(int j = 0; j <= lim; j ++)
            Up(ans, mul(dp[i - 1][j], dp[n - i][j]));
        ans = mul(ans, (K + 1));
        ans = sub(ans, 1);
        printf("%d\n", ans);
    }
    return 0;
}

E.Random LIS

爆搜每个位置上面数字的排名,此时最长上上子序列的长度一定。问题转化为满足\(x_{n} < x_{n + 1}\) 且 \(x_{n} <= A_{n}\) 的数列有多少个。把\(A_{n}\) 从小到大分成一段一段的,不同段之间相对大小一定满足,方案相乘,同段之中则实用组合数求解。虽然C(n, m) 中的n很大,但因为m很小,所以可以O(m) 计算。

#include <bits/stdc++.h>
using namespace std;
#define N 1000
#define INF 1000000009
#define mod 1000000007
int n, len, ans = 0, mx, cnt, a[N], b[N], p[N], cal[N];
int A[N], inv[N], dp[N], lim[N], R[N], t, mark[N];

int read() {
    int x = 0, k = 1;
    char c; c = getchar();
    while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * k;
}

int mul(int x, int y) {
    return 1ll * x * y % mod;
}

int add(int x, int y) {
    x += y; if(x >= mod) x -= mod;
    return x;
}

void Up(int &x, int y) {
    x += y; if(x >= mod) x -= mod;
}

int Qpow(int x, int t) {
    int base = 1; 
    for(; t; t >>= 1, x = mul(x, x))
        if(t & 1) base = mul(base, x);
    return base;
}

int C(int n, int m) {
    if(n < m) return 0;
    int num = 1;
    for(int i = 1; i <= m; i ++) 
        num = mul(num, mul(n - i + 1, inv[i]));
    return num; 
}

void dfs2(int now, int lst) {
    if(now == (mx + 1)) {
        int ans1 = 1;
        for(int i = 1; i <= cnt; i ++) cal[i] = 0;
        for(int i = 1; i <= mx; i ++) cal[b[i]] ++;
        for(int i = 1; i <= cnt; i ++) 
            ans1 = mul(ans1, C(p[i], cal[i]));
        ans1 = mul(ans1, len);
        Up(ans, ans1);
        return;
    }
    for(int i = lst; i <= cnt; i ++) {
        if(lim[now] < R[i]) break;
        b[now] = i;
        dfs2(now + 1, i);
    }
}

void Work() {
    cnt = 0; len = 0;
    for(int i = 1; i <= n; i ++) dp[i] = 0;
    for(int i = 1; i <= n; i ++)
        for(int j = 0; j < i; j ++)
            if(a[j] < a[i]) dp[i] = max(dp[i], dp[j] + 1);
    for(int i = 1; i <= n; i ++) len = max(len, dp[i]);
    for(int i = 1; i <= mx; i ++) lim[i] = INF;
    for(int i = 1; i <= n; i ++)
        lim[a[i]] = min(lim[a[i]], A[i]);
    for(int i = 1; i <= mx; i ++)
        for(int j = i + 1; j <= mx; j ++)
            lim[i] = min(lim[i], lim[j]);
    for(int i = 1; i <= mx; i ++) 
        if(lim[i] - lim[i - 1]) p[++ cnt] = lim[i] - lim[i - 1], R[cnt] = lim[i];
    dfs2(1, 1);
}

void dfs(int num) {
    if(num == (n + 1)) {
        for(int i = 1; i <= mx; i ++) 
            if(!mark[i]) return;
        Work();
        return;
    }
    for(int i = 1; i <= n; i ++) {
        a[num] = i; int rec = mx; mx = max(mx, i);
        mark[i] ++;
        dfs(num + 1);
        mx = rec; mark[i] --;
    }
}

int main() {
    n = read();
    for(int i = 1; i <= n; i ++) A[i] = read();
    for(int i = 1; i <= n; i ++) inv[i] = Qpow(i, mod - 2);
    dfs(1);
    for(int i = 1; i <= n; i ++) 
        ans = mul(ans, Qpow(A[i], mod - 2));
    printf("%d\n", ans);
    return 0;
}

F.Visibility Sequence

如果把一个点同它左侧高度大于自己的点之间连一条边(不存在则连向超级源点)那么将会构成一棵树。观察这棵树的性质,会发现每一个子树节点的编号都是一个连续的区间[l, r],且根节点的编号为l,其权值为子树中最大。考虑当前的一个根节点下面的若干个儿子,则编号大的点的权值一定大于等于编号小的点的权值。若每个点的权值都在约束范围之内,则满足以上几个条件的每一棵不同形态(点的编号不同)的树都是一个不同的p序列。为了尽量让每个点的权值在范围内,可以贪心的让每个点的权值往大了取减少对下方点的限制。设dp[l][r][num]为子树(不包括当前根)的节点编号为[l,r],当前根的权值为num+1 的不同方案数。枚举的时候就枚举儿子中编号最大的点进行转移。

#include <bits/stdc++.h>
using namespace std;
#define N 105
#define mod 1000000007
int n, a[N], dp[N][N][N];

int read() {
    int x = 0, k = 1;
    char c; c = getchar();
    while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * k;
}

int mul(int x, int y) {
    return 1ll * x * y % mod;
}

void Up(int &x, int y) {
    x += y; if(x >= mod) x -= mod;
}

int dfs(int l, int r, int mx) {
    if(l > r) return 1;
    if(!mx) return 0;
    if(dp[l][r][mx] != -1) return dp[l][r][mx];
    dp[l][r][mx] = 0; 
    for(int i = l; i <= r; i ++) {
        int top = min(mx, a[i]);
        Up(dp[l][r][mx], mul(dfs(l, i - 1, top), dfs(i + 1, r, top - 1))); 
    }
    return dp[l][r][mx];
}

int main() {
    n = read();
    for(int i = 1; i <= n; i ++) a[i] = read();
    for(int i = 1; i <= n; i ++)
        for(int j = i; j <= n; j ++)
            for(int k = 1; k <= n; k ++)
                dp[i][j][k] = -1;
    printf("%d\n", dfs(1, n, n));
    return 0;
}

 

posted @ 2020-10-07 16:30  Twilight_Sx  阅读(152)  评论(0编辑  收藏  举报