首页 > 其他分享 >DP1

DP1

时间:2024-07-03 21:43:19浏览次数:14  
标签:DP1 min int freopen include data dp

T1

Atcoder ABC217F Make Pair

考虑区间 dp,\(dp[l][r]\) 表示消完区间 \([l, r]\) 中所有人的方案数,转移枚举分界点,如果 \(l\) 和 \(r\) 有朋友关系则再从 \(dp[l + 1][r - 1]\) 转移。但是这样会算重,所以再加一维 \(0 / 1\) 表示这个区间是否封闭(不是由两个区间拼起来),这样就可以转移了。

代码
#include <iostream>
#define int long long
using namespace std;
const int P = 998244353;
int g[405][405];
int dp[405][405][2];
int n, m;
void Madd(int& x, int y) { (x += y) >= P ? (x -= P) : 0; }
int C[405][405];
signed main() {
    cin >> n >> m;
    C[0][0] = 1;
    for (int i = 1; i <= 400; i++) {
        for (int j = 0; j <= i; j++) 
            C[i][j] = (C[i - 1][j] + (j ? C[i - 1][j - 1] : 0)) % P;
    }
    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        g[u][v] = g[v][u] = 1;
    }
    for (int i = 1; i < n * 2; i++) dp[i][i + 1][1] = g[i][i + 1];
    for (int d = 4; d <= n * 2; d += 2) {
        for (int l = 1; l + d - 1 <= n * 2; l++) {
            int r = l + d - 1;
            for (int k = l + 1; k < r; k += 2) 
                Madd(dp[l][r][0], (dp[l][k][0] + dp[l][k][1]) % P * dp[k + 1][r][1] % P * C[d / 2][(r - k) / 2] % P);
            if (g[l][r]) 
                Madd(dp[l][r][1], dp[l + 1][r - 1][0] + dp[l + 1][r - 1][1]);
        }
    }
    cout << (dp[1][n << 1][0] + dp[1][n << 1][1]) % P << "\n";
    return 0;
}

T2

洛谷 P3607 Subsequence Reversal P

考虑转化一下子序列翻转,把它变成第一个与最后一个换,第二个与倒数第二个换。这样就拆成了一些数对的交换,对应的位置构成的区间互相严格包含。于是可以考虑类似于区间 dp 的东西。设 \(dp[l][r][a][b]\) 表示整个序列的前 \(l\) 个元素中的 LIS 和最后 \(r\) 个元素中的 LIS 加起来的长度,其中前面的 LIS 的最后一个元素在 \(a\),后面 LIS 的最后一个元素在 \(b\)。转移考虑每次把右边或左边往里推一个,不接就不管,如果能往左往右接就接一下,如果要翻转就拿右边的往左接,拿左边的往右接,转移到 \([l + 1][r - 1]\) 的位置。

代码
#include <iostream>
using namespace std;
int n;
int A[55];
int dp[55][55][55][55];
void Cmax(int& x, int y) { x = max(x, y); }
int main() {
    // freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> A[i];
    dp[0][n + 1][0][0] = 0;
    A[n + 1] = 2147483647;
    for (int d = n + 2; d > 2; d--) {
        for (int l = 0; l + d - 1 <= n + 1; l++) {
            int r = l + d - 1;
            for (int a = 0; a <= n + 1; a++) {
                if (a == l + 1) 
                    a = r;
                for (int b = 0; b <= n + 1; b++) {
                    if (b == l + 1) 
                        b = r;
                    Cmax(dp[l + 1][r][a][b], dp[l][r][a][b]);
                    Cmax(dp[l][r - 1][a][b], dp[l][r][a][b]);
                    if (A[l + 1] >= A[a]) 
                        Cmax(dp[l + 1][r][l + 1][b], dp[l][r][a][b] + 1);
                    if (A[r - 1] <= A[b]) 
                        Cmax(dp[l][r - 1][a][r - 1], dp[l][r][a][b] + 1);
                    if (A[l + 1] <= A[b]) 
                        Cmax(dp[l + 1][r - 1][a][l + 1], dp[l][r][a][b] + 1);
                    if (A[r - 1] >= A[a]) 
                        Cmax(dp[l + 1][r - 1][r - 1][b], dp[l][r][a][b] + 1);
                    if (A[r - 1] >= A[a] && A[l + 1] <= A[b]) 
                        Cmax(dp[l + 1][r - 1][r - 1][l + 1], dp[l][r][a][b] + 2 - (l + 1 == r - 1));
                }
            }
        }
    }
    int ans = 0;
    for (int a = 0; a <= n; a++) {
        for (int b = 0; b <= n; b++) {
            for (int c = 1; c <= n; c++) {
                if (A[a] <= A[b]) {
                    Cmax(ans, dp[c][c][a][b]);
                    Cmax(ans, dp[c][c + 1][a][b]);
                }
            }
        }
    }
    cout << ans << "\n";
    return 0;
}

