2016 UESTC Training for Dynamic Programming

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]}

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

#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]}

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

#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)查询。

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

#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;

}

  


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

#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。

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

#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]。

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

#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]}。

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

#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的位数)。

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

#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),就是在一个序列中不断替换第一个大于等于当前的数,因为这样序列就更有延展性,答案保证不会差,但是并不一定是这一个序列,但是长度一定相同的。

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

#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.如果为空集的话就特判一下。

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

#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

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

#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。

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

#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])

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

#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)

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

#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[]求和。

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

#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是因为特判第一件事情)。

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

#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]}

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

#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]为止

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

#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]就解决问题了。

代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

#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 -

  

时间: 2024-10-25 16:58:46

2016 UESTC Training for Dynamic Programming的相关文章

2017 UESTC Training for Dynamic Programming

2017 UESTC Training for Dynamic Programming A    思维, 或 dp, 很有意思 方法1: 构造法:蛇形安排赛程表算法复杂度:O(N^2)将1-N排成两竖列,每一轮同一行的为对手保持1的位置不变,其他位置按顺(逆)时方向依次旋转1    6          1    2          1    3          1    4          1    5      2    5          3    6          4   

UESTC_邱老师玩游戏 2015 UESTC Training for Dynamic Programming&lt;Problem G&gt;

G - 邱老师玩游戏 Time Limit: 3000/1000MS (Java/Others)     Memory Limit: 65535/65535KB (Java/Others) Submit Status 邱老师最近在玩一种战略游戏,在一个地图上,有N座城堡,每座城堡都有一定的宝物,在每次游戏中邱老师允许攻克M个城堡并获得里面的宝物. 但由于地理位置原因,有些城堡不能直接攻克,要攻克这些城堡必须先攻克其他某一个特定的城堡.你能帮邱老师算出要获得尽量多的宝物应该攻克哪M个城堡吗? In

UESTC_男神的约会 2015 UESTC Training for Dynamic Programming&lt;Problem J&gt;

J - 男神的约会 Time Limit: 3000/1000MS (Java/Others)     Memory Limit: 65535/65535KB (Java/Others) Submit Status 有一天男神约了学姐姐去看电影,电影院有一个活动,给你一个10*10的矩阵,每一个格子上都有一个0-9的整数,表示一共十种优惠券中的一种. 观众从左上角的格子开始走,走到右下角.每走到一个有着a号优惠券的格子,都必须要玩一个a分钟的游戏来领取这张优惠券. 每次只能向右或向下走.当走到右

UESTC_菲波拉契数制 2015 UESTC Training for Dynamic Programming&lt;Problem E&gt;

E - 菲波拉契数制 Time Limit: 3000/1000MS (Java/Others)     Memory Limit: 65535/65535KB (Java/Others) Submit Status 我们定义如下数列为菲波拉契数列: F(1)=1 F(2)=2 F(i)=F(i−1)+F(i−2)(i>=3) 给定任意一个数,我们可以把它表示成若干互不相同的菲波拉契数之和.比如13有三种表示法 13=13 13=5+8 13=2+3+8 现在给你一个数n,请输出把它表示成若干互

UESTC_酱神的旅行 2015 UESTC Training for Dynamic Programming&lt;Problem M&gt;

M - 酱神的旅行 Time Limit: 3000/1000MS (Java/Others)     Memory Limit: 65535/65535KB (Java/Others) Submit Status 酱神要去一棵树上旅行. 酱神制定了一个旅行计划,他要按顺序去m个树上的结点,a1,a2,a3,...,am. 酱神有一辆车,树上的每一条边既可以开车通过,也可以走过去,两种方法需要不同的时间.如果选择走路,酱神需要先把车停在结点上,在他下一次要开车的时候,必须先回到停车的结点取车.

UESTC_邱老师选妹子(二) 2015 UESTC Training for Dynamic Programming&lt;Problem I&gt;

I - 邱老师选妹子(二) Time Limit: 3000/1000MS (Java/Others)     Memory Limit: 65535/65535KB (Java/Others) Submit Status 邱老师长得帅这是人尽皆知,于是追他的妹子就会很多.但是你知道,邱老师是一个很专一的人,所以他心里面只能有一个人.于是他决定从追他的众多妹子里挑选一个出来. 在第一轮的选拔中,剩余了一些妹子.酱神又给邱老师出主意了,因为最近酱神刚刚学习了最长上升子序列,所以这次,依然是把妹子们

UESTC_酱神赏花 2015 UESTC Training for Dynamic Programming&lt;Problem C&gt;

C - 酱神赏花 Time Limit: 3000/1000MS (Java/Others)     Memory Limit: 262143/262143KB (Java/Others) Submit Status 酱神去杭州赏花. 花展在一条街道上举行,这条街道上有一共有n个节点,自左而右从1到n编号,1号和n号是左右两个端点,两个相邻端点之间的距离为1.本次花展一共要展出m朵花,在第ti时刻,有一朵颜值为bi的花将在第ai个节点展出,如果酱神在ti时刻处于第x个节点,那么他能获得的开心值为

UESTC_菲波拉契数制升级版 2015 UESTC Training for Dynamic Programming&lt;Problem L&gt;

L - 菲波拉契数制升级版 Time Limit: 3000/1000MS (Java/Others)     Memory Limit: 65535/65535KB (Java/Others) Submit Status 我们定义如下数列为菲波拉契数列: F(1)=1 F(2)=2 F(i)=F(i−1)+F(i−2)(i>=3) 给定任意一个数,我们可以把它表示成若干互不相同的菲波拉契数之和.比如13有三种表示法 13=13 13=5+8 13=2+3+8 现在给你一个数n,请输出把它表示成

UESTC_摩天轮 2015 UESTC Training for Dynamic Programming&lt;Problem K&gt;

K - 摩天轮 Time Limit: 10000/4000MS (Java/Others)     Memory Limit: 262143/262143KB (Java/Others) Submit Status 一天,冬马被春希和雪菜拉着去一起去游乐园玩. 经过了各种过山车的洗礼后,三人决定去坐摩天轮休息下. 这是一个巨大的摩天轮,每一个车厢能坐任意多的人.现在,等着坐摩天轮的有n个人(包含他们3人),摩天轮还有m个车厢可以坐人.每个人都有自己肥胖程度,出于某些原因,胖子和瘦子坐在同一节车