2020寒假训练第二周(DP)解题报告

目录

训练链接:https://vjudge.net/contest/350406

A - Boredom (CodeForces 455A

B - Flowers (CodeForces 474D

C - Consecutive Subsequence (CodeForces 977F)

D - Easy Problem (CodeForces 1096D)

E - Flood Fill (CodeForces 1114D)


A - Boredom

题意:给出长度为N的数组,每轮游戏可以进行一次操作,每次操作任选一个数(定义为a_{k}),游戏者获得a_{k}分,操作结束后,删去所有a_{k}和等于a_{k} + 1a_{k} - 1的数,问游戏者最大能够获得多少分。

思路:记录每个数出现的次数(times[ ]),与数组最大值(Max)。

        状态:dp[i][0|1]表示第i个数选或不选。

        状态转移方程:dp[i][1] = max(dp[i - 3][1], dp[i - 1][0]) * times[i] * i

                                 dp[i][0] = dp[i - 1][1]

        选择当前数,则可以由两种状态转移过来:前一个不选(选择前2个)和选择前3个,如选择:第一个和第四个数,或者选择第二个数与第四个数。

        不选择当前数,则状态等于前一个数选。

AC代码:

const int maxn = 2e5 + 5;
typedef long long LL;

LL n, a[maxn];
LL dp[maxn][2];
LL times[maxn];

int main(){
	cin >> n;
	LL Max = 0;
	for(int i = 1; i <= n; ++i){
		cin >> a[i];
		times[a[i]]++;
		Max = max(Max, a[i]);
	}
	LL ans = 0;
	dp[1][1] = 1 * times[1];
	dp[1][0] = 0;
	dp[2][1] = dp[1][0] + 2 * times[2];
	dp[2][0] = dp[1][1];
	for(int i = 3; i <= Max; ++i){
		dp[i][1] = max(dp[i - 3][1], dp[i - 1][0]) + i * times[i];
		dp[i][0] = dp[i - 1][1];
	}
	ans = max(dp[Max][1], dp[Max][0]);
	cout << ans << endl;
	return 0;
}

B - Flowers

题意:有红色和白色两种颜色的花,Marmot的晚餐是白花,给出T组测试数据与一个数K,代表着Marmot的T顿晚餐,每顿晚餐可以让餐桌上摆上a_{i}b_{i}朵花,但是Marmot只吃长度为K的连续的白花(意味着如果K == 2,则若是白花单独放置,Marmot不吃),所以若是将白花摆上餐桌,则必定是连续摆上K朵白花,否则只摆红花。输出T行,每行代表着这顿晚餐Marmot有多少种摆花的方法。

思路:找规律推状态。随便写几组就知道了。

AC代码:

const int maxn = 1e5 + 5;
typedef long long LL;
const LL mod = 1e9 + 7;

LL dp[maxn];
LL pre[maxn];

int main(){
	int t, k;
	cin >> t >> k;
	//k == 1: 1 -> 2, 2 -> 4, 3 -> 8, 4 -> 16              8 + 8
	//k == 2: 1 -> 1, 2 -> 2, 3 -> 3, 4 -> 5, 5 -> 8      5 + 3 
	//k == 3: 1 -> 1, 2 -> 1, 3 -> 2, 4 -> 3, 5 -> 4      3 + 1
	//k == 4: 1 -> 1, 2 -> 1, 3 -> 1, 4 -> 2, 5 -> 3, 6 -> 4    3 + 1      
	// l < k -> 1, l == k -> 2, l > k -> l - 1 + l - k
	for(int i = 1;i < k; ++i) dp[i] = 1, pre[i] = pre[i - 1] + dp[i];
	dp[k] = 2;
	pre[k] = pre[k - 1] + dp[k];
	for(int i = k + 1; i <= (int)1e5; ++i) dp[i] = dp[i - 1] + dp[i - k], pre[i] = pre[i - 1] + dp[i], pre[i] %= mod, dp[i] %= mod;
	//for(int i = 1; i <= 8; ++i) printf(" %lld ", dp[i]); printf("\n");
	//for(int i = 1; i <= 4; ++i) printf(" %lld ", pre[i]); printf("\n");
	while(t--){ 
		int a, b;
		cin >> a >> b;
		LL ans = (pre[b] - pre[a - 1] + mod) % mod;
		printf("%lld\n", ans);
	}
	return 0;
}

C - Consecutive Subsequence

题意:给出长度为N的数组,输出最长连续上升子序列,要求上升子序列的两个数差值为1。

思路:

        状态:dp[a_{i}]表示以a_{i}结尾的符合题意的最长上升子序列长度。

        转移方程:dp[a[i]] = dp[a[i] - 1] + 1

遍历map,得到最长的上升子序列与数值,从后往前遍历输出。

AC代码:



#define fi first
#define se second
onst int maxn = 2e5 + 5;

map<int, int> dp;
int a[maxn];

int main(){
	int n;
	scanf("%d", &n);
	dp.clear();
	for(int i = 1; i <= n; ++i){
		scanf("%d", a + i);
		dp[a[i]] = max(dp[a[i]], dp[a[i] - 1] + 1);
	}
	map<int, int> :: iterator it;
	int ans = 0;
	int x;
	for(it = dp.begin(); it != dp.end(); ++it){
		if(ans < (it -> se)){
			ans = it -> se;
			x = it -> fi;
		}
	}
	vector<int> out;
	out.clear();
	for(int i = n; i >= 1; --i){
		if(a[i] == x){
			out.pb(i);
			x--;
		}
		if(!x) break;
	}
	int	len = out.size();
	printf("%d\n", len);
	for(int i = len - 1; i >= 0; --i){
		printf("%d ", out[i]);
	}
	printf("\n");
	return 0;
}

D - Easy Problem

题意:给出长度为N的字符串S,与字符串每个字符对应的权重,要求删去字符串中的一些字符,使得字符串不含“hard”子序列。删去一个字符的花费为此字符的权重,要求删除字符的权重和最小。

思路:设字符数组ch[] = {"#hard"}

        状态:dp[i][j]表示字符串S的前i个字符里 ,不含子序列ch[1] - ch[j]的花费,如:dp[15][3]表示S的前15个字符所组成的字串不含“har”子序列。

        转移方程:若S_{i}==ch_{j},则dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j] + a[i])

                          若S_{i} != ch_{j},则dp[i][j] = dp[i - 1][j]

