2016 UESTC Training for Dynamic Programming - xgtao -
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 -