2016 UESTC Training for Dynamic Programming
A - 柱爷与咸鱼神功
题意:
柱爷有n(<=5000)点心情去学m(<=5000)个招式,每个招式会得到一定的修炼值,但要消耗一定的心情,求最多的修炼值。
题解:
0.这是一个裸的背包问题,用每一个物品去更新每一种背包的状态.
1.状态定义:dp[i]表示用i点心情得到的最多修炼值。
2.状态转移:dp[i] = max{dp[i-v[j]]+w[j]}
代码:
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010;
int n,m,v[N],w[N],dp[N];
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= m;++i){
scanf("%d%d",&v[i],&w[i]);
}
for(int i = 1;i <= m;++i){
for(int j = n;j >= v[i];--j)
dp[j] = max(dp[j-v[i]]+w[i],dp[j]);
}
cout<<dp[n]<<endl;
return 0;
}//- xgtao -
B - 柱爷与最大区间和
题意:
给出一个长度为n(<=500000)的序列,输出两个不相邻区间的和。
题解:
0.因为要严格控制不相邻的区间,定义状态f[i][0/1]表示在1~i之前的最大区间和(并且不选或选第i点),g[i][0/1]表示在i~n之前的最大区间和(并且不选或选i点)。
1.f[i][0] = max{f[i-1][0],f[i-1][1]} f[i][1] = max{f[i-1][1]+w[i],w[i]} g[i][0] = max{g[i+1][0],g[i+1][1]} g[i][1] = max{g[i+1][1]+w[i],w[i]}
2.最后的答案即为max{f[i][0]+g[i][0],f[i][0]+g[i][1],f[i][1]+g[i][0]}
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int inf = 1<<30;
const int N = 500010;
int f[N][2],g[N][2],a[N],n,ans = -inf;
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;++i)scanf("%d",&a[i]);
f[0][0] = f[0][1] = -inf,g[n+1][0] = g[n+1][1] = -inf;
for(int i = 1;i <= n;++i){
f[i][1] = max(f[i-1][1]+a[i],a[i]);
f[i][0] = max(f[i-1][1],f[i-1][0]);
}
for(int i = n;i >= 1;--i){
g[i][1] = max(g[i+1][1]+a[i],a[i]);
g[i][0] = max(g[i+1][1],g[i+1][0]);
}
for(int i = 1;i <= n;++i){
int ret = max(max(f[i][0]+g[i+1][1],f[i][0]+g[i][0]),f[i][1]+g[i+1][0]);
ans = max(ans,ret);
}
cout<<ans<<endl;
return 0;
}//- xgtao -
C - 柱爷的下凡
题意:
用三种硬币表示1~n(<=200)的数,并且总共用的硬币数最少,多组数据(<=200)!!!!!
题解:
0.三种硬币中必定包含一枚1元。
1.那么另外两种硬币b,c就可以枚举,定义状态dp[i]表示表示出i元花费最少的硬币数。
2.状态转移即为:dp[i] = max{dp[i-b]+1,dp[i-c]+1,dp[i-1]+1},只需要计算Σdp[1~n]即可。
3.这是O(n^3)的算法,但是有多组数据就是O(n^4)。
4.我们可以通过打表解决,枚举n,预处理出所有n的情况,再用O(1)查询。
代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 500;
int T,n,dp[N],a = 1,ans2[N],ans3[N],ans = 1e9,ret;
int main(){
freopen("C_list.cpp","w",stdout);
ans2[1] = 2,ans3[1] = 3;
ans2[2] = 2,ans3[2] = 3;
ans2[3] = 2,ans3[3] = 3;
for(int i = 4;i <= 200;++i){
ans = 1e9;
for(int j = 2;j < i;++j){
for(int k = j+1;k <= i;++k){
ret = 0;
dp[0] = 0;
for(int c = 1;c <= i;++c){
if(c >= k)dp[c] = min(dp[c-k],min(dp[c-j],dp[c-1]))+1;
else if(c >= j)dp[c] = min(dp[c-j],dp[c-1])+1;
else if(c >= 1)dp[c] = dp[c-1]+1;
}
for(int c = 1;c <= i;++c)ret += dp[c];
if(ret < ans)ans = ret,ans2[i] = j,ans3[i] = k;
}
}
}
for(int i = 1;i <= 200;++i){
printf(" if(n == %d)printf",i);
printf("(\"1 %d %d\\n\"\);\n",ans2[i],ans3[i]);
}
return 0;
}
#include <cstdio>
int T,n;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
if(n == 1)printf("1 2 3\n");
if(n == 2)printf("1 2 3\n");
if(n == 3)printf("1 2 3\n");
if(n == 4)printf("1 2 3\n");
if(n == 5)printf("1 2 3\n");
if(n == 6)printf("1 2 3\n");
if(n == 7)printf("1 2 5\n");
if(n == 8)printf("1 3 4\n");
if(n == 9)printf("1 3 4\n");
if(n == 10)printf("1 2 5\n");
if(n == 11)printf("1 2 5\n");
if(n == 12)printf("1 4 6\n");
if(n == 13)printf("1 4 6\n");
if(n == 14)printf("1 4 6\n");
if(n == 15)printf("1 3 7\n");
if(n == 16)printf("1 4 6\n");
if(n == 17)printf("1 3 7\n");
if(n == 18)printf("1 4 6\n");
if(n == 19)printf("1 3 8\n");
if(n == 20)printf("1 3 8\n");
if(n == 21)printf("1 5 7\n");
if(n == 22)printf("1 4 9\n");
if(n == 23)printf("1 4 9\n");
if(n == 24)printf("1 5 8\n");
if(n == 25)printf("1 5 8\n");
if(n == 26)printf("1 5 8\n");
if(n == 27)printf("1 5 8\n");
if(n == 28)printf("1 4 9\n");
if(n == 29)printf("1 5 8\n");
if(n == 30)printf("1 5 8\n");
if(n == 31)printf("1 4 9\n");
if(n == 32)printf("1 5 8\n");
if(n == 33)printf("1 5 8\n");
if(n == 34)printf("1 7 11\n");
if(n == 35)printf("1 7 11\n");
if(n == 36)printf("1 7 11\n");
if(n == 37)printf("1 5 12\n");
if(n == 38)printf("1 5 12\n");
if(n == 39)printf("1 5 12\n");
if(n == 40)printf("1 7 11\n");
if(n == 41)printf("1 5 12\n");
if(n == 42)printf("1 5 12\n");
if(n == 43)printf("1 5 12\n");
if(n == 44)printf("1 7 11\n");
if(n == 45)printf("1 7 11\n");
if(n == 46)printf("1 7 11\n");
if(n == 47)printf("1 7 11\n");
if(n == 48)printf("1 6 14\n");
if(n == 49)printf("1 6 14\n");
if(n == 50)printf("1 6 14\n");
if(n == 51)printf("1 6 14\n");
if(n == 52)printf("1 8 13\n");
if(n == 53)printf("1 8 13\n");
if(n == 54)printf("1 8 13\n");
if(n == 55)printf("1 8 13\n");
if(n == 56)printf("1 8 13\n");
if(n == 57)printf("1 6 14\n");
if(n == 58)printf("1 6 14\n");
if(n == 59)printf("1 6 14\n");
if(n == 60)printf("1 8 13\n");
if(n == 61)printf("1 8 13\n");
if(n == 62)printf("1 6 14\n");
if(n == 63)printf("1 6 14\n");
if(n == 64)printf("1 6 14\n");
if(n == 65)printf("1 8 13\n");
if(n == 66)printf("1 8 13\n");
if(n == 67)printf("1 8 13\n");
if(n == 68)printf("1 8 13\n");
if(n == 69)printf("1 7 17\n");
if(n == 70)printf("1 7 17\n");
if(n == 71)printf("1 7 17\n");
if(n == 72)printf("1 7 17\n");
if(n == 73)printf("1 7 17\n");
if(n == 74)printf("1 9 14\n");
if(n == 75)printf("1 7 17\n");
if(n == 76)printf("1 7 17\n");
if(n == 77)printf("1 7 17\n");
if(n == 78)printf("1 7 17\n");
if(n == 79)printf("1 7 17\n");
if(n == 80)printf("1 10 16\n");
if(n == 81)printf("1 10 16\n");
if(n == 82)printf("1 10 16\n");
if(n == 83)printf("1 10 16\n");
if(n == 84)printf("1 8 19\n");
if(n == 85)printf("1 8 19\n");
if(n == 86)printf("1 8 19\n");
if(n == 87)printf("1 8 19\n");
if(n == 88)printf("1 6 20\n");
if(n == 89)printf("1 10 17\n");
if(n == 90)printf("1 11 15\n");
if(n == 91)printf("1 11 15\n");
if(n == 92)printf("1 11 15\n");
if(n == 93)printf("1 11 15\n");
if(n == 94)printf("1 11 18\n");
if(n == 95)printf("1 11 18\n");
if(n == 96)printf("1 12 19\n");
if(n == 97)printf("1 12 19\n");
if(n == 98)printf("1 12 19\n");
if(n == 99)printf("1 12 19\n");
if(n == 100)printf("1 12 19\n");
if(n == 101)printf("1 12 19\n");
if(n == 102)printf("1 12 19\n");
if(n == 103)printf("1 12 19\n");
if(n == 104)printf("1 12 19\n");
if(n == 105)printf("1 12 19\n");
if(n == 106)printf("1 12 19\n");
if(n == 107)printf("1 12 19\n");
if(n == 108)printf("1 12 19\n");
if(n == 109)printf("1 12 19\n");
if(n == 110)printf("1 12 19\n");
if(n == 111)printf("1 13 18\n");
if(n == 112)printf("1 12 19\n");
if(n == 113)printf("1 12 19\n");
if(n == 114)printf("1 12 19\n");
if(n == 115)printf("1 12 19\n");
if(n == 116)printf("1 12 19\n");
if(n == 117)printf("1 12 19\n");
if(n == 118)printf("1 12 19\n");
if(n == 119)printf("1 12 19\n");
if(n == 120)printf("1 12 19\n");
if(n == 121)printf("1 12 19\n");
if(n == 122)printf("1 12 19\n");
if(n == 123)printf("1 7 23\n");
if(n == 124)printf("1 7 23\n");
if(n == 125)printf("1 8 27\n");
if(n == 126)printf("1 8 27\n");
if(n == 127)printf("1 8 27\n");
if(n == 128)printf("1 9 23\n");
if(n == 129)printf("1 9 23\n");
if(n == 130)printf("1 9 23\n");
if(n == 131)printf("1 9 30\n");
if(n == 132)printf("1 9 30\n");
if(n == 133)printf("1 14 22\n");
if(n == 134)printf("1 14 22\n");
if(n == 135)printf("1 10 26\n");
if(n == 136)printf("1 14 22\n");
if(n == 137)printf("1 8 27\n");
if(n == 138)printf("1 8 27\n");
if(n == 139)printf("1 14 22\n");
if(n == 140)printf("1 8 27\n");
if(n == 141)printf("1 8 27\n");
if(n == 142)printf("1 10 26\n");
if(n == 143)printf("1 8 27\n");
if(n == 144)printf("1 8 27\n");
if(n == 145)printf("1 8 27\n");
if(n == 146)printf("1 8 27\n");
if(n == 147)printf("1 8 27\n");
if(n == 148)printf("1 8 27\n");
if(n == 149)printf("1 8 27\n");
if(n == 150)printf("1 8 27\n");
if(n == 151)printf("1 8 27\n");
if(n == 152)printf("1 8 27\n");
if(n == 153)printf("1 9 30\n");
if(n == 154)printf("1 9 30\n");
if(n == 155)printf("1 9 30\n");
if(n == 156)printf("1 9 30\n");
if(n == 157)printf("1 9 30\n");
if(n == 158)printf("1 9 30\n");
if(n == 159)printf("1 9 30\n");
if(n == 160)printf("1 9 30\n");
if(n == 161)printf("1 9 30\n");
if(n == 162)printf("1 9 30\n");
if(n == 163)printf("1 9 30\n");
if(n == 164)printf("1 9 30\n");
if(n == 165)printf("1 9 30\n");
if(n == 166)printf("1 9 30\n");
if(n == 167)printf("1 9 30\n");
if(n == 168)printf("1 9 30\n");
if(n == 169)printf("1 9 30\n");
if(n == 170)printf("1 9 30\n");
if(n == 171)printf("1 9 30\n");
if(n == 172)printf("1 12 31\n");
if(n == 173)printf("1 12 31\n");
if(n == 174)printf("1 12 31\n");
if(n == 175)printf("1 12 31\n");
if(n == 176)printf("1 10 34\n");
if(n == 177)printf("1 10 34\n");
if(n == 178)printf("1 10 34\n");
if(n == 179)printf("1 10 33\n");
if(n == 180)printf("1 10 34\n");
if(n == 181)printf("1 10 34\n");
if(n == 182)printf("1 10 34\n");
if(n == 183)printf("1 10 34\n");
if(n == 184)printf("1 10 34\n");
if(n == 185)printf("1 10 34\n");
if(n == 186)printf("1 10 34\n");
if(n == 187)printf("1 10 34\n");
if(n == 188)printf("1 12 31\n");
if(n == 189)printf("1 12 31\n");
if(n == 190)printf("1 12 31\n");
if(n == 191)printf("1 12 31\n");
if(n == 192)printf("1 12 31\n");
if(n == 193)printf("1 12 31\n");
if(n == 194)printf("1 12 31\n");
if(n == 195)printf("1 12 31\n");
if(n == 196)printf("1 12 31\n");
if(n == 197)printf("1 17 27\n");
if(n == 198)printf("1 12 31\n");
if(n == 199)printf("1 12 31\n");
if(n == 200)printf("1 12 31\n");
}
return 0;
}// - xgtao -
D - 柱爷的恋爱
题意:
给出一个包含") ( ] ["的括号序列,可以选择括号删除,但是不能全部删除,问有多少种方式让其成为合法的括号序列(%1000000007),
合法的括号序列定义如下:
- 如果S是合法序列,那(S)和[S]也是合法序列。
- 如果A和B都是合法序列,那么AB也是合法序列。
- (), [], (()), ([]), ()[], ()[()]。
题解:
0.因为如果A,B是合法的那么AB也是合法的,所以这是区间dp。
1.定义状态dp[i][j]表示在i~j这个区间内的合法方案数目。
2.状态转移种,括号面临两种决策,删还是不删。
3.如果删除当前的一个括号dp[i][j] = dp[i+1][j]。
4.如果不删除,但必须满足后面有一个括号能够和它匹配,如果i&k匹配那么[i+1,k-1]要合法,[k+1,j]也要合法 dp[i][j] += dp[i+1][k-1]*dp[k+1][j]。
5.因为计算的是dp[0][n-1]包含了把括号全部删除的情况,所以最后要+1。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int mod = 1000000007;
#define ll long long
const int N = 500;
ll f[N][N];
char bracket[N];
int n,vis[N][N];
bool flag(int l,int r){
if(bracket[l] == '(' && bracket[r] == ')')return true;
if(bracket[l] == '[' && bracket[r] == ']')return true;
return false;
}
ll dp(int l,int r){
if(vis[l][r])return f[l][r];
vis[l][r] = 1;
if(l >= r)return f[l][r] = 1;
f[l][r] = dp(l+1,r)%mod;
for(int k = l;k <= r;++k){
if(!flag(l,k))continue;
f[l][r] = (f[l][r]+dp(l+1,k-1)*dp(k+1,r))%mod;
}
return f[l][r]%mod;
}
int main(){
scanf("%d",&n);
scanf("%s",bracket);
cout<<(dp(0,n-1)-1)%mod<<endl;
return 0;
}// - xgtao -
E - 柱爷与远古法阵
题意:
有一排长度为n(<=300)的格子,从1开始走到n,有一个骰子(1~6),每次可以走骰子的点数那么多的步数,并且中途会有一些传送门,求走到n点的期望数。
题解:
0.定义状态dp[x]表示从x点走到n的期望步数。
1.状态转移有两种情况,这个点有还是没有传送门。
2.是有传送门:dp[x] = dp[x']
3.没有传送门并且x <= n-6:由全期望公式得到dp[x] = (dp[x+1]+dp[x+2]+dp[x+3]+dp[x+4]+dp[x+5]+dp[x+6])/6 + 1;
4.没有传送门并且x > n-6也就是面临越界的情况dp[x] = (dp[x]+dp[x]+dp[x+1]+...dp[n])/6 + 1;因为越界了之后可以选择不走,它就转移到了自己,。
5.边界就是dp[n] = 0。
6.这里有n个点可以列出n个式子,那么就是n元一次方程,用高斯消元就可以解出dp[1]。
代码:
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
const long double eps = 1e-14;
const int N = 400;
int n,m,link[N],u,v;
long double Gau[N][N];
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;++i)link[i] = i;
for(int i = 1;i <= m;++i){
scanf("%d%d",&u,&v);link[u] = v;
}
for(int i = 1;i < n;++i){
Gau[i][i] = 6.0;
if(link[i] != i)Gau[i][link[i]] = -6.0;
else{
Gau[i][n+1] = 6.0;
for(int j = 1;j <= 6;++j){
if(i+j <= n)Gau[i][i+j] = -1.0;
else {
Gau[i][i] -= 1.0;
}
}
}
}
Gau[n][n] = 1.0;
Gau[n][n+1] = 0;
for(int i = 1;i <= n;++i){
int cur = i;
for(int j = i+1;j <= n;++j)if(fabs(Gau[j][i])>eps)cur = j;
if(fabs(Gau[cur][i])>eps){
for(int j = i;j <= n+1;++j)swap(Gau[i][j],Gau[cur][j]);
for(int j = i+1;j <= n;++j){
if(fabs(Gau[j][i])<=eps)continue;
long double coe = Gau[j][i]/Gau[i][i];
for(int k = i;k <= n+1;++k){
Gau[j][k] -= Gau[i][k]*coe;
}
}
}
}
for(int i = n;i >= 1;--i){
for(int j = i+1;j <= n;++j){
if(fabs(Gau[i][j])<=eps)continue;
Gau[i][n+1] -= Gau[i][j]*Gau[j][n+1];
}
if(fabs(Gau[i][i])<=eps && fabs(Gau[i][n+1])>eps){
printf("-1\n");
return 0;
}
Gau[i][n+1] /= Gau[i][i];
}
printf("%.10lf\n",(double)Gau[1][n+1]);
return 0;
}// - xgtao -
F - 柱爷与三叉戟不得不说的故事
题意:
柱爷要修复三叉戟要15种元素,有两种方式,方式一:分别给出获得每一种元素的代价,方式二:分别给出获得一套元素的代价,要求一种元素不能重复获得,求最小代价。
题解:
0.数据范围是15,考虑状态压缩。
1.定义状态dp[S]表示获得S这个集合的元素的代价的最小值。
2.S是由其子集转移而来,状态转移dp[S] = min{dp[S0]+dp[S0^S]}。
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
int dp[400010];
int main(){
memset(dp,127,sizeof(dp));
for(int i = 0;i < 15;++i)scanf("%d",&dp[(1<<i)]);
scanf("%d",&n);
for(int i = 0;i < n;++i){
int m,x,set = 0;
scanf("%d",&m);
for(int j = 1;j <= m;++j){
scanf("%d",&x),set |= (1<<(x-1));
}
scanf("%d",&x);
dp[set] = min(dp[set],x);
}
int all = (1<<15); dp[0] = 0;
for(int S = 0;S < all;++S){
for(int S0 = S;S0;S0 = (S0-1)&S)dp[S] = min(dp[S],dp[S0]+dp[S0^S]);
}
printf("%d\n",dp[all-1]);
return 0;
}//- xgtao -
G - 柱爷与三叉戟
题意:
给出一个数n(<=10^500),定义函数F(x)表示x转化成二进制后1的个数,求满足1<=i<j<=n && F(i)>F(j)的对数。
题解:
0.首先考虑最原始最暴力的解法,从1开始枚举i,从i+1开始枚举j,再计算F(i)和F(j),如果F(i)>F(j)那么答案+1,但是发现F(x)计算多次,就发现可以记忆化,那么就考虑数位dp。
1.首先把n转成2进制数。
2.从高位开始枚举两个数i,j,始终保持i<j,并且记录F(i)与F(j)的差值,到了最后一位如果差值>0,就返回1。
3.状态定义:dp[pos][ch][lia][lib][lic]表示从高位考虑到pos位F(i)与F(j)之差为ch的个数,lia是枚举i的限制条件,lib是枚举j的限制条件,lic表示i是否小于j。
4.因为差值在某一位置可能为负数为了防止dp下标为负数,所以一开始dfs差值为N(N为二进制10^500的位数)。
代码:
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1667;
const int mod = 1000000007;
string str;
int dp[N+10][N*2+10][2][2][2];
string BigINt(string x){
string ret = "";
string c = "2333";
while(c.length()){
c = "";
char ch;
int i = 0;
while(i < x.length()){
ch = x[i]-'0';
if(ch>=2)c += static_cast<char>(ch/2+'0');
else if(c.length())c += '0';
if(ch%2 == 1 && i < x.length()-1)x[i+1] += 10;
++i;
}
ret = ret+static_cast<char>(ch%2+'0');
x = c;
}
return ret;
}
int dfs(int pos,int ch,bool lia,bool lib,bool lic){
if(pos == str.length())return (lic == 1 && ch > N);
if(dp[pos][ch][lia][lib][lic] != -1)return dp[pos][ch][lia][lib][lic];
int &ret = dp[pos][ch][lia][lib][lic] = 0;
int end1 = lia ? str[pos]-'0' : 1;
int end2 = lib ? str[pos]-'0' : 1;
for(int i = 0;i <= end1;++i){
for(int j = 0;j <= end2;++j){
if(!lic && i > j)continue;
ret = (ret+dfs(pos+1,ch+(i==1)-(j==1),lia&&i==end1,lib&&j==end2,lic||i<j))%mod;
}
}
return ret%mod;
}
int main(){
cin>>str;
str = BigINt(str);
reverse(str.begin(),str.end());
memset(dp,-1,sizeof(dp));
printf("%d\n",dfs(0,N,1,1,0));
return 0;
}// - xgtao -
H - 柱爷大战滑稽王
题意:
给出两个长度为n(<=1000000)序列A,B,(|Ai,Bi|<=10^9),保证同一序列数字不重复,求这两个序列的最长公共子序列。
题解:
0.每个数字数据范围太大需要离散。
1.把A中的每一个数字出现的位置记录下来,在B中把A中出现的数的位置按B排好。
也就是说 A: 1 5 6 9 4
B: 1 6 9 4 5 对应关系也就是:1在A中的位置是1,6在A中的位置是3,9在A中的位置是4,4在A中的位置是5,5在A中的位置是2。
对应得到 C: 1 3 4 5 2
2.问题转化为求C得LIS,LIS的O(nlogn),就是在一个序列中不断替换第一个大于等于当前的数,因为这样序列就更有延展性,答案保证不会差,但是并不一定是这一个序列,但是长度一定相同的。
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1000001;
int n,m,x,cnt,ans,p[N],g[N],dp[N],id[N],c[N],ncnt,hase[N];
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;++i){
scanf("%d",&c[i]);
hase[++ncnt] = c[i];
}
sort(hase+1,hase+ncnt+1);
for(int i = 1;i <= n;++i){
int l = lower_bound(hase+1,hase+1+ncnt,c[i])-hase;
id[l] = i;
}
for(int i = 1;i <= m;++i){
scanf("%d",&x);
int l = lower_bound(hase+1,hase+1+ncnt,x)-hase;
if(hase[l] != x)continue;
p[++cnt] = id[l];
}
memset(g,127,sizeof(g));
for(int i = 1;i <= cnt;++i){
int k = lower_bound(g+1,g+cnt+1,p[i])-g;
dp[i] = k;
g[k] = p[i];
ans = max(ans,dp[i]);
}
cout<<ans+1<<endl;
return 0;
}// - xgtao -
I - 柱爷抢银行
题意:
给出n*m的矩阵,表示aij表示i行j列的金钱数目,如果aij<0,如果选择了这点会损失abs(aij),如果aij>0,就可以直接得到钱,柱爷因为实力很强所以要走一个联通块,柱爷不是个退缩的人!来了就要抢。
题解:
0.状态定义:dpl[i][j]表示在i行j列向左走能够得到的最大值,dpr[i][j]表示在i行j列向右走的最大值,dpu[i][k]表示链接(i,k)这个点的上面的联通块的最大值。
1.状态转移:dpl,dpr不难写出dpl[i][j+1] = max{dpl[i][j]+mat[i][j+1]} dpr[i][j-1] = max{dpr[i][j]+mat[i][j-1]}
2.状态转移:dpu有两种决策
①:dpu[i+1][j] = max{dpu[i][k]+sum[j]-sum[k-1]+dpr[i][j+1]+dpl[i][k-1]} = max{dpu[i][k]-sum[k-1]+dpl[i][k-1]}+sum[j]+dpr[i][j+1]}
也就是当(k<=j)时维护max{dpu[i][k]-sum[k-1]+dpl[i][k-1]}
②:dpu[i+1][j] = max{dpu[i][k]+sum[k]-sum[j-1]+dpr[i][k+1]+dpl[i][j-1]} = max{dpu[i][k]+sum[k]+dpr[i][k+1]}-sum[j-1]+dpl[i][j-1]}
也就是当(k>=j)时维护max{dpu[i][k]+sum[k]+dpr[i][k+1]}
3.答案也就是max{dpu[i+1][j],dpl[i][j-1]+mat[i][j]+dp[i][j+1]}
4.如果为空集的话就特判一下。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
const int N = 1010;
const ll inf = 1e20;
int n,m,flag;
ll sum[N][N],dpl[N][N],dpr[N][N],dpu[N][N],maxg,mat[N][N],maxp,ret;
int main(){
scanf("%d%d",&n,&m);
maxp = ret = maxg = -inf;
for(int i = 1;i <= n;++i){
for(int j = 1;j <= m;++j){
cin>>mat[i][j];
if(!flag && mat[i][j]>0)flag = 1;
maxg = max(maxg,mat[i][j]);
sum[i][j] = sum[i][j-1]+mat[i][j];
}
}
for(int i = 1;i <= n;++i){
for(int k = 1;k <= m;++k){
dpl[i][k] = max(dpl[i][k],mat[i][k]);
dpl[i][k+1] = max(dpl[i][k+1],dpl[i][k]+mat[i][k+1]);
}
for(int k = m;k >= 1;--k){
dpr[i][k] = max(dpr[i][k],mat[i][k]);
dpr[i][k-1] = max(dpr[i][k-1],dpr[i][k]+mat[i][k-1]);
}
maxp = -inf;
for(int k = 1;k <= m;++k){
maxp = max(maxp,dpu[i][k]-sum[i][k-1]+dpl[i][k-1]);
dpu[i+1][k] = max(dpu[i+1][k],maxp+sum[i][k]+dpr[i][k+1]);
ret = max(ret,dpl[i][k-1]+mat[i][k]+dpr[i][k+1]);
ret = max(ret,dpu[i+1][k]);
}
maxp = -inf;
for(int k = m;k >= 1;--k){
maxp = max(maxp,dpu[i][k]+sum[i][k]+dpr[i][k+1]);
dpu[i+1][k] = max(dpu[i+1][k],maxp-sum[i][k-1]+dpl[i][k-1]);
ret = max(ret,dpl[i][k-1]+mat[i][k]+dpr[i][k+1]);
ret = max(ret,dpu[i+1][k]);
}
}
if(!flag && ret == 0)ret = maxg;
cout<<ret<<endl;
return 0;
} // - xgtao -
J - 柱爷抢银行II
题意:
有n(<=1000000)个银行是按照顺时针的圈排布,给出在每个银行可以抢多少钱,柱爷决定选择不超过长度为k(<=1000000)的区间里面抢钱,求最多能抢多少?柱爷不是个退缩的人!来了就要抢!
题解:
0.既然是环,那么就需要断环为链。
1.定义状态dp[i]表示在i点之前长度不超过k的区间最大和。
2.状态转移很容易想到dp[i] = max{dp[i-1],sum[i]-sum[j-1]}(i-k+1<=j<=i) 复杂度为O(n*k),这个复杂度伤不起。
3.就用单调递增队列维护sum[j-1],单调队列的头就是最优sum的下标。注意维护的下标是j-1
代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 4000000;
int n,k,a[N],sum[N],dp[N],ansl,ansr;
class singlequeue{
protected:
int q[N],head,tail;
public:
void pop_back(){--tail;}
void pop_front(){++head;}
int back(){return q[tail];}
int front(){return q[head];}
int size(){return tail-head+1;}
void push_back(int x){q[++tail] = x;}
void init(){head = 0,tail = -1;}
bool empty(){return tail-head+1>0 ? 0 : 1;}
singlequeue(){init();}
}sq;
int main(){
scanf("%d%d",&n,&k);
for(int i = 1;i <= n;++i){
scanf("%d",&a[i]),a[n+i] = a[i];
}
for(int i = 1;i <= 2*n;++i){
sum[i] = sum[i-1]+a[i];
}
sq.init();ansl = ansr = 1;
sq.push_back(1); dp[1] = sum[1];
for(int i = 2;i <= 2*n;++i){
while(!sq.empty() && sq.front() < i-k) sq.pop_front();
if(dp[i-1] < sum[i]-sum[sq.front()]){
dp[i] = sum[i]-sum[sq.front()];
ansl = sq.front() + 1;
ansr = i;
}
else dp[i] = dp[i-1];
while(!sq.empty() && sum[i] < sum[sq.back()])sq.pop_back();
sq.push_back(i);
}
printf("%d %d %d\n",dp[2*n],ansl>n?ansl-n:ansl,ansr>n?ansr-n:ansr);
return 0;
}// - xgtao -
K - 柱爷抢银行III
题意:
给出一棵树,节点数<=500,每一个节点代表一个银行,柱爷抢银行太熟练所以抢银行不会消耗精力,但是柱爷从一个银行到另外一个银行会消耗精力,有q个询问,给出一开始柱爷的精力,问柱爷从0号点出发最多能抢几家银行?
题解:
0.不难看出这是一个树形dp,定义状态dp[u][i][0/1]表示在根节点为u的子树中抢i家银行并且当前是否在i这个节点最小的精力消耗。
1.状态转移:dp[u][i+k][0] = max{dp[u][i][1]+dp[v][k][1]+w,dp[u][i][0]+dp[v][k][1]+2*w,dp[u][i][1]+dp[v][k][0]+w}
dp[u][i+k][1] = max{dp[u][i][1]+dp[v][k][1]+2*w}
2.最后只需倒序枚举节点数,找到第一个小于等于原始精力的min{dp[0][i][1],dp[0][i][0]},输出i。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510;
struct edge{
int v,w;
edge *nxt;
}*head[N],*cur,Edge[N*2];
int dp[N][N][2],size[N],n,u,v,w,q,x;
void addedge(int u,int v,int w){
cur->v = v;
cur->w = w;
cur->nxt = head[u];
head[u] = cur++;
}
int dfs(int u,int fa){
dp[u][1][1] = 0;
size[u] = 1;
for(edge *it = head[u];it;it = it->nxt){
int v = it->v;
if(v == fa)continue;
size[v] = dfs(v,u);
for(int i = size[u];i >= 1;--i){
for(int k = size[v];k >= 1;--k){
dp[u][i+k][0] = min(dp[u][i+k][0],dp[u][i][0]+dp[v][k][1]+2*it->w);
dp[u][i+k][0] = min(dp[u][i+k][0],dp[u][i][1]+dp[v][k][0]+1*it->w);
dp[u][i+k][0] = min(dp[u][i+k][0],dp[u][i][1]+dp[v][k][1]+1*it->w);
dp[u][i+k][1] = min(dp[u][i+k][1],dp[u][i][1]+dp[v][k][1]+2*it->w);
}
}
size[u] += size[v];
}
return size[u];
}
int main(){
cur = Edge;
scanf("%d",&n);
for(int i = 0;i < n-1;++i){
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
addedge(v,u,w);
}
scanf("%d",&q);
memset(dp,127,sizeof(dp));
dfs(0,-1);
while(q--){
scanf("%d",&x);
for(int i = n;i >= 1;--i){
if(min(dp[0][i][0],dp[0][i][1])<=x){
printf("%d\n",i);
break;
}
}
}
return 0;
}// - xgtao -
L - 柱爷抢银行MkⅣ
题意:
一条街上有n(<=100000)个银行,对于第i个银行位置是x[i](<=109),钱是w[i](<=109),并且对于i号银行,只有位置比i小y[i]以内的银行才能到i。柱爷不是个退缩的人!来了就要抢!
题解:
0.因为位置的数据范围太大,需要离散,再把银行按照位置从小到大排序。
1.定义状态dp[i]表示到达i号银行所得的最多的钱数。
2.状态转移dp[i] = max{dp[j]+w[i]}(i-y[i]<=j<i)复杂度为O(n*n)所以需要优化。
3.用线段树维护区间[i-y[i],i]这个区间里的极大值,再单点修改i点的极大值
4.转台转移即为dp[i] = query(1,1,n,i-y[i],i)+w[i],修改即为update(1,1,n,i,dp[i])
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
const int N = 100010;
struct gg{ll v;int x,y;}bank[N];
int hase[N],cnt;
ll maxi[N<<2],n,dp[N];
ll query(int k,int l,int r,int L,int R){
if(L <= l && R >= r)return maxi[k];
int mid = (l+r)>>1;
ll ret = 0;
if(L <= mid)ret = max(ret,query(k<<1,l,mid,L,R));
if(R > mid)ret = max(ret,query(k<<1|1,mid+1,r,L,R));
return ret;
}
void update(int k,int l,int r,int x,ll w){
if(l == r){
maxi[k] = max(maxi[k],w);
return;
}
int mid = (l+r)>>1;
if(x <= mid)update(k<<1,l,mid,x,w);
else if(x > mid)update(k<<1|1,mid+1,r,x,w);
maxi[k] = max(maxi[k<<1],maxi[k<<1|1]);
}
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;++i){
scanf("%d%d%d",&bank[i].x,&bank[i].v,&bank[i].y);
hase[++cnt] = bank[i].x;
}
sort(hase+1,hase+1+cnt);
cnt = unique(hase+1,hase+cnt+1)-hase-1;
for(int i = 1;i <= n;++i){
int r = lower_bound(hase+1,hase+1+cnt,bank[i].x)-hase;
int l = lower_bound(hase+1,hase+1+cnt,bank[i].x-bank[i].y)-hase;
dp[i] = bank[i].v+query(1,1,cnt,l,r);
update(1,1,cnt,r,dp[i]);
}
ll ans = 0;
for(int i = 1;i <= n;++i)ans = max(ans,dp[i]);
cout<<ans<<endl;
return 0;
}// - xgtao -
M - 柱爷抢银行欢庆5.1special
题意:
给出一个n*m(<=500)的方阵,方阵中的每个格子都有一家银行,柱爷抢这家银行能得到aij的钱,当aij<0柱爷会损失的钱。他决定要抢的银行要形成k阶顺时针螺旋状,其中k为≥3的任意奇数。下面是k=3,5,7的图形,其中黑色方块是柱爷要抢钱的银行。求最多抢的钱数,柱爷不是个退缩的人!来了就要抢!
题解:
0.很容易观察出pic3的白色部分和pic2的黑色部分形状是一样的,只不过多了那一小块。
1.定义状态:dp[i][j][k]表示以(i,j)这个点为左上角并且当前是第k阶的黑色部分的和,但是500*500*500会爆,所以要滚动~
2.状态转移:dp[i][j][x] = gs(i,j,i+k-1,j+k-1)-dp[i+1][j+1][x^1]-mat[i+1][j] (k = 3;k <= min(n,m);k+=2)
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510;
int sum[N][N],n,m,mat[N][N],dp[N][N][2];
int gs(int x,int y,int i,int j){
return sum[i][j]-sum[x-1][j]-sum[i][y-1]+sum[x-1][y-1];
}
int main(){
scanf("%d%d",&n,&m);
int x = 1;
for(int i = 1;i <= n;++i){
for(int j = 1;j <= m;++j){
scanf("%d",&mat[i][j]);
dp[i][j][x] = mat[i][j];
sum[i][j] = sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+mat[i][j];
}
}
int ans = -1e9;
for(int k = 3;k <= min(m,n);k+=2){
x ^= 1;
for(int i = 1;i+k-1 <= n;++i){
for(int j = 1;j+k-1 <= m;++j){
dp[i][j][x] = gs(i,j,i+k-1,j+k-1)-dp[i+1][j+1][x^1]-mat[i+1][j];
ans = max(ans,dp[i][j][x]);
}
}
}
cout<<ans<<endl;
return 0;
}// - xgtao -
N - 柱爷与子序列
题意:
给出一个长度为n(<=100000)的序列A,Ai<=10^9,求出一共有多少组完美子序列(长度>=2,下标呈升序,相邻两数差的绝对值小于一个数k),对1000000009取模。
题解:
0.数据太大,肯定需要离散。
1.状态定义:dp[i]表示以A[i]结尾的完美子序列的个数。
2.状态转移:dp[i] += dp[j](abs(A[j]-A[i]) <= k && 1 <= j < i),复杂度O(n^2)所以要优化。
3.用树状数组离散后的序列,以值为下标,那么能够与A[i]对接的就是A[i]-k ~ A[i]+k 这段区间,树状数组维护的是一个以1~某个值结尾前缀和。
4.状态转移:dp[x] += query(x+k)-query(x-k-1), update(x,dp[x]+1);
5.最后把dp[]求和。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
const int mod = 1000000009;
#define lowbit(i) i&-i
#define ll long long
ll c[N],ret;
int a[N],cnt,hase[N],n,k;
ll query(int x){
ll ret = 0;
for(int i = x;i >= 1;i -= lowbit(i)){
ret = (ret+c[i])%mod;
ret = (ret+mod)%mod;
}
return ret;
}
void update(int x,ll p){
for(int i = x;i <= N;i += lowbit(i)){
c[i] = (c[i]+p)%mod;
c[i] = (c[i]+mod)%mod;
}
}
int main(){
scanf("%d%d",&n,&k);
for(int i = 1;i <= n;++i){
cin>>a[i];
hase[++cnt] = a[i];
}
sort(hase+1,hase+cnt+1);
cnt = unique(hase+1,hase+cnt+1)-hase-1;
for(int i = 1;i <= n;++i){
int x = lower_bound(hase+1,hase+1+cnt,a[i])-hase;
int l = lower_bound(hase+1,hase+1+cnt,a[i]-k)-hase;
int r = upper_bound(hase+1,hase+1+cnt,a[i]+k)-hase;--r;
ll c = query(r)-query(l-1);//c可能为负数因为query(r)被%,query(l-1)还没有被%
ret = (ret+c)%mod;
ret = (ret+mod)%mod;
update(x,c+1);
}
cout<<ret<<endl;
return 0;
}// - xgtao -
O - 柱爷很忙
题意:
给出n(n<=1000)件事情的类型ai和重要性bi(<=7),事情可以推后,但是一件事情为K,并且K>i+b[i]则k就不能放在i之前做完,如果要做事情类型为i,前一件做的事情为j,那么要花费的时间为(a[i]|a[j])-(a[i]&a[j]),求做完事情的最短时间。
题解:
0.b的数据范围才7,考虑状态压缩。
1.定义状态:dp[i][S][d]第一维表示以第i件事情结尾,第二维表示以i结尾的最后8件事情的完成情况为S,第三维d表示上一件做的事情离i的距离,dp表示最短的时间
2.状态转移:有两种决策:
①对于以i结尾的8件事情先不慌,往后推,dp[i+1][S^(1<<7)<<1][min(d+1,16)] = min{dp[i][S][d]};(条件是以i结尾的8件事情中最早的那一件事情先做完)
②对于以i结尾的8件事情要做,dp[i][S|(1<<k)][k] = min{dp[i][S][l]+((d==16)?a[i-k]:(a[i-k]|a[i-d])-(a[i-k]&a[i-d])};(条件是离i的距离为k的那件事情没有做过,并且要把不能再拖的事情做了)
(注意d最多为15,d == 16是因为特判第一件事情)。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int all = 1<<8;
const int inf = 0x7f7f7f7f;
const int N = 1616;
int n,a[N],b[N],dp[N][all][21];
int main(){
scanf("%d",&n);
memset(dp,0x7f,sizeof(dp));
for(int i = 1;i <= n;++i)cin>>a[i]>>b[i];
dp[0][all-1][16] = 0;
for(int i = 0;i <= n;++i)
for(int S = 0;S < all;++S)
for(int d = 0;d <= 16;++d)
if(dp[i][S][d] != inf){
int p = min(i,8),putoff = inf;
for(int k = p-1;k >= 0;--k){
if( ((S>>k)&1) == 0 )putoff = min(putoff,i-k+b[i-k]);
}
if( ((S>>7)&1) == 1){
dp[i+1][(S^(1<<7))<<1][min(d+1,16)] = min(dp[i+1][(S^(1<<7))<<1][min(d+1,16)],dp[i][S][d]);
}
for(int k = p-1;k >= 0 && i<=putoff+k;--k){
if(((S>>k)&1) == 0){
dp[i][S|(1<<k)][k] = min(dp[i][S|(1<<k)][k],dp[i][S][d]+((d==16)?a[i-k]:(a[i-k]|a[i-d])-(a[i-k]&a[i-d])));
}
}
}
int ret = inf;
for(int i = 0;i <= 16;++i)ret = min(ret,dp[n][all-1][i]);
cout<<ret<<endl;
return 0;
}// - xgtao -
P - 柱爷的矩阵
题意:
给出矩阵的行数n(<=1000),给出每一行第一个数a[i],再给出每一行的b[i],a[i][j+1] = a[i][j]-b[i],在每一行每一列取至多一个数,求取出的数和最大。
题解:
0.对于要去第i行,第j行的数字并且b[i]>b[j]要先考虑第i行的那个数字,所以要按照b从大到小排序。
1.状态定义:dp[i][j][0/1],dp[i][j][0]表示不一定选了(i,j)这个位置的数字,也就是说所选的数字在k1(k1<=i)行的k2列(k2<=j)处,dp[i][j][1]必定选(i,j)这个位置的数字时的最大值。
2.状态转移:dp[i][j][1] = dp[i-1][j-1][0]+max(0,a[i]-(j-1)*b[i])
dp[i][j][0] = max{dp[i-1][j][0],dp[i][j-1],dp[i][j][1]}
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int dp[N][N][2],n,m;
struct node{
int a,b;
bool operator < (const node &rhs)const{
return b > rhs.b;
}
}x[N];
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;++i)scanf("%d",&x[i].a);
for(int i = 1;i <= n;++i)scanf("%d",&x[i].b);
sort(x+1,x+n+1);
for(int j = 1;j <= m;++j){
for(int i = 1;i <= n;++i){
dp[i][j][1] = dp[i-1][j-1][0]+max(0,x[i].a-(j-1)*x[i].b);
dp[i][j][0] = max(dp[i-1][j][0],dp[i][j-1][0]);
dp[i][j][0] = max(dp[i][j][0],dp[i][j][1]);
}
}
cout<<dp[n][m][0]<<endl;
return 0;
}// - xgtao -
Q - 柱爷的宝藏
题意:
给出一个长度为n(<=500000)的序列,把这个序列分成任意多个区间,如果某个区间有{a[x],a[x+1],...a[y]},那么这个区间的权值为(a[x]+a[x+1]+...a[y])^2+m,怎么划分使得整个序列的区间权值和最小,求最小权值。
题解:
0.定义状态:dp[i]表示前i个数的组成序列的所有区间的权值和。
1.状态转移:dp[i] = min{dp[j]+(sum[i]-sum[j])^2+m}(1<=j<i)复杂度为O(n^2)超时!
2.斜率优化!!
①假设k<j<i如果j比k优,那么dp[j]+(sum[i]-sum[j])^2+m<dp[k]+(sum[i]-sum[k])^2+m化简后得到式子[(dp[j]+sum[j]^2)-(dp[k]+sum[k]^2)]/(2*sum[j]-2*sum[k])<sum[i],令dp[]+sum[]^2为y[],令2*sum[x]为x[],那么将x[],y[]带入原式,则g[j,k] = (y[j]-y[k])/(x[j]-x[k])<sum[i]。
②那么反过来如果g[j,k]<sum[i],就可以得到j一定比k优,再反过来g[j,k]>sum[i]那么k一定比j优,如果g[j,k] = sum[i]那么k一定不会比j差
③从左到右a<b<c<i,如果g[b,a]>=g[c,b],那么b永远不可能为最优解,证明需要分类讨论
假设sum[i]>=g[b,a]>=g[c,b]这种情况下c优于b优于a或者b一起优,但是我们也可以不选b啊
假设g[b,a]>=sum[i]>=g[b,c]这种情况是a优于b优于c或者b一起优,但是我们也可以不选b啊
假设g[b,a]>=g[c,b]>=sum[i]这种情况是a优于b优于c或者b一起优,但是我们也可以不选b啊
总而言之每一种情况下b都不可能成为最优的。
④排除了g[b,a]>g[c,b]这种情况之后,就只剩下g[b,a]<=g[c,b]的情况,那么斜率整个就成一个单调递增的了。
⑤用一个单调队列来维护斜率。
⑥入队的时候如果队列中已经有a,b,c,d,e五个元素了,现在f需要进队但是如果g[e,d]>=g[f,e]那么e就出队,直到满足单调递增为止
⑦求解的时候如果队列中已经有a,b,c,d,e五个元素了,如果g[b,a]<=sum[i]a就排除,a就出队,直到找到一个g[head+1,head]>=sum[i]为止
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
#define square(i) ((i)*(i))
const int N = 500010;
ll dp[N],sum[N],n,m,x,sq[N];
ll deltay(ll i,ll j){
return dp[i]-dp[j]+square(sum[i])-square(sum[j]);
}
ll deltax(ll i,ll j){
return sum[i]-sum[j] <<1;
}
int main(){
cin>>n>>m;
for(int i = 1;i <= n;++i){
cin>>x;
sum[i] = sum[i-1]+x;
}
int head = 0,tail = -1;
for(int i = 1;i <= n;++i){
while(tail-head+1>0 && deltay(sq[head+1],sq[head])<=deltax(sq[head+1],sq[head])*sum[i])++head;
dp[i] = dp[sq[head]]+square(sum[i]-sum[sq[head]])+m;
while(tail-head+1>0 &&
deltay(i,sq[tail])*deltax(sq[tail],sq[tail-1])<=deltay(sq[tail],sq[tail-1])*deltax(i,sq[tail])
)--tail;
sq[++tail] = i;
}
cout<<dp[n]<<endl;
return 0;
}// - xgtao -
R - 柱爷把妹(吃惊高清重制版)
题意:
柱爷通过PY交易,知道了接下来n天的股市行情,第i天买入卖出的的价格为Pi,每天柱爷可以买卖或者不动,但是买卖一次会交F的手续费,买之前扣,卖之后扣,柱爷也有一定的本金m,求最大收益。
题解:
0.定义状态:dp[i][0/1],dp[i][0]表示第i天不做任何操作拥有的钱,dp[i][1]表示第i天操作后有的钱
1.
①假设p[a]<p[b]<p[c]。
②分析a->b(a天买b天卖)moneya-b = (m-F)/p[a]*p[b]-F+(m-F)%p[a]
③分析a->c(a天买c天卖)moneya-c = (m-F)/p[a]*p[c]-F+(m-F)%p[a]
④分析a->b->c moneya-b-c = (moneya-b+F)/p[b]*p[c]-F+(moneya-b+F)%p[b] = {(m-F)/p[a]*p[b]+(m-F)%p[a]}/p[b]*p[c]-F+{(m-F)/p[a]*p[b]+(m-F)%p[a]}%p[b]
因为p[a]<p[b]所以(m-F)%p[a]/p[b]为0,又因为(m-F)/p[a]*p[b]%p[b]等于0,所以moneya-c = (m-F)/p[a]*p[c]-F+(m-F)%p[a];
⑤所以得到结论moneya-b-c = moneya-c;
2.状态转移:
①dp[nxt[i]][1] = max{(dp[i][0]-F)/p[i]*p[nxt[i]]-F+(dp[[i][0]-F)%p[i],(dp[i][1]+F)/p[i]*p[nxt[i]]-F+(dp[i][1]-F)%p[i]}(p[nxt[i]]>p[i])
②dp[i+1][0] = max{dp[i][0],dp[i][1]}
③dp[nxt[i]][0] = max{dp[i][0],dp[i][1]}
3.预处理出nxt[i]就解决问题了。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
const int N = 100010;
ll dp[N][2];
int Stack[N],p[N],nxt[N],n,m,f;
int main(){
cin>>n>>m>>f;
for(int i = 1;i <= n;++i)cin>>p[i];
int top = 0;Stack[top] = n;
for (int i = n; i; --i){
while (top && p[i] >= p[Stack[top]])--top;
nxt[i] = top?Stack[top]:i;
Stack[++top] = i;
}
memset(dp,128,sizeof(dp));
dp[1][0] = m;ll tmp,ans = 0;
for(int i = 1;i <= n;++i){
ans = max(ans,max(dp[i][0],dp[i][1]));
dp[i+1][0] = max(dp[i+1][0],max(dp[i][0],dp[i][1]));
dp[nxt[i]][0] = max(dp[nxt[i]][0],max(dp[i][0],dp[i][1]));
if(nxt[i] <= i)continue;
tmp = dp[i][0]-f;
if(tmp > 0)dp[nxt[i]][1] = max(dp[nxt[i]][1],tmp/p[i]*p[nxt[i]]+tmp%p[i]-f);
tmp = dp[i][1]+f;
if(tmp > 0)dp[nxt[i]][1] = max(dp[nxt[i]][1],tmp/p[i]*p[nxt[i]]+tmp%p[i]-f);
}
cout<<ans<<endl;
return 0;
}// - xgtao -