T3

Atcoder ABC219H Candles

考虑最基础的区间 dp,\(dp[l][r]\) 表示处理完区间 \([l, r]\) 的蜡烛之后这区间内剩余的最大长度。然后由于处理完之后位置要么在左端点要么在右端点,就再加一维 \(0 / 1\) 记录一下当前位置。先考虑蜡烛能被减成负数的情况。此时我们的转移就是每次往左往右扩,看是从哪个端点走到哪个端点。但是这样不能知道时间,所以考虑进行费用提前计算,每次走向下一支蜡烛时把这一次行走花费的时间给所有区间之外的(尚未处理的)蜡烛造成的减少量先算进当前的 dp 值里面,然后走到一支蜡烛之后直接把它的长度加进 dp 值。但是实际上蜡烛不能被减成负数。

由于负数的蜡烛不被算进答案,我们走到这个蜡烛时就不把它算进答案了。由于不把它算进答案,消耗时间也就不会给它的贡献带来减少量。所以走向一个蜡烛并且提前计算费用时,还需要知道这个区间之外有多少个蜡烛到最后剩下正数长度。我们考虑直接把这玩意也扔进状态里,于是状态变成 \(dp[l][r][0 / 1][k]\) 表示已经处理完了区间 \([l, r]\) 的蜡烛,当前在左 / 右端点,\([l, r]\) 之外有多少蜡烛我们要求它剩下正数。转移的时候考虑走向的这个蜡烛最后要不要留成正数,然后就可以转移了。

代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <map>
#define int long long
using namespace std;
const int inf = 448509071590753727;
int n;
int a[305];
map<int, int> mp, cnt, id;
struct Pos {
    int p, len, cnt;
} p[305];
int dp[305][305][305][2];
void Cmax(int& x, int y) { x = max(x, y); }
signed main() {
    memset(dp, -63, sizeof dp);
    cin >> n;
    int S = 0;
    for (int i = 1; i <= n; i++) {
        cin >> p[i].p >> p[i].len;
        p[i].cnt = 1;
        if (p[i].p == 0) 
            S += p[i].len;
    }
    p[++n] = (Pos) { 0, 0, 1 };
    sort(p + 1, p + n + 1, [](Pos a, Pos b) { return a.p < b.p; });
    int pos = 0;
    for (int i = 1; i <= n; i++) {
        if (p[i].p == 0 && p[i].len == 0) 
            pos = i;
    }
    for (int i = 1; i <= n; i++) id[p[i].p] = i;
    for (int i = 1; i <= n; i++) 
        dp[pos][pos][i][0] = dp[pos][pos][i][1] = 0;
    for (int d = 1; d <= n; d++) {
        for (int l = 1; l + d - 1 <= n; l++) {
            int r = l + d - 1;
            for (int k = 0; k <= n; k++) {
                int t;
                if (l != 1) {
                    t = p[l].p - p[l - 1].p;
                    if (k >= p[l - 1].cnt) 
                        Cmax(dp[l - 1][r][k - p[l - 1].cnt][0], dp[l][r][k][0] + p[l - 1].len - k * t);
                    Cmax(dp[l - 1][r][k][0], dp[l][r][k][0] - k * t);

                    t = p[r].p - p[l - 1].p;
                    if (k >= p[l - 1].cnt) 
                        Cmax(dp[l - 1][r][k - p[l - 1].cnt][0], dp[l][r][k][1] + p[l - 1].len - k * t);
                    Cmax(dp[l - 1][r][k][0], dp[l][r][k][1] - k * t);
                }
                if (r != n) {
                    t = p[r + 1].p - p[l].p;
                    if (k >= p[r + 1].cnt) 
                        Cmax(dp[l][r + 1][k - p[r + 1].cnt][1], dp[l][r][k][0] + p[r + 1].len - k * t);
                    Cmax(dp[l][r + 1][k][1], dp[l][r][k][0] - k * t);

                    t = p[r + 1].p - p[r].p;
                    if (k >= p[r + 1].cnt) 
                        Cmax(dp[l][r + 1][k - p[r + 1].cnt][1], dp[l][r][k][1] + p[r + 1].len - k * t);
                    Cmax(dp[l][r + 1][k][1], dp[l][r][k][1] - k * t);
                }
            }
        }
    }
    int ans = 0;
    for (int i = 0; i <= n; i++) Cmax(ans, max(dp[1][n][i][0], dp[1][n][i][1]));
    cout << ans << "\n";
    return 0;
}

