【10.5校内测试】【DP】【概率】

转移都很明显的一道DP题。按照不优化的思路,定义状态$dp[i][j][0/1]$表示吃到第$i$天,当前胃容量为$j$,前一天吃(1)或不吃(0)时能够得到的最大价值。

因为有一个两天不吃可以复原容量的定义,所以需要前一天的状态。

而注意,容量表示的是当前第$i$天吃之前的容量。

然后考虑压缩空间,将天数滚动。要注意的是滚动过后$now$指向的是$i$后一天的状态,因此刷表更新。

#include<bits/stdc++.h>
using namespace std;

int n, m;
int a[1005], dp[2][20005][3], ap[20005];

int main() {
    freopen("buffet.in", "r", stdin);
    freopen("buffet.out", "w", stdout);
    scanf("%d%d", &n, &m);
    int ans = 0;
    for(int i = 1; i <= n; i ++)    scanf("%d", &a[i]);
    ap[1] = m;
    for(int i = 2; i <= n; i ++) ap[i] = ap[i-1] * 2 / 3;
    int now = 0;
    memset(dp[now], -1, sizeof(dp[now]));
    dp[0][1][0] = 0; 
    for(int i = 1; i <= n; i ++) {
        now ^= 1;
        memset(dp[now], -1, sizeof(dp[now]));
        for(int j = 1; j <= n; j ++) {
            if(~dp[now^1][j][0]) {
                dp[now][1][0] = max(dp[now][1][0], dp[now^1][j][0]);
                dp[now][j+1][1] = max(dp[now][j+1][1], dp[now^1][j][0] + min(a[i], ap[j]));
            }
            if(~dp[now^1][j][1]) {
                dp[now][j+1][1] = max(dp[now][j+1][1], dp[now^1][j][1] + min(a[i], ap[j]));
                dp[now][j-1][0] = max(dp[now][j-1][0], dp[now^1][j][1]);
            }
        }
    }
    for(int i = 1; i <= n; i ++)
        ans = max(ans, max(dp[now][i][1], dp[now][i][0]));
    printf("%d", ans);
    return 0;
}

概率神题,三校只有$yuli$dalao$A$掉了%%%%

非常神奇的记忆化搜索,就算我能把式子推出来也会弄晕的QAQ

定义$dp[i][j]$表示当前剩余$i$个人,当前编号为$j$的人的存活概率。枪在当前1号手中。

注意这个当前,表示的是此时剩下来的人重新从0编号。

可以推出转移式子:$dp[i][j]=q*dp[i-1][j-1]+(1-q)*dp[i][(j-kmodi+i)modi]$,$q$表示当前这枪打出去能打出来的概率。$q=(i-1)/C$,因为当前子弹比人数要少1。前一个式子是打出了这个枪,于是0死了,j在剩下的人中号数要-1,(1把枪移交2相当与2变为了现在的1),后面的式子是打了空枪,枪移交给后面第k个人,同样也是把整个队列往前移k位。

观察式子可以发现,$dp[i-1][j-1]$我们可以在记忆化搜索中递归求得,而后面$dp[i][(j-kmodi+i)modi]$是与$dp[i][j]$同层的,考虑怎么求得。

可以发现,如果一直打空枪,从$j$开始,一定可以通过环走回$j$。所以在递归边界式子变为$dp[i][j]=o+(1-q)^ndp[i][j]$,其中每个$(1-q)$虽然不同,但在搜索过程中可以顺便算出来。$o$表示的是在以后的层数中可以求得的,因为我们可以把$dp[i][(j-kmodi+i)modi]$带入最开始的转移式,再把它们按层数分离。然后就按分离出来的式子将每一步更新即可。

过程中记忆化即可。然后概率要用逆元,可以预处理出来。

#include<bits/stdc++.h>
#define LL long long
#define mod 1000000009
using namespace std;

int T, N, C, K;

int vf[1005];
int dp[1005][1005], vis[1005][1005];
LL mpow(int a, LL b) {
    LL ans = 1;
    for(; b; b >>= 1, a = 1ll * a * a % mod)
        if(b & 1)    ans = 1ll * ans * a % mod;
    return ans;
}

int dfs(int res, int pos, int oo, int gl) {
    if(pos == -1)    return 0;
    if(res == 1)    return 1;
    if(vis[res][pos] && dp[res][pos] == -1) {//同层中走回来了 可以直接算 
        dp[res][pos] = 1ll * oo * mpow((1 - gl + mod) % mod, mod - 2) % mod;
        return dp[res][pos];
    }
    if(vis[res][pos])    return dp[res][pos];//记忆化 
    vis[res][pos] = 1;
    dfs(res, (pos - K % res + res) % res, (oo + 1ll * gl * (res - 1) % mod * vf[C] % mod * dfs(res - 1, pos - 1, 0, 1) % mod) % mod, 1ll * gl * (C - res + 1) % mod * vf[C] % mod);//子弹共有res-1个 
    if(~dp[res][pos])    return dp[res][pos];
    dp[res][pos] = (1ll * (res - 1) * vf[C] % mod * dfs(res - 1, pos - 1, 0, 1) % mod + 1ll * (C - res + 1) * vf[C] % mod * dp[res][(pos - K % res + res) % res] % mod) % mod;
    return dp[res][pos];
}

int main() {
    freopen("gun.in", "r", stdin);
    freopen("gun.out", "w", stdout);
    scanf("%d", &T);
    for(int i = 1; i <= 1000; i ++)    vf[i] = mpow(i, mod - 2);
    while(T --) {
        memset(vis, 0, sizeof(vis));
        memset(dp, -1, sizeof(dp));
        scanf("%d%d%d", &N, &C, &K);
        for(int i = 0; i < N; i ++)
            printf("%d ", dfs(N, i, 0, 1));
        printf("\n");
    }
}

 

posted @ 2018-10-05 16:31  Wans_ovo  阅读(147)  评论(0编辑  收藏  举报