牛客动态规划入门班(上)

不降数
题意:
求出恰好 n n n 位,从高位到低位非递减这样的数的个数
思路:
可以用矩阵快速幂 或者卡常优化
鉴于牛客跑的快,可以卡常优化过掉
考虑 f [ i ] f[i] f[i] 表示以 i i i 结尾的个数
可以发现,递推几次, f [ i ] f[i] f[i] 就表示几位数,以 i i i 结尾的数的个数

舔狗舔到最后一无所有
题意:
思路:
题解
集合划分的思路

牛牛的旅游纪念品
要求选择的任意两个物品之间的距离 > = k >=k >=k

困难的数学题
要求把 n n n 分解乘若干个不小于 k k k 的数的方案数

Music Problem
背包dp,抽屉定理,二进制优化,bitset优化
题解

简单瞎搞题
bitset优化
注意这个每次必须转移,所以上一层转移到下层之后,需要清空掉上层的状态
题解
因为要 + j ∗ j +j*j +jj,被转移的所有状态都左移 j ∗ j j*j jj 位,原来为 i i i 位,现在成了 i + j ∗ j i+j*j i+jj,或一下就可以将 1 1 1 的状态转移过去,优化掉一层循环
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 1e6 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;

void work()
{
	cin >> n;
	bitset <maxn> f;
	f[0] = 1;
	for(int i = 1; i <= n; ++i){
		int l, r;cin >> l >> r;
		bitset <maxn> tmp;// 空间优化,只和上一层有关
		for(int j = l; j <= r; ++j){
			tmp |= (f << j * j);// 这样bitset直接或可以优化掉一层for循环,也就是枚举上一层状态的循环
		}
		f = tmp;
	}
	cout << f.count();
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

美味菜肴
贪心+dp

失衡天平
题意:
n n n 个物品,每个重 w i w_i wi,天平两端的重量之差小于等于 m m m 时会平衡,如果平衡则可以那走两边的物品,可以多次称量,求拿走物品重量之和最大是多少
思路:
左右盘差值就是能称出来的重量,所以和砝码称重类似
f [ i ] [ j ] f[i][j] f[i][j] i i i 个物品,左盘和右盘差值为 j j j 的最大重量
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
int k;
int a[maxn], c[maxn]; 
int f[109][maxn];
// 前 i 个物品,左盘和右盘差值为 j 的最大重量 
void work()
{
	cin >> n >> m;
	int sum = 0;
	for(int i = 1; i <= n; ++i){
		cin >> a[i];
		sum += a[i];
	}
	memset(f, -0x3f, sizeof(f));
	f[0][0] = 0;
	for(int i = 1; i <= n; ++i){
		for(int j = 0; j <= sum; ++j){
			f[i][j] = f[i-1][j];// 不选a_i
			int Max = max(f[i-1][abs(j - a[i])], f[i-1][j + a[i]]);// 选a_i的最大值
			f[i][j] = max(f[i][j], Max + a[i]);
		}
	}
	int ans = 0;
	for(int i = 0; i <= m; ++i)	ans = max(ans, f[n][i]);
	cout << ans;
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

钉子和小球
思路:
题解
本质就是类似数字三角形的递推
因为存在空缺的位置,从讨论当前状态,往后转移比较好
这种求概率的思路:
概率 d p dp dp
求总方案数,然后 合 法 方 案 数 / 总 方 案 数 合法方案数/总方案数 / 就好了
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 1e2 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
/*
数字三角形
* 
* .
* * * 
* . * * 
* * * * *
*/
char a[maxn][maxn];
ll f[maxn][maxn];

void work()
{
	cin >> n >> m;
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= i; ++j)
			cin >> a[i][j];
	f[1][1] = 1;
	for(int i = 1; i <= n; ++i){
		for(int j = 1; j <= i; ++j)
		{
			if(a[i][j] == '*')// 当前状态往后转移比较好写
			{
				f[i+1][j] += f[i][j];
				f[i+1][j+1] += f[i][j];
			}
			else f[i+2][j+1] += f[i][j] * 4;
		}
	}	
	ll sum = 0;
	++m;++n;// 细节,答案在 n+1 层,m是从0开始的
	for(int i = 1; i <= n; ++i) sum += f[n][i];
	ll d = __gcd(f[n][m], sum);
	if(d > 1) f[n][m] /= d, sum /= d;
	cout << f[n][m] << "/" << sum;
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

小A买彩票
题意:
四种彩票金额分别为 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4 元,买一张需要 3 3 3 元,连续买了 n n n 天后不亏本的概率是多少
思路:
转化题目问题,求满足 n n n 天后获奖金额 > = 3 ∗ n >=3*n >=3n 的方案数
code:

#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
ll f[39][129]; // 对于前i张中奖j元的方案数
void work()
{
	cin >> n;
	f[0][0] = 1;
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= 4 * i; ++j)
		{
			if(j >= 1) f[i][j] += f[i-1][j-1];
			if(j >= 2) f[i][j] += f[i-1][j-2];
			if(j >= 3) f[i][j] += f[i-1][j-3];
			if(j >= 4) f[i][j] += f[i-1][j-4];
		}
	ll ans = 0;
	for(int i = 3 * n; i <= 4 * n; ++i)
		ans += f[n][i];
	ll sum = (1ll << (n << 1));
	ll d = __gcd(sum, ans);
	sum /= d; ans /= d;
	cout << ans << "/" << sum << endl;	
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

购物
题意:
一个糖果工厂每天生产 m m m 个糖果,矩阵中 a i , j a_{i,j} ai,j 表示 第 i i i 天,第 j j j 个糖果的价格。每天可以买至多 m m m 个糖果(每种买一个,或者不买),买的糖果可以当天不吃,但要保证每天至少吃一个糖果。
某一天买了 k k k 个糖果需要多支付 k 2 k^2 k2
求最小花费
思路:
背包dp
考虑 f [ i ] [ j ] f[i][j] f[i][j] 表示前 i i i 天买了 j j j 个糖果,需要保持 i < = j i<=j i<=j
我们没必要知道每个糖果的价格,每次买 k k k 个糖果,那么只需要价格最小的 k k k 个即可
预处理前缀和
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 3e2 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
ll c[maxn][maxn];
ll f[maxn][maxn];// f[i][j] 表示前i天买了j个糖果,需要一直满足 i <= j 
void work()
{
	cin >> n >> m;
	for(int i = 1; i <= n; ++i)
	{
		for(int j = 1; j <= m; ++j)
			cin >> c[i][j];
		// 非常nice的处理
		sort(c[i] + 1, c[i] + 1 + m);
		for(int j = 1; j <= m; ++j)
			c[i][j] += c[i][j-1];
	}
	
	memset(f, 0x3f, sizeof(f));
	f[0][0] = 0;
	for(int i = 1; i <= n; ++i)
		for(int j = i; j <= maxn - 5; ++j)
			for(int k = 0; k <= min(1ll*j, m); ++k)	
				f[i][j] = min(f[i][j], f[i-1][j-k] + c[i][k] + k * k);
		
	ll ans = INF;
	for(int i = n; i <= maxn - 5; ++i)
		ans = min(ans, f[n][i]);
	cout << ans;
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

Palindrome
这题假了??????没这个题了
题意:
给定一个字符串,要求通过插入字符的操作,使其变成回文串,所需要插入的最少的字符个数。
思路:
思路一:求原串与反转串的最长公共子序列,答案就是 n − L C S n-LCS nLCS
思路二:
容易考虑区间 [ i , j ] [i,j] [i,j],容易想到状态转移方程
s [ i ] = s [ j ] s[i]=s[j] s[i]=s[j] 时, f [ i ] [ j ] = f [ i + 1 ] [ j − 1 ] f[i][j]=f[i+1][j-1] f[i][j]=f[i+1][j1]
s [ i ] ! = s [ j ] s[i]!=s[j] s[i]!=s[j] 时, f [ i ] [ j ] = m i n ( f [ i + 1 ] [ j ] , f [ i ] [ j − 1 ] ) + 1 f[i][j]=min(f[i+1][j],f[i][j-1])+1 f[i][j]=min(f[i+1][j],f[i][j1])+1
但这题不能用区间dp,仔细想想这题是回文串,小区间是无法递推出大区间的
正确思路是左端点从 n n n 开始枚举,右端点从 1 1 1 开始枚举,两层 f o r for for 转移
因为 i i i 需要从 i + 1 i+1 i+1 递推来, j j j 需要从 j − 1 j-1 j1 递推来
注意可以 i i i 只与前一维有关,可以滚动数组
j j j 虽然也只与前一维有关,但是应该不能压了
code1:

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 5e3 + 3;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n, m;
string s;
short f[maxn][maxn];

void work()
{
	cin >> n >> s;
	string t = s;
	reverse(t.begin(), t.end());
	s = "@" + s; t = "@" + t;
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= n; ++j)
			if(s[i] == t[j])
				f[i][j] = f[i-1][j-1] + 1;
			else 
				f[i][j] = max(f[i-1][j], f[i][j-1]);
	cout << n - f[n][n];
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

code2:

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 5e3 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n, m;
int f[2][maxn];

void work()
{
	string s;cin >> n >> s;
	s = "@" + s;
	int op = 1;
	for(int i = n; i >= 1; --i, op ^= 1)
		for(int j = i + 1; j <= n; ++j)
			if(s[i] == s[j])
				f[op][j] = f[op ^ 1][j-1];
			else 
				f[op][j] = min(f[op ^ 1][j], f[op][j-1]) + 1;
	cout << f[op ^ 1][n];
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

Cheapest Palindrome–poj–3280
(这道题虽然不是这个专题的,但是这道题和上边的题十分相似,就搬来了
题意:
给你一串字符串,可以插入或者删除,将之变为回文数。插入和删除每个字符都有代价,问你代价和最小的为多少。
思路:

code:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值