T4

CF1922F Replace on Segment

首先容易想到 \(f[l][r][k]\) 表示将 \([l, r]\) 变为 \(k\) 的最小代价。然后由于需要知道一个区间能不能被 \(k\) 赋值,需要知道 \(g[l][r][k]\) 表示 \([l, r]\) 区间内变得没有 \(k\) 的最小代价。首先 \(f\) 和 \(g\) 都有显然的枚举分界点转移。另外有 \(f[l][r][k] = g[l][r][k] + 1\),这是好理解的。对于 \(g\) 有 \(g[l][r][a] = g[l][r][b] + 1(a \neq b)\),这表示将 \([l, r]\) 变成 \(b\),这样区间中也是没有 \(a\),符合条件。复杂度 \(\mathcal{O}(n^4)\),\(n = 100\) 随便跑。

代码
#include <iostream>
#include <string.h>
using namespace std;
int f[105][105][105];
int g[105][105][105];
int n, M;
int a[105];
void Cmin(int& x, int y) { x = min(x, y); }
int main() {
    // freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
    int tc;
    cin >> tc;
    while (tc--) {
        memset(f, 63, sizeof f);
        memset(g, 63, sizeof g);
        cin >> n >> M;
        for (int i = 1; i <= n; i++) {
            cin >> a[i];
            for (int j = 1; j <= M; j++) {
                if (a[i] != j) {
                    f[i][i][j] = 1;
                    g[i][i][j] = 0;
                } else {
                    f[i][i][j] = 0;
                    g[i][i][j] = 1;
                }
            }
        }
        for (int d = 2; d <= n; d++) {
            for (int l = 1; l + d - 1 <= n; l++) {
                int r = l + d - 1;
                for (int k = 1; k <= M; k++) {
                    for (int m = l; m < r; m++) {
                        Cmin(f[l][r][k], f[l][m][k] + f[m + 1][r][k]);
                        Cmin(g[l][r][k], g[l][m][k] + g[m + 1][r][k]);
                    }
                }
                for (int k = 1; k <= M; k++) {
                    for (int x = 1; x <= M; x++) {
                        if (k != x) 
                            Cmin(g[l][r][k], g[l][r][x] + 1);
                    }
                }
                for (int k = 1; k <= M; k++) Cmin(f[l][r][k], g[l][r][k] + 1);
            }
        }
        int ans = 2147483647;
        for (int i = 1; i <= M; i++) Cmin(ans, f[1][n][i]);
        cout << ans << "\n";
    }
    return 0;
}

T5

Atcoder ABC223G Vertex Deletion

由于树是二分图,而二分图最大匹配等于最小点覆盖,我们转化为最小点覆盖来做。\(dp[x][0 / 1]\) 表示 \(x\) 子树内 \(x\) 不选 / 选的最小点覆盖。整棵树的答案是 \(\min\{dp[1][0], dp[1][1]\}\)。接下来考虑换根,递归时 \(x\) 向子树 \(y\) 传入两个参数表示 \(x\) 选 / 不选时 \(y\) 子树外的最小点覆盖。这样就相当于以 \(y\) 为根,做 \(x\) 子树的 dp。考虑对 \(x\) 的子树做前后缀 dp 值的合并,然后结合父亲传下来的 dp 值就可以很容易地求出 \(y\) 为根时 \(x\) 的 dp 值。然后去掉 \(x\) 这个点相当于以 \(x\) 为根并且强制选 \(x\) 这个点时整棵树的最小点覆盖减 \(1\)(减去 \(x\))。于是答案就很好求了。

