Atcoder Educational DP Contest 题解 + 总结

比赛页面

非常感谢Atcoder提供dp专项比赛,题目都不错(虽然都是一些很有名的题目

迟到1小时的本人打了2小时才过了13题,。。。(打完O就睡觉去了

后来发现是场Unrated比赛? 666

dp还真的挺有趣的

A Frog1

大水题

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[i1]+a[i]a[i1],dp[i2]+a[i]a[i2])

时间复杂度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 ;
}

B - Frog 2

也还是一样的

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(i1,k)dp[ij]+abs(a[i]a[ij])

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

C - Vacation

依然是入门难度的

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

D - Knapsack 1

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

E - Knapsack 2

物品体积很大,不能按照原先的 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[jv[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 ;
}

F - LCS

啊又是一个模板题,不就是求最长公共子序列么

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[i1]=s2[j1] d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] dp[i][j]=dp[i-1][j-1] dp[i][j]=dp[i1][j1]

否则 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[i1][j],dp[i][j1])

然后再用个 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 ;
}

G - Longest Path

不看题目的恶果啊。。写了个最短路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 ;
}

H - Grid 1

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

N - Slimes

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

I - Coins

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[x1][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][y1](1a[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 ;
}

K - Stones

蛤?没过大样例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[ka[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") ;
}

P - Independent Set

没有上司的舞会? 差不多

又是个树形 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) ;
}

O - Matching

一看数据范围就是状压 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 i1这个人选的是 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[i1][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(2nn)

蛮经典的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]) ;
}

之后的题目都是订正的

L - Deque

感觉又是博弈论? 实质就是个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+j1]=max(a[j]dp[j+1][i+j1],dp[j][i+j2]+a[i+j1])

为什么会是负的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]) ;
}

M - Candies

这个题目也比较简单,考试的时候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[i1][jk]

发现这样的时间复杂度是 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[i1]的东西是不会变的,而且对于每个 d p [ i ] [ j ] dp[i][j] dp[i][j]有可能要计算很多遍 d p [ i − 1 ] [ j − k ] dp[i-1][j-k] dp[i1][jk]

于是我们想出来通过前缀和去优化

s [ j ] s[j] s[j]表示 d p [ i − 1 ] [ 0 ] dp[i-1][0] dp[i1][0] d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1]的和

然后查找时就是 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 ;
}

J - Sushi

明显的期望 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[a1][b][c]a/d+dp[a+1][b1][c]b/d+dp[a][b+1][c1]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 ;
}

S - Digit Sum

数位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 ;
}

Q - Flowers

很容易想出一种类似最长上升子序列的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 ;
}

你可能感兴趣的:(———DP———,背包类型DP,前缀和优化DP,状压DP,数位DP,期望DP,数据结构优化DP,树形DP,线性DP)