比赛页面
非常感谢Atcoder提供dp专项比赛,题目都不错(虽然都是一些很有名的题目
迟到1小时的本人打了2小时才过了13题,。。。(打完O就睡觉去了
后来发现是场Unrated比赛? 666
dp还真的挺有趣的
大水题
d p [ i ] dp[i] dp[i]表示到达i的最小花费
d p [ i ] = m i n ( d p [ i − 1 ] + ∣ a [ i ] − a [ i − 1 ] ∣ , d p [ i − 2 ] + ∣ a [ i ] − a [ i − 2 ] ) dp[i]=min(dp[i-1]+|a[i]-a[i-1]|,dp[i-2]+|a[i]-a[i-2]) dp[i]=min(dp[i−1]+∣a[i]−a[i−1]∣,dp[i−2]+∣a[i]−a[i−2])
时间复杂度O(n)
啊比赛开场不在状态,忘记 d p [ 0 ] = i n f dp[0]=inf dp[0]=inf导致 w a wa wa了2发罚了 10 m i n 10min 10min
哭啊
int n ;
ll a[N], dp[N] ;
signed main(){
scanf("%d", &n) ;
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]) ;
for (int i = 0; i <= n; i++) dp[i] = linf ;
dp[1] = 0ll ;
for (int i = 2; i <= n; i++)
dp[i] = min(dp[i], min(dp[i - 1] + abs(a[i] - a[i - 1]), dp[i - 2] + abs(a[i] - a[i - 2]))) ;
printf("%lld\n", dp[n]) ;
return 0 ;
}
也还是一样的
d p [ i ] = min j = 1 m i n ( i − 1 , k ) d p [ i − j ] + a b s ( a [ i ] − a [ i − j ] ) dp[i]=\min_{j=1}^{min(i-1,k)}dp[i-j]+abs(a[i]-a[i-j]) dp[i]=minj=1min(i−1,k)dp[i−j]+abs(a[i]−a[i−j])
O(nk)
int n, k ;
ll a[N], dp[N] ;
signed main(){
scanf("%d%d", &n, &k) ;
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]) ;
for (int i = 0; i <= n; i++) dp[i] = linf ;
dp[1] = 0ll ;
for (int i = 2; i <= n; i++)
for (int j = 1; j <= min(i - 1, k); j++)
dp[i] = min(dp[i], dp[i - j] + abs(a[i] - a[i - j])) ;
printf("%lld\n", dp[n]) ;
return 0 ;
}
依然是入门难度的
d p [ i ] [ j ] dp[i][j] dp[i][j]表示玩到第 i i i天,当天玩的是 j j j项目的最大值
d p [ i + 1 ] [ k ] = m i n ( d p [ i + 1 ] [ k ] , d p [ i ] [ j ] + a [ i + 1 ] [ k ] ) dp[i+1][k]=min(dp[i+1][k],dp[i][j]+a[i+1][k]) dp[i+1][k]=min(dp[i+1][k],dp[i][j]+a[i+1][k])
a n s = min i = 1 3 d p [ n ] [ i ] ans=\min_{i=1}^3dp[n][i] ans=mini=13dp[n][i]
时间复杂度O(n)
int n, ans ;
int a[N][4], dp[N][4] ; // dp[i][j]表示玩到第i天,当天玩的是j项目的最大值
signed main(){
scanf("%d", &n) ;
for (int i = 1; i <= n; i++) scanf("%d%d%d", &a[i][1], &a[i][2], &a[i][3]) ;
for (int i = 1; i <= n; i++) dp[i][1] = dp[i][2] = dp[i][3] = -iinf ;
for (int i = 1; i <= 3; i++) dp[1][i] = a[1][i] ;
for (int i = 1; i < n; i++)
for (int j = 1; j <= 3; j++)
for (int k = 1; k <= 3; k++)
if (j != k)
dp[i + 1][k] = max(dp[i + 1][k], dp[i][j] + a[i + 1][k]) ;
for (int i = 1; i <= 3; i++) ans = max(ans, dp[n][i]) ;
printf("%d\n", ans) ;
return 0 ;
}
01背包纯模板题
直接打就是了
int n, m ;
ll f[N], w[N], v[N] ;
signed main(){
scanf("%d%d", &n, &m) ;
for (int i = 1; i <= n; i++) scanf("%lld%lld", &w[i], &v[i]) ; // height and value
memset(f, 0xcf, sizeof(f)) ;
f[0] = 0 ;
for (int i = 1; i <= n; i++)
for (int j = m; j >= w[i]; j--)
f[j] = max(f[j], f[j - w[i]] + v[i]) ;
ll ans = 0 ;
for (int j = 0; j <= m; j++) ans = max(ans, f[j]) ;
printf("%lld\n", ans) ;
return 0 ;
}
物品体积很大,不能按照原先的 d p dp dp方程去 d p dp dp了
但是物品的价值最大才 1 0 5 10^5 105
于是我们能够轻松地想出 d p dp dp方程:
d p [ i ] dp[i] dp[i]表示达到 i i i的价值最少需要多大的体积
d p [ j ] = m i n ( d p [ j − v [ i ] ] + w [ i ] ) dp[j]=min(dp[j-v[i]]+w[i]) dp[j]=min(dp[j−v[i]]+w[i]) 其中 v v v是价值, w w w是体积
然后 i i i从大往小枚举,如果 d p [ i ] < = W dp[i]<=W dp[i]<=W则答案就是 i i i
int n ;
ll W, c ;
ll w[N], v[N], dp[N] ;
signed main(){
scanf("%d%lld", &n, &W) ;
for (int i = 1; i <= n; i++) scanf("%lld%lld", &w[i], &v[i]) ; // weight ans value
for (int i = 1; i <= n; i++) c += v[i] ;
for (int i = 0; i <= c; i++) dp[i] = linf ;
dp[0] = 0 ;
for (int i = 1; i <= n; i++)
for (int j = c; j >= v[i]; j--)
dp[j] = min(dp[j], dp[j - v[i]] + w[i]) ;
for (ll i = c; i >= 0; i--)
if (dp[i] <= W) {
printf("%lld\n", i) ;
break ;
}
return 0 ;
}
啊又是一个模板题,不就是求最长公共子序列么
dp[i][j]表示 s 1 s1 s1枚举到 i i i, s 2 s2 s2枚举到 j j j的最长公共子序列长度
如果 s 1 [ i − 1 ] = s 2 [ j − 1 ] s1[i-1]=s2[j-1] s1[i−1]=s2[j−1] d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] dp[i][j]=dp[i-1][j-1] dp[i][j]=dp[i−1][j−1]
否则 d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) dp[i][j]=max(dp[i-1][j],dp[i][j-1]) dp[i][j]=max(dp[i−1][j],dp[i][j−1])
然后再用个 f l a g flag flag数组记录一下从哪里转移来就行了
int flag[N][N], dp[N][N] ;
char s1[N], s2[N] ;
void LCS() {
clr(dp) ; clr(flag) ;
int n = strlen(s1), m = strlen(s2) ;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
if (s1[i - 1] == s2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1, flag[i][j] = 0 ;
else if (dp[i - 1][j] >= dp[i][j - 1]) dp[i][j] = dp[i - 1][j], flag[i][j] = 1 ;
else dp[i][j] = dp[i][j - 1], flag[i][j] = -1 ;
}
}
void PrintLCS(int i, int j) {
if (i == 0 || j == 0) return ;
if (flag[i][j] == 0) {
PrintLCS(i - 1,j - 1) ;
cout << s1[i - 1] ;
}
else if (flag[i][j] == 1) PrintLCS(i - 1, j) ;
else PrintLCS(i, j - 1) ;
}
int main(){
cin >> s1 >> s2 ;
LCS() ;
PrintLCS(strlen(s1), strlen(s2)) ;
return 0 ;
}
不看题目的恶果啊。。写了个最短路TLE了
然后仔细一看tmd就是个 D A G DAG DAG
直接拓扑排序 d p dp dp去了
预告一下后面的题目都是1A的
queue <int> q ;
vector <int> e[N] ;
int n, m, ans ;
int dp[N], in[N] ;
signed main(){
scanf("%d%d", &n, &m) ;
for (int i = 1, a, b; i <= m; i++) {
scanf("%d%d", &a, &b) ;
e[a].pb(b) ; // directed
in[b]++ ;
}
for (int i = 1; i <= n; i++) if (!in[i]) q.push(i) ;
while (!q.empty()) {
int now = q.front() ; q.pop() ;
for (int i = 0; i < siz(e[now]); i++) {
int to = e[now][i] ;
in[to]-- ;
if (!in[to]) {
dp[to] = dp[now] + 1 ;
q.push(to) ;
}
}
}
for (int i = 1; i <= n; i++) ans = max(ans, dp[i]) ;
printf("%d\n", ans) ;
return 0 ;
}
dp?我就得更像bfs
直接bfs,然后 d p [ x ] [ y ] dp[x][y] dp[x][y]表示到大 a [ x ] [ y ] a[x][y] a[x][y]的方案数记录一下就行了
别忘了打标记
const int dx[] = {1, 0} ;
const int dy[] = {0, 1} ;
queue <pii> q ;
char s[N][N] ;
ll dp[N][N] ;
bool vis[N][N] ;
int n, m ;
signed main(){
scanf("%d%d", &n, &m) ;
for (int i = 1; i <= n; i++) scanf("%s", s[i] + 1) ;
q.push(mp(1, 1)) ; dp[1][1] = 1 ; vis[1][1] = 1 ;
while (!q.empty()) {
pii now = q.front() ; q.pop() ;
for (int i = 0; i < 2; i++) {
int tx = now.fi + dx[i], ty = now.se + dy[i] ;
if (s[tx][ty] == '.') {
dp[tx][ty] = (dp[tx][ty] + dp[now.fi][now.se]) % MOD ;
if (!vis[tx][ty]) q.push(mp(tx, ty)), vis[tx][ty] = 1 ;
}
}
}
printf("%lld\n", dp[n][m]) ;
return 0 ;
}
NOI1995 石子合并? 原题666
前缀和+区间dp就搞定了
int n ;
ll a[N], s[N], f[N][N] ;
int main(){
scanf("%d", &n) ;
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]) ;
s[i] = s[i - 1] + a[i] ;
}
for (int len = 2; len <= n; len++)
for (int i = 1; i <= n - len + 1; i++){
int j = i + len - 1 ;
f[i][j] = linf ;
for (int k = i; k < j; k++) f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]) ;
}
printf("%lld\n", f[1][n]) ;
}
tmd我花了40分钟做出来的题目别的到老3分钟? 我还是太菜了
dp打的还不是很熟练啊
一眼就是概率dp
d p [ x ] [ y ] dp[x][y] dp[x][y]表示翻出 x x x个正面和 y y y个反面的概率为多少
如果x大于0 d p [ x ] [ y ] + = d p [ x − 1 ] [ y ] ∗ a [ i ] dp[x][y]+=dp[x-1][y]*a[i] dp[x][y]+=dp[x−1][y]∗a[i]
如果y大于0 d p [ x ] [ y ] + = d p [ x ] [ y − 1 ] ∗ ( 1 − a [ i ] ) dp[x][y]+=dp[x][y-1]*(1-a[i]) dp[x][y]+=dp[x][y−1]∗(1−a[i])
初始 d p [ 0 ] [ 0 ] = 1 dp[0][0]=1 dp[0][0]=1
答案就是 x > y x>y x>y的 d p [ x ] [ y ] dp[x][y] dp[x][y]的和
double dp[N][N] ;
double a[N] ;
double ans ;
int n ;
signed main(){
scanf("%d", &n) ;
for (int i = 1; i <= n; i++) scanf("%lf", &a[i]) ;
for (int i = 0; i <= n; i++)
for (int j = 0; i + j <= n; j++)
dp[i][j] = 0.00 ;
dp[0][0] = 1.00 ;
for (int i = 1; i <= n; i++) // play i round
for (int j = 0; j <= i; j++) { // and have j coins head
int x = j, y = i - j ; // in all coins, x is head, y is tail
if (x) dp[x][y] += dp[x - 1][y] * a[i] ;
if (y) dp[x][y] += dp[x][y - 1] * (1 - a[i]) ;
}
int l = n, r = 0 ;
while (l > r) {
ans += dp[l][r] ;
l--, r++ ;
}
printf("%.10lf\n", ans) ;
return 0 ;
}
蛤?没过大样例AC了? Atcoder不测样例? 难怪此题这么多人AC
我觉得此题更像是博弈论
d p [ k ] [ t ] dp[k][t] dp[k][t]表示 t t t先手时石头还剩 k k k个, t t t是否能赢
然后记忆化搜索一下
如果有一个 d p [ k − a [ i ] ] [ ! t ] dp[k-a[i]][!t] dp[k−a[i]][!t]是false则 d p [ k ] [ t ] dp[k][t] dp[k][t]是true
被最后一个毒瘤样例卡了
听说把递归改成递推就能过? 可能是栈空间的原因吧
反正大概思路对了就行,数据太水找出题人去
int n, k ;
int dp[N][2] ;
int a[N] ;
bool dfs(int k, int t) {
if (dp[k][t] >= 0) return dp[k][t] ;
int ans = 0 ;
for (int i = 1; i <= n; i++) if (k >= a[i]) ans |= !dfs(k - a[i], !t) ;
return dp[k][t] = ans ;
}
signed main() {
memset(dp, -1, sizeof(dp)) ;
scanf("%d%d", &n, &k) ;
for (int i = 1; i <= n; i++) scanf("%d", &a[i]) ;
if (dfs(k, 0) == true) puts("First") ;
else puts("Second") ;
}
没有上司的舞会? 差不多
又是个树形 d p dp dp
d p [ i ] [ 0 / 1 ] dp[i][0/1] dp[i][0/1]表示 i i i节点涂白/黑的方案数
d p [ i ] [ 0 ] = ∐ j = s o n [ i ] ( d p [ j ] [ 0 ] + d p [ j ] [ 1 ] ) dp[i][0]=\coprod_{j=son[i]}(dp[j][0]+dp[j][1]) dp[i][0]=∐j=son[i](dp[j][0]+dp[j][1])
d p [ i ] [ 1 ] = ∐ j = s o n [ i ] d p [ j ] [ 0 ] dp[i][1]=\coprod_{j=son[i]}dp[j][0] dp[i][1]=∐j=son[i]dp[j][0]
叶子结点的 d p [ i ] [ 0 ] = d p [ i ] [ 1 ] = 1 dp[i][0]=dp[i][1]=1 dp[i][0]=dp[i][1]=1
答案是 d p [ r o o t ] [ 0 ] + d p [ r o o t ] [ 1 ] dp[root][0]+dp[root][1] dp[root][0]+dp[root][1]
ll dp[N][5] ;
int flag[N] ;
vector <int> e[N] ;
int n ;
ll ans ;
void dfs(int k, int fat) {
if (siz(e[k]) == 1 && e[k][0] == fat) { // leaves
dp[k][0] = dp[k][1] = 1 ;
return ;
}
ll x = 1, y = 1 ;
for (int i = 0; i < siz(e[k]); i++) {
int to = e[k][i] ; if (to == fat) continue ;
dfs(to, k) ;
x = (x * (dp[to][0] + dp[to][1]) % MOD) % MOD ;
y = y * dp[to][0] % MOD;
}
dp[k][0] = x, dp[k][1] = y ;
}
signed main(){
scanf("%d", &n) ;
for (int i = 1, x, y; i < n; i++) {
scanf("%d%d", &x, &y) ;
e[x].pb(y) ; e[y].pb(x) ;
flag[y] = 1 ;
}
int root = 0 ;
for (int i = 1; i <= n; i++) if (!flag[i]) {
root = i ;
break ;
}
dfs(root, -1) ;
ans = (dp[root][0] + dp[root][1]) % MOD ;
printf("%lld\n", ans) ;
}
一看数据范围就是状压 d p dp dp
d p [ i ] [ m a s k ] dp[i][mask] dp[i][mask]表示枚举到第 i i i个男士,女士选择的情况为 j j j的方案数
枚举 i − 1 i-1 i−1这个人选的是 j j j,必须保证 m a s k mask mask包含j且i-1与j有边
d p [ i ] [ m a s k ] + = d p [ i − 1 ] [ m a s k − ( 1 < < j ) ] dp[i][mask]+=dp[i-1][mask-(1<<j)] dp[i][mask]+=dp[i−1][mask−(1<<j)]
然后我们发现不需要枚举 i i i
因为 m a s k mask mask中包含的1的个数就是 i i i的值
于是乎时间复杂度就变成了 O ( 2 n ∗ n ) O(2^n*n) O(2n∗n)
蛮经典的dp问题
int n ;
int a[N][N] ;
int dp[1 << N] ;
signed main() {
scanf("%d", &n) ;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
scanf("%d", &a[i][j]) ;
dp[0] = 1 ;
for (int i = 1; i < (1 << n); i++) {
int now = __builtin_popcount(i) - 1 ;
for (int j = 0; j < n; j++)
if (i & (1 << j) && a[now][j]){
dp[i] = (dp[i] + dp[i ^ (1 << j)]) % MOD ;
}
}
printf("%d\n", dp[(1 << n) - 1]) ;
}
之后的题目都是订正的
感觉又是博弈论? 实质就是个dp
我们感性理解一下X-Y:其实就是一个人拼命地想要拉大差值,而另一个人要组织他的行为,他的目的其实也是拉大差值(因为他的也是X-Y而不是Y-X)
请注意下面所说的差值并非单单指X-Y,而是:对于先手时X-Y,而后手是Y-X,即双方的差距
这一点很重要,想清楚这一点题就做出来了
我们发现从外向内扩展dp比较难,尝试一步步扩展长度(从内向外)
d p [ i ] [ j ] dp[i][j] dp[i][j]表示现在扩展到 i i i到 j j j区间的最大差值
首先 d p [ i ] [ i ] = a [ i ] dp[i][i]=a[i] dp[i][i]=a[i]可以肯定
然后
d p [ j ] [ i + j − 1 ] = m a x ( a [ j ] − d p [ j + 1 ] [ i + j − 1 ] , − d p [ j ] [ i + j − 2 ] + a [ i + j − 1 ] ) dp[j][i+j-1]=max(a[j]-dp[j+1][i+j-1],-dp[j][i+j-2]+a[i+j-1]) dp[j][i+j−1]=max(a[j]−dp[j+1][i+j−1],−dp[j][i+j−2]+a[i+j−1])
为什么会是负的dp?
因为对于每一轮,上一轮一定是不是他取,那么这一轮他取的话他的值肯定要剪掉这么多
他一定是想要拉大他与对方的差值,与是要去max
看着难理解其实还不错
ll dp[N][N] ;
ll a[N] ;
int n ;
signed main() {
scanf("%d", &n) ;
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]) ;
for (int i = 1; i <= n; i++) dp[i][i] = a[i] ;
for (int i = 2; i <= n; i++)
for (int j = 1; i + j - 1 <= n; j++)
dp[j][i + j - 1] = max(a[j] - dp[j + 1][i + j - 1], -dp[j][i + j - 2] + a[i + j - 1]) ;
printf("%lld\n", dp[1][n]) ;
}
这个题目也比较简单,考试的时候AC的人不多就没看这题
首先能够想到dp
d p [ i ] [ j ] dp[i][j] dp[i][j]表示枚举到第 i i i个人,当前用掉了 j j j颗糖果的方案数
d p [ i ] [ j ] = ∑ k = 1 a i d p [ i − 1 ] [ j − k ] dp[i][j]=\sum_{k=1}^{a_i}dp[i-1][j-k] dp[i][j]=∑k=1aidp[i−1][j−k]
发现这样的时间复杂度是 O ( n k 2 ) O(nk^2) O(nk2),会TLE
怎么优化?
我们发现在做 d p [ i ] dp[i] dp[i]这个维度的时候, d p [ i − 1 ] dp[i-1] dp[i−1]的东西是不会变的,而且对于每个 d p [ i ] [ j ] dp[i][j] dp[i][j]有可能要计算很多遍 d p [ i − 1 ] [ j − k ] dp[i-1][j-k] dp[i−1][j−k]
于是我们想出来通过前缀和去优化
s [ j ] s[j] s[j]表示 d p [ i − 1 ] [ 0 ] dp[i-1][0] dp[i−1][0]到 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i−1][j−1]的和
然后查找时就是 O ( 1 ) O(1) O(1)的了
时间复杂度 O ( n k ) O(nk) O(nk)
当然 d p [ i ] [ j ] dp[i][j] dp[i][j]的 i i i这一维可以滚动掉,由于空间够就没写
ll dp[N][K] ;
ll a[N], s[K] ;
int n, k ;
signed main(){
scanf("%d%d", &n, &k) ;
for (int i = 0; i < n; i++) scanf("%lld", &a[i]) ;
for (int j = 0; j <= k; j++) dp[0][j] = 0 ;
for (int j = 0; j <= n; j++) dp[j][0] = 1 ;
for (int i = 1; i <= n; i++) {
s[0] = 0 ;
for (int j = 1; j <= k + 1; j++) s[j] = s[j - 1] + dp[i - 1][j - 1] ;
for (int j = 1; j <= k; j++) {
dp[i][j] = s[j + 1] - s[max(0ll, j - a[i - 1])] ;
dp[i][j] %= MOD ;
}
}
printf("%lld\n", dp[n][k]) ;
return 0 ;
}
明显的期望 d p dp dp
发现寿司最多只有3个,那么就能够想出 d p dp dp状态
d p [ a ] [ b ] [ c ] dp[a][b][c] dp[a][b][c]表示现在还有1个寿司的盘子有 a a a个,2个的 b b b个,3个的 c c c个
d p [ 0 ] [ 0 ] [ 0 ] = 0 dp[0][0][0]=0 dp[0][0][0]=0
设 d = a + b + c d=a+b+c d=a+b+c
d p [ a ] [ b ] [ c ] = n / d + d p [ a − 1 ] [ b ] [ c ] ∗ a / d + d p [ a + 1 ] [ b − 1 ] [ c ] ∗ b / d + d p [ a ] [ b + 1 ] [ c − 1 ] ∗ c / d dp[a][b][c]=n/d+dp[a-1][b][c]*a/d+dp[a+1][b-1][c]*b/d+dp[a][b+1][c-1]*c/d dp[a][b][c]=n/d+dp[a−1][b][c]∗a/d+dp[a+1][b−1][c]∗b/d+dp[a][b+1][c−1]∗c/d
double dp[N][N][N] ;
int a[N] ;
int n ;
double dfs(int a, int b, int c) {
if (a == 0 && b == 0 && c == 0) return 0 ; // 边界
if (dp[a][b][c] >= 0) return dp[a][b][c] ;
int sum = a + b + c ;
double ans = 1.0 * n / sum ;
if (a) ans += 1.0 * dfs(a - 1, b, c) * a / sum ;
if (b) ans += 1.0 * dfs(a + 1, b - 1, c) * b / sum ;
if (c) ans += 1.0 * dfs(a, b + 1, c - 1) * c / sum ;
return dp[a][b][c] = ans ;
}
signed main(){
ass(dp, -1) ;
scanf("%d", &n) ;
for (int i = 1, t; i <= n; i++) {
scanf("%d", &t) ;
a[t]++ ;
}
printf("%.10lf\n", dfs(a[1], a[2], a[3])) ;
return 0 ;
}
数位dp的模板题
d p [ k ] [ s u m ] [ 0 / 1 ] dp[k][sum][0/1] dp[k][sum][0/1]表示枚举到第i位,当前数字之和 % d \%d %d为 s u m sum sum,是否达到上限的方案数
设 u p up up为当前枚举的数的上限
如果当前数位上限有要求则为 a [ k ] a[k] a[k],否则为9
d p [ k ] [ s u m ] [ l i m ] = ∑ i = 0 u p d p [ k + 1 ] [ ( s u m + i ) % d ] [ l i m & & ( i = u p ) ] dp[k][sum][lim]=\sum_{i=0}^{up}dp[k+1][(sum+i)\%d][lim\&\&(i=up)] dp[k][sum][lim]=∑i=0updp[k+1][(sum+i)%d][lim&&(i=up)]
然后发现都比答案多1
为什么? 原因是我们都多算了0。哈哈哈
int dp[N][K][2] ;
char s[N] ;
int a[N] ;
int n, m ;
int dfs(int k, int now, int lim) {
if (dp[k][now][lim] >= 0) return dp[k][now][lim] ;
if (k == m) return dp[k][now][lim] = (now == 0) ;
int sum = 0, up = lim ? a[k] : 9 ;
for (int i = 0; i <= up; i++) {
sum += dfs(k + 1, (now + i) % n, lim && i == up) ;
if (sum >= MOD) sum -= MOD ;
}
return dp[k][now][lim] = sum ;
}
signed main(){
scanf("%s%d", s, &n) ;
m = strlen(s) ;
for (int i = 0; i < m; i++) a[i] = s[i] - '0' ;
ass(dp, -1) ;
printf("%d\n", (dfs(0, 0, 1) + MOD - 1) % MOD) ; // 0 is incorrect
return 0 ;
}
很容易想出一种类似最长上升子序列的dp方程
dp[i]表示选第i朵花的最大价值
d p [ i ] = m a x ( d p [ j ] ) + a [ i ] dp[i]=max(dp[j])+a[i] dp[i]=max(dp[j])+a[i] 必须满足 h [ j ] < h [ i ] h[j]<h[i] h[j]<h[i]
时间复杂度 O ( n 2 ) O(n^2) O(n2),TLE
然后发现要查找的 d p dp dp值都是 h h h值比 i i i要小的,可以使用树状数组优化到 O ( n l o g n ) O(nlogn) O(nlogn)
ll a[N], h[N], dp[N], bit[N] ;
int n ;
void add(int x, ll y) {
for (; x <= n; x += lowbit(x)) bit[x] = max(bit[x], y) ;
}
ll query(int x) {
ll res = 0 ;
for (; x; x -= lowbit(x)) res = max(res, bit[x]) ;
return res ;
}
signed main(){
scanf("%d", &n) ;
for (int i = 1; i <= n; i++) scanf("%lld", &h[i]) ;
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]) ;
for (int i = 1; i <= n; i++) {
dp[i] = query(h[i]) + a[i] ;
add(h[i], dp[i]) ;
}
printf("%lld\n", query(n)) ; // all h[i] <= n
return 0 ;
}