代码
#include <iostream>
using namespace std;
int n;
int head[200005], nxt[400005], to[400005], ecnt;
void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
int dp[200005][2];
int dp2[200005][2];
int ps[200005], ns[200005];
int pre[200005][2], suf[200005][2];
void dfs1(int x, int fa) {
    dp[x][1] = 1;
    int tmp = 0;
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (v != fa) {
            dfs1(v, x);
            dp[x][1] += min(dp[v][0], dp[v][1]);
            dp[x][0] += dp[v][1];
            ns[ps[v] = tmp] = v;
            pre[v][0] = min(dp[v][0], dp[v][1]) + pre[ps[v]][0];
            pre[v][1] = dp[v][1] + pre[ps[v]][1];
            tmp = v;
        }
    }
    while (tmp) {
        suf[tmp][0] = min(dp[tmp][0], dp[tmp][1]) + suf[ns[tmp]][0];
        suf[tmp][1] = dp[tmp][1] + suf[ns[tmp]][1];
        tmp = ps[tmp];
    }
}
void dfs2(int x, int fa, int up0, int up1) {
    dp2[x][0] = dp[x][0];
    dp2[x][1] = dp[x][1];
    if (fa) {
        dp2[x][0] += up1;
        dp2[x][1] += min(up0, up1);
    }
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (v != fa) {
            int t1 = up1 + pre[ps[v]][1] + suf[ns[v]][1];
            int t2 = min(up0, up1) + pre[ps[v]][0] + suf[ns[v]][0] + 1;
            dfs2(v, x, t1, t2);
        }
    }
}
int main() {
    // freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
    cin >> n;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        add(u, v);
        add(v, u);
    }
    int ans = 0;
    dfs1(1, 0);
    ans = min(dp[1][0], dp[1][1]);
    dfs2(1, 0, 0, 0);
    int cnt = 0;
    for (int i = 1; i <= n; i++) cnt += (dp2[i][1] - 1 == ans);
    cout << cnt << "\n";
    return 0;
}

T6

Atcoder ABC115F Perils in Parallel

先对原数组异或差分一下,这样区间异或转化为两次单点异或。考虑把每个开关操作的两个点连起来,构成一张图。我们的操作是选中一条边,将其两个端点的状态取反,我们的目标是将所有点点权变成 \(0\)。我们考虑一棵 dfs 树和其上的一条返祖边。这条返祖边构成了一个环。注意到我们把整个环上所有边操作一遍等于没操作,所以操作这条返祖边相当于操作一遍这条返祖边连接的两个点在 dfs 树上的路径上的所有边。于是返祖边无用,全部删掉。这样就变成了一棵树(森林),而这种情况是好处理的,只需要从叶子往上考虑,如果叶子为 \(1\) 就操作叶子上面的边,然后依次向上推,如果最后根是 \(0\) 则合法,否则不可能达成。

