1. 程式人生 > 實用技巧 >【題錄】Atcoder ARC#104

【題錄】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;
}