20250615 集训 DP
JSOI2018] 潜入行动
给定一棵树,要求在树上恰好放置 kk 个监视器,每个监视器放在某个结点上,可以监视
到与其直接相邻的结点,但无法监视自己。一个结点至多有一个监视器,问有多少种放
置监视器的方式,使得整棵树所有结点被监视。答案  mod 109+7mod109+7。

这题坑点在于
你滚动数组每个节点需要先记录目前所有状态.
这玩意用long long会被卡,用int容易爆炸
转移很好理解
#include<bits/stdc++.h>
using namespace std;
constexpr int maxn = 1e5+10;
constexpr int maxk = 110;
constexpr int MOD = 1e9+7;
#define int long long 
struct edge{
    int32_t to,next;
}e[maxn<<1];
int32_t head[maxn],cnt;
void addedge(int u,int v){
    e[++cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}
int32_t n, k;
int f[maxn][maxk][2][2];
int32_t siz[maxn];
inline void dfs(int u,int fa){
    siz[u] = f[u][0][0][0] = f[u][1][1][0] = 1;
    for(int i = head[u];i;i = e[i].next){
        int v = e[i].to;
        if(v == fa) continue;
        dfs(v,u);
        int g[maxk][2][2];
        for(int i = 0;i < maxk;i++) g[i][0][0] = g[i][0][1] = g[i][1][1] = g[i][1][0] = 0;
        for(int i = 0;i <= min(siz[u],k);i++){
            g[i][0][0] = f[u][i][0][0];
            g[i][0][1] = f[u][i][0][1];
            g[i][1][0] = f[u][i][1][0];
            g[i][1][1] = f[u][i][1][1];
            f[u][i][0][0] = f[u][i][0][1] = f[u][i][1][0] = f[u][i][1][1] = 0;
        }
        for(int32_t i = 0;i <= min(siz[u],k);i++){
            for(int32_t j = 0;j <= min(siz[v],k-i);j++){
                f[u][i+j][0][0] += f[v][j][0][1] * g[i][0][0] % MOD;
                f[u][i+j][1][0] += (f[v][j][0][0] + f[v][j][0][1]) * g[i][1][0] % MOD;
                f[u][i+j][0][1] += f[v][j][1][1] * g[i][0][0] % MOD + 
                    (f[v][j][0][1] + f[v][j][1][1]) * g[i][0][1] % MOD;
                f[u][i+j][1][1] += (f[v][j][1][0] + f[v][j][1][1]) * g[i][1][0] % MOD + 
                    (f[v][j][0][0] + f[v][j][0][1] + f[v][j][1][0] + f[v][j][1][1]) * g[i][1][1] % MOD;
                f[u][i+j][0][1] %= MOD,f[u][i+j][1][1] %= MOD;
            }    
        }
        siz[u] += siz[v];
    }
}
    
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n>>k;
    for(int i = 1;i < n;i++){
        int u,v;
        cin>>u>>v;
        addedge(u,v),addedge(v,u);
    }
    
    dfs(1,0);
    
    cout<<(f[1][k][0][1] + f[1][k][1][1]) % MOD << endl;
    return 0;
}
双木棋
Alice 和 Bob 下棋。
棋局开始时,棋盘上没有任何棋子,两人轮流在格子上落子,直到填满棋盘时结束。
落子的规则是:一个格子可以落子当且仅当这个格子内没有棋子且这个格子的左侧及上方的所有格子内都有棋子。
每个格子有 ai,jai,j 和 bi,jbi,j,分别代表 Alice 和 Bob 在这个格子落子获得的分数。双方都希望自己的分数减去对方的分数尽可能大,求最后双方分数差。
#include<bits/stdc++.h>
using namespace std;
constexpr int maxn = 11;
constexpr int inf = 1e9;
int a[maxn][maxn];
int b[maxn][maxn];
int f[1 << (maxn << 1)];
int n, m;
inline int dfs(int now, bool flag) {
    if(~f[now]) return f[now];
    if(flag) f[now] = -inf;
    else f[now] = inf;
    int x = n, y = 0; 
    for(int i = 0; i < n + m - 1; i++) { 
        if((now >> i) & 1) x--;
        else y++;
        if(((now >> i) & 3) != 1) continue;  
        int next = now ^ (3 << i);
        if(flag) {
            f[now] = max(f[now], dfs(next, 0) + a[x+1][y+1]);  
        }
        else {
            f[now] = min(f[now], dfs(next, 1) - b[x+1][y+1]);  
        }
    }
    return f[now];
}
int main() {
    ios::sync_with_stdio(0);
    cin >> n >> m;
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            cin >> a[i][j];
        }
    }
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            cin >> b[i][j];
        }
    }
    for(int i = 0;i < (1 << (maxn << 1));i++) f[i] = -1;
    f[((1 << n) - 1) << m] = 0;
    cout << dfs((1 << n) - 1, 1) << endl;
    return 0;
}
考虑轮廓线, 因为n是10,直接记录状态无法接受,但是发现已经下棋的和没有下棋的构成一个轮廓线.
我们记从左下角走到右上角的轮廓线, 然后dp转移即可.
[NOIP2021] 数列
给定整数 n,m,kn,m,k,和一个长度为 m+1m+1 的正整数数组 v0,v1,…,vmv0,v1,…,vm。
对于一个长度为 nn,下标从 11 开始且每个元素均不超过 mm 的非负整数序列 {ai}{ai},我们定义它的权值为 va1×va2×⋯×vanva1×va2×⋯×van。
当这样的序列 {ai}{ai} 满足整数 S=2a1+2a2+⋯+2anS=2a1+2a2+⋯+2an 的二进制表示中 11 的个数不超过 kk 时,我们认为 {ai}{ai} 是一个合法序列。
计算所有合法序列 {ai}{ai} 的权值和对 998244353998244353 取模的结果。
想到转移就做完了.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
constexpr ll mod = 998244353;
ll ans, v[105], dp[105][35][35][16], pv[105][35];
ll C[35][35];
inline void init(int n) {
    for (int i = 0; i <= n; i++) C[i][0] = 1;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= i; j++) {
            C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
        }
    }
}
int main() {
    init(30);
    int n, m, K;
    cin >> n >> m >> K;
    for (int i = 0; i <= m; i++) {
        cin >> v[i];
        pv[i][0] = 1;
        for (int j = 1; j <= n; j++) pv[i][j] = pv[i][j - 1] * v[i] % mod;
    }
    dp[0][0][0][0] = 1;
    for (int i = 0; i <= m; i++) {
        for (int j = 0; j <= n; j++) {
            for (int k = 0; k <= K; k++) {
                for (int p = 0; p <= n >> 1; p++) {
                    for (int t = 0; t <= n - j; t++) {
                        int nxj = j + t;
                        int nxk = k + ((t + p) & 1);
                        int nxp = (t + p) >> 1;
                        if (nxj <= n && nxk <= K && nxp <= (n >> 1)) {
                            dp[i + 1][nxj][nxk][nxp] = (dp[i + 1][nxj][nxk][nxp] + dp[i][j][k][p] * pv[i][t] % mod * C[n - j][t] % mod) % mod;
                        }
                    }
                }
            }
        }
    }
    for (int k = 0; k <= K; k++) {
        for (int p = 0; p <= n >> 1; p++) {
            if (k + __builtin_popcount(p) <= K) ans = (ans + dp[m + 1][n][k][p]) % mod;
        }
    }
    cout << ans << endl;
    return 0;
}
联合省选2023 圣诞树
发现一个性质
电线一定不能交叉
所以就变成了区间dp
设计状态 f(l,r,0/1) 表示经过区间 [l,r] 内的点,当前位置在左/右时的最小花费。
#include<bits/stdc++.h>
using namespace std;
constexpr int maxn = 2e3 + 10;
double X[maxn], Y[maxn];
inline double dis(int x, int y) {
    return sqrt((X[x] - X[y]) * (X[x] - X[y]) + (Y[x] - Y[y]) * (Y[x] - Y[y]));
}
int n;
double dp[maxn][maxn][2];
int pre_l[maxn][maxn][2], pre_r[maxn][maxn][2], pre_f[maxn][maxn][2];
double N = -1e9;
int sti;
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> X[i] >> Y[i];
        X[i + n] = X[i], Y[i + n] = Y[i];
        if (Y[i] > N) {
            N = Y[i];
            sti = i;
        }
    }
    for (int i = 0; i < maxn; i++) {
        for (int j = 0; j < maxn; j++) {
            dp[i][j][0] = dp[i][j][1] = 1e18;
        }
    }
    dp[sti][sti][0] = dp[sti][sti][1] = 0;
    dp[sti + n][sti + n][0] = dp[sti + n][sti + n][1] = 0;
    for (int len = 2; len <= n; len++) {
        for (int i = 1; i + len - 1 <= n * 2; i++) {
            int j = i + len - 1;
            double x = dp[i + 1][j][0] + dis(i, i + 1);
            double y = dp[i + 1][j][1] + dis(i, j);
            if (x < y) {
                dp[i][j][0] = x;
                pre_l[i][j][0] = i + 1;
                pre_r[i][j][0] = j;
                pre_f[i][j][0] = 0;
            } else {
                dp[i][j][0] = y;
                pre_l[i][j][0] = i + 1;
                pre_r[i][j][0] = j;
                pre_f[i][j][0] = 1;
            }
            x = dp[i][j - 1][0] + dis(i, j);
            y = dp[i][j - 1][1] + dis(j - 1, j);
            if (x < y) {
                dp[i][j][1] = x;
                pre_l[i][j][1] = i;
                pre_r[i][j][1] = j - 1;
                pre_f[i][j][1] = 0;
            } else {
                dp[i][j][1] = y;
                pre_l[i][j][1] = i;
                pre_r[i][j][1] = j - 1;
                pre_f[i][j][1] = 1;
            }
        }
    }
    double ans = 1e18;
    int ansl = 0, ansr = 0, ansf = 0;
    for (int i = 1; i + n - 1 <= n * 2; i++) {
        int j = i + n - 1;
        if (dp[i][j][0] < ans) {
            ans = dp[i][j][0];
            ansl = i;
            ansr = j;
            ansf = 0;
        }
        if (dp[i][j][1] < ans) {
            ans = dp[i][j][1];
            ansl = i;
            ansr = j;
            ansf = 1;
        }
    }
    stack<int> st;
    if (ansf == 0) st.push(ansl % n ? ansl % n : n);
    else st.push(ansr % n ? ansr % n : n);
    for (int k = 1; k < n; k++) {
        int l = pre_l[ansl][ansr][ansf];
        int r = pre_r[ansl][ansr][ansf];
        int f = pre_f[ansl][ansr][ansf];
        ansl = l; ansr = r; ansf = f;
        if (ansf == 0) st.push(ansl % n ? ansl % n : n);
        else st.push(ansr % n ? ansr % n : n);
    }
    //cout << (sti % n ? sti % n : n) << " ";
    while (!st.empty()) {
        cout << st.top() << " ";
        st.pop();
    }
    cout << endl;
    return 0;
}
P1654 OSU
随机生成长度为 nn 的 01 串,每一段极长的 1 长度为 xx 贡献为 x3x3,现在给出每一位是 1 的概率,问期望贡献是多少。

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn = 1e5+10;
#define int long long 
int n;
double p[maxn];
double ans[maxn];
double x1[maxn],x2[maxn];
signed main(){
    cin>>n;
    for(int i = 1;i <= n;i++){
        cin>>p[i];
    }
    for(int i = 1;i <= n;i++){
        x1[i] = (x1[i-1] + 1) * p[i];
        x2[i] = (x2[i-1] + 2 * x1[i-1] + 1) * p[i];
        ans[i]=ans[i-1]+(3*(x1[i-1]+x2[i-1])+1)*p[i];
    }
    
    printf("%.1lf",ans[n]);
    return 0;
}
 
                    
                 
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号