代码
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
int n, m;
struct Lamp {
    int p, s;
} a[100005];
struct Switch {
    int l, r, id;
} b[1000005];
int head[1000005], nxt[2000005], to[2000005], ew[2000005], ecnt;
void add(int u, int v, int ww) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, ew[ecnt] = ww; }
int ans[1000005], acnt;
bool vis[1000005];
void dfs(int x) {
    vis[x] = 1;
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (!vis[v]) {
            dfs(v);
            if (a[v].s) {
                ans[++acnt] = ew[i];
                a[x].s ^= 1;
                a[v].s = 0;
            }
        }
    }
}
signed main() {
    // freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i].p >> a[i].s;
    sort(a + 1, a + n + 1, [](Lamp a, Lamp b) { return a.p < b.p; });
    for (int i = n + 1; i; i--) a[i].s ^= a[i - 1].s;
    for (int i = 1; i <= m; i++) cin >> b[i].l >> b[i].r, b[i].id = i;
    sort(b + 1, b + m + 1, [](Switch a, Switch b) { return a.l < b.l; });
    for (int i = 1, j = 1; i <= m; i++) {
        while (j <= n && a[j].p < b[i].l) ++j;
        b[i].l = j;
    }
    sort(b + 1, b + m + 1, [](Switch a, Switch b) { return a.r > b.r; });
    for (int i = 1, j = n; i <= m; i++) {
        while (j && a[j].p > b[i].r) --j;
        b[i].r = j;
    }
    for (int i = 1; i <= m; i++) {
        if (b[i].r >= b[i].l) {
            add(b[i].l, b[i].r + 1, b[i].id);
            add(b[i].r + 1, b[i].l, b[i].id);
        }
    }
    for (int i = 1; i <= n + 1; i++) {
        if (!vis[i]) {
            dfs(i);
            if (a[i].s) {
                cout << -1 << "\n";
                return 0;
            }
        }
    }
    cout << acnt << "\n";
    sort(ans + 1, ans + acnt + 1);
    for (int i = 1; i <= acnt; i++) cout << ans[i] << " ";
    cout << "\n";
    return 0;
}

T7

Atcoder ABC293Ex Optimal Path Decomposition

先二分答案 \(k\)。考虑 \(f[x][0 / 1 / 2]\) 表示 \(x\) 周围有不超过 \(0 / 1 / 2\) 个点与其颜色相同时 \(x\) 子树内最优情况下 \(x\) 向下走经过的最多颜色数(不包含 \(x\) 的颜色)。转移考虑在保证合法的情况下以尽量优的情况更新 \(x\) 的 dp 值。如果不合法了就赋成 \(inf\),最后在根的地方判一下是否合法即可。

代码
#include <iostream>
#define int long long
using namespace std;
const int inf = 1000000005;
int n, K;
int head[200005], nxt[400005], to[400005], ecnt;
void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
int f[200005][3];
void dfs(int x, int fa) {
    f[x][0] = f[x][1] = f[x][2] = 0;
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (v != fa) {
            dfs(v, x);
            int g0 = inf, g1 = inf, g2 = inf;
            if (f[x][0] + f[v][1] < K) 
                g1 = min(g1, max(f[x][0], f[v][1]));
            if (f[x][0] + f[v][2] + 1 < K) 
                g0 = min(g0, max(f[x][0], f[v][2] + 1));
            if (f[x][1] + f[v][1] < K) 
                g2 = min(g2, max(f[x][1], f[v][1]));
            if (f[x][1] + f[v][2] + 1 < K) 
                g1 = min(g1, max(f[x][1], f[v][2] + 1));
            if (f[x][2] + f[v][2] + 1 < K) 
                g2 = min(g2, max(f[x][2], f[v][2] + 1));
            f[x][0] = g0, f[x][1] = min(f[x][0], g1), f[x][2] = min(f[x][1], g2);
        }
    }
}
signed main() {
    // freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
    cin >> n;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        add(u, v);
        add(v, u);
    }
    int l = 1, r = n, ans = -1;
    while (l <= r) {
        K = (l + r) >> 1;
        dfs(1, 0);
        if (f[1][2] < inf) 
            r = K - 1, ans = K;
        else 
            l = K + 1;
    }
    cout << ans << "\n";
    return 0;
}

T8

Atcoder ABC328G Cut and Reorder

容易发现一定是一次 \(1\) 操作之后一堆 \(2\) 操作。于是钦定 \(b\) 数组顺序,把 \(a\) 数组一段一段接着往上拼。考虑 \(f[S]\) 表示 \(a\) 数组已经有 \(S\) 集合的数拿来拼 \(b\) 数组的最小代价。接下来枚举把哪个区间拼到 \(b\) 上去。这段区间必须是全 \(0\) 的,否则不合法。拼上去之后操作 \(2\) 带来的代价可以暴力枚举预处理出来,总复杂度 \(\mathcal{O}(2^nn^2)\),但是远远跑不满,可以通过。

