转移都很明显的一道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(" "); } }