AC代码:

#define LL long long
const int maxn = 1e5 + 5;

char s[maxn];
LL a[maxn];
LL dp[maxn][5];//前i个字符不含ch1 ~ chj;
static char ch[] = {"#hard"};

int main(){
	int n;
	scanf("%d", &n);
	scanf(" %s", s + 1);
	for(int i = 1; i <= n; ++i){
		scanf("%lld", a + i);
	}
	for(int i = 1; i <= n; ++i) {
		if(s[i] == 'h') dp[i][1] = dp[i - 1][1] + a[i];
		else dp[i][1] = dp[i - 1][1];
	}
	for(int i = 2; i <= 4; ++i){
		for(int j = 1; j <= n; ++j){
			if(s[j] == ch[i]) dp[j][i] = min(dp[j - 1][i - 1], dp[j - 1][i] + a[j]);
			else dp[j][i] = dp[j - 1][i];
		}
	}
	printf("%lld\n", dp[n][4]);
	return 0;
}

E - Flood Fill

题意:有N个方块,每个方块有个初始的颜色c_{i},,要求选定一个方块,然后不断进行一个操作:将选定方块所在的连通块(颜色相同的邻接方块)的颜色转化成其他颜色,问需要操作几次才能够使得所有方块的颜色一致。

思路:区间dp。

           状态:dp[i][j][1]表示将区间[i, j]中的所有方块转化为与第i个方块相同的颜色。

                      dp[i][j][0]表示将区间[i, j]中的所有方块转化为与第j个方块相同的颜色。

          转移方程:dp[i][j][1]dp[i + 1][j][1]dp[i + 1][j][0]转移而来。dp[i][j][0]同理。

AC代码:

const int maxn = 5e3 + 5;

int c[maxn];
int dp[maxn][maxn][2];

int main(){
	int n;
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i) {
		scanf(" %d", c + i);
		dp[i][i][1] = dp[i][i][0] = 0;
	}
	for(int len = 2; len <= n; ++len){
		for(int i = 1; i <= n; ++i){
			int j = i + len - 1;
			if(j > n) break;
			dp[i][j][1] = min(dp[i + 1][j][1] + (c[i] != c[i + 1]), dp[i + 1][j][0] + (c[i] != c[j]));
			dp[i][j][0] = min(dp[i][j - 1][1] + (c[i] != c[j]), dp[i][j - 1][0] + (c[j] != c[j - 1]));
		}
	}
	int ans = min(dp[1][n][1], dp[1][n][0]);
	printf("%d\n", ans);
	return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值