代码
#include <iostream>
#include <string.h>
#define abs(x) (((x) > 0) ? (x) : (-(x)))
#define lowbit(x) ((x) & (-(x)))
#define int long long
using namespace std;
int n, c;
int a[25], b[25];
int dif[25][25][25];
int pcnt[5000005];
int dp[5000005];
void Cmin(int& x, int y) { x = min(x, y); }
signed main() {
    // freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
    cin >> n >> c;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) cin >> b[i];
    for (int k = 1; k <= n; k++) {
        for (int i = 1; i <= n - k + 1; i++) {
            for (int j = 1; j <= n - k + 1; j++) {
                int ca = i, cb = j;
                for (; ca <= i + k - 1; ca++, cb++) 
                    dif[i][j][k] += abs(a[ca] - b[cb]);
            }
        }
    }
    memset(dp, 63, sizeof dp);
    dp[0] = -c;
    pcnt[0] = -1;
    for (int S = 0; S < (1 << n); S++) {
        pcnt[S] = pcnt[S - lowbit(S)] + 1;
        for (int i = 1; i <= n; i++) {
            if (S & (1 << (i - 1))) 
                continue;
            int to = S;
            for (int j = i; j <= n && (!(S & (1 << (j - 1)))); j++) {
                to |= (1 << (j - 1));
                Cmin(dp[to], dp[S] + c + dif[i][pcnt[S] + 1][j - i + 1]);
            }
        }
    }
    cout << dp[(1 << n) - 1] << "\n";
    return 0;
}

T9

Atcoder ABC295Ex E or m

容易发现一个位置可以是 \(1\) 当且仅当其上全 \(1\) 或其左全 \(1\)。于是容易想到进行按格转移的轮廓线dp,状态 \(f[i][j][S][0 / 1]\) 表示处理到第 \(i\) 行第 \(j\) 列,每一列目前是否是全 \(1\),当前行前面是否是全 \(1\)。然后转移就是平凡的,只需要考虑当前列是否全 \(1\) 和当前行是否全 \(1\) 即可。转移到行末之后要记得换行,无论当前行是否全 \(1\) 到了下一行都是全 \(1\)。

代码
#include <iostream>
// #define int long long
using namespace std;
const int P = 998244353;
int n, m;
string str[20];
int dp[20][20][262200][2];
void Madd(int& x, int y) { ((x += y) >= P) ? (x -= P) : 0; }
signed main() {
    // freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> str[i], str[i] = ' ' + str[i];
    dp[1][0][(1 << m) - 1][1] = 1;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            for (int k = 0; k < (1 << m); k++) {
                if (str[i][j] != '1') {
                    int x = (k ^ (k & (1 << (j - 1))));
                    Madd(dp[i][j][x][0], (dp[i][j - 1][k][0] + dp[i][j - 1][k][1]) % P);
                }
                if (str[i][j] != '0') {
                    Madd(dp[i][j][k][1], dp[i][j - 1][k][1] % P);
                    if (k & (1 << (j - 1))) 
                        Madd(dp[i][j][k][0], dp[i][j - 1][k][0] % P);
                }
            }
        }
        for (int k = 0; k < (1 << m); k++) Madd(dp[i + 1][0][k][1], (dp[i][m][k][0] + dp[i][m][k][1]) % P);
    }
    int ans = 0;
    for (int i = 0; i < (1 << m); i++) Madd(ans, dp[n + 1][0][i][1]);
    cout << ans << "\n";
    return 0;
}

T10

CF383E Vowels

正难则反,考虑求出有多少字符串完全属于某个字符集。注意到这实际上是一个高维前缀和的问题,于是直接高维前缀和即可。这个高维前缀和的基本思想就是从低维向高维扩展,要求一个体的和,先求所有面的和,再把面的求和分成线的求和,最后把这些和一步步加起来得到答案。

代码
#include <iostream>
using namespace std;
int n;
string str;
int dp[16777220];
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> str;
        int a = str[0] - 'a';
        int b = str[1] - 'a';
        int c = str[2] - 'a';
        dp[(1 << a) | (1 << b) | (1 << c)]++;
    }
    for (int i = 0; i < 24; i++) {
        for (int j = 0; j < (1 << 24); j++) {
            if (j & (1 << i)) 
                dp[j] += dp[j ^ (1 << i)];
        }
    }
    int ans = 0;
    int S = (1 << 24) - 1;
    for (int i = 0; i <= S; i++) ans ^= (n - dp[i]) * (n - dp[i]);
    cout << ans << "\n";
    return 0;
}

标签:DP1,min,int,freopen,include,data,dp
From: https://www.cnblogs.com/forgotmyhandle/p/18282596

相关文章

  • DP10RF001一款200MHz~960MHz 低功耗(G)FSK/OOK无线收发芯片应用无线遥控工控设备无线
    产品概述.DP10RF001是一款工作于200MHz~960MHz范围内的低功耗、高性能、单片集成的(G)FSK/OOK无线收发机芯片。内部集成完整的射频接收机、射频发射机、频率综合器、调制解调器,只需配备简单、低成本的外围器件就可以获得良好的收发性能。芯片支持灵活可设的数据包格式,支持自动应......
  • 动态规划---线性dp1
    7-3最大子序列总和给定K个整数序列{N1,N2,...,NK}。连续子序列定义为{Ni,Ni+1,...,Nj},其中1≤i≤j≤K。最大子序列是具有最大元素总和的连续子序列。例如,给定序列{-2,11,-4,13,-5,-2},其最大子序列为{11,-4,13},最大和为20。现在,您应该找到最大的总和,以及......
  • 龙迅#LT8712SX适用于Type-C/DP1.4转两路Type-C/DP1.4/HDMI2.0应用方案,支持MST和SST功
    1.描述LT8712SX是一款高性能Type-C/DP1.4转Type-C/DP1.4/HD-DVI2.0/DP++转换器,具有两个可配置的DP1.4/HD-DVI2.0输出接口和音频输出接口。LT8712SX支持DisplayPort™单流传输(SST)模式和多流传输(MST)模式。当接收通过单个DP链路打包和传输的多个视频/音频流时,LT8712SX......
  • DP19 最长公共子序列(一)C
    建议直接网上看思路....#include<stdio.h>intmax(inti,intj){if(i>j)returni;returnj;}intmaxlength[1001][1001];intmain(){intn,m;while(scanf("%d%d",&n,&m)!=EOF){charc=getchar();//读取换行char......
  • DP1363F高性能、多协议NFC读卡IC
     DP1363F是一款高度集成的非接触读写芯片,集强大的多协议支持、最高射频输出功率,以及突破性技术低功耗卡片检测等优势于一身,满足市场对更高集成度、更小外壳和互操作性的需求,适用于银行、电子政务、交通、移动支付等众多基础设施应用。相关参数DP1363F支持下列操作模式:•读写......
  • AtcoderDP1
    AtcoderDP1收录非计数dp题。[ABC227F]TreasureHunting(2323)Problem给你一个\(N\timesM\)的矩阵,你需要从坐标\((1,1)\)走到坐标\((N,M)\)去,每次只能向右或者向下走。坐标\((i,j)\)的价值是\(A_{i,j}\)。我们定义一条路径的价值是,这条路径经过的坐标的前\(K\)......
  • CodeforcesDP1
    CodeforcesDP1CF833BTheBakery(2200)Problem将一个长度为\(n\)的序列\(a\)分为\(k\)段,使得总价值最大。一段区间的价值表示为区间内不同数字的个数。\(n\leq35000,k\leqmin(n,50),1\lea_i\len\)。Solution记\(f_{i,l,j}\)表示考虑前\(i\)个数,划分成\(......
  • DP1
    DP1P2523[HAOI2011]Problemc从后往前考虑,容易判掉无解。启发我们计数也从后往前考虑,设\(f[i][j]\)表示考虑到\([i,n]\)的位置,确定了\(j\)个人的编号的方案数。转移枚举之前确定了多少个人、在当前位置确定多少个人即可。CF311BCatsTransport求出正好接到每只小......
  • nefu-dp1 (线性dp)
    nefu-dp1https://vjudge.csgrandeur.cn/contest/571200#overview感谢z神的题单dp废物来打基础了。(感觉难度大概是递减的)琪露诺单调队列优化dp#include<bits/stdc++.h>usingnamespacestd;constintN=2e5+5;intf[N],a[N],n,l,r;//f[i]:i结尾的最大值in......
  • eDP1.4a/DP1.4 转 GMSL2 方案
    GMSL2点屏及老化,应用于车载中控及仪表屏 ......