第十三届蓝桥杯C++组
题目 2693:
蓝桥杯2022年第十三届决赛真题-卡牌
题目描述
这天,小明在整理他的卡牌。
他一共有 n 种卡牌,第 i 种卡牌上印有正整数数 i(i ∈ [1, n]),且第 i 种卡牌 现有 ai 张。
而如果有 n 张卡牌,其中每种卡牌各一张,那么这 n 张卡牌可以被称为一 套牌。小明为了凑出尽可能多套牌,拿出了 m 张空白牌,他可以在上面写上数 i,将其当做第 i 种牌来凑出套牌。然而小明觉得手写的牌不太美观,决定第 i 种牌最多手写 bi 张。
请问小明最多能凑出多少套牌?
输入格式
输入共 3 行,第一行为两个正整数 n, m。
第二行为 n 个正整数 a1, a2, …, an。
第三行为 n 个正整数 b1, b2, …, bn。
输出格式
一行,一个整数表示答案。
样例输入
4 5
1 2 3 4
5 5 5 5
样例输出
3
提示
这 5 张空白牌中,拿 2 张写 1,拿 1 张写 2,这样每种牌的牌数就变为了 3, 3, 3, 4,可以凑出 3 套牌,剩下 2 张空白牌不能再帮助小明凑出一套。
对于 30% 的数据,保证 n ≤ 2000 ;
对于 100% 的数据,保证 n ≤ 2 × 10^5 ; ai , bi ≤ 2n; m ≤ n2 。
听典的一个题,直接二分就行了
#include <iostream>
#include<vector>
#include<map>
#include<string>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll N = 2e5 + 10;
const ll mod = 1e9 + 7;
ll a[N],b[N];
ll n,m;
bool check(ll mid){
ll sum=0;
for(int i=1;i<=n;i++){
if(a[i]<mid)
if(mid-a[i]<=b[i])
sum+=mid-a[i];
else
return false;
if(sum>m)
return false;
}
return true;
}
void solve()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int j=1;j<=n;j++)
cin>>b[j];
ll l=0,r=N*N;
while(l<r){
ll mid=l+r>>1;
if(check(mid))
l=mid+1;
else
r=mid;
}
cout<<r-1<<'\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
题目 2694:
蓝桥杯2022年第十三届决赛真题-最大数字
题目描述
给定一个正整数 N。你可以对 N 的任意一位数字执行任意次以下 2 种操作:
-
将该位数字加 1。如果该位数字已经是 9,加 1 之后变成 0。
-
将该位数字减 1。如果该位数字已经是 0,减 1 之后变成 9。
你现在总共可以执行 1 号操作不超过 A 次,2 号操作不超过 B 次。
请问你最大可以将 N 变成多少?
输入格式
第一行包含 3 个整数:N, A, B。
输出格式
一个整数代表答案。
样例输入
123 1 2
样例输出
933
提示
对百位数字执行 2 次 2 号操作,对十位数字执行 1 次 1 号操作。
对于 30% 的数据,1 ≤ N ≤ 100; 0 ≤ A, B ≤ 10
对于 100% 的数据,1 ≤ N ≤ 10^17; 0 ≤ A, B ≤ 100
考虑数据范围,复杂度只和位数有关,首先贪心,尽量让前面的数变大,当然变大可以减法或者加法两种操作,因此复杂度大概在2^17左右,需要注意的地方就是如果减法剩余次数不能使得数字变大的话,就不需要进行减法操作。
#include <iostream>
#include<vector>
#include<map>
#include<string>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll N = 2e5 + 10;
const ll mod = 1e9 + 7;
ll ans=0;
void dfs(string s,int pos,int a,int b){
if(pos==s.size()||a==0&&b==0){
ll sum=0;
for(int i=0;i<s.size();i++)
sum=sum*10+s[i]-'0';
ans=max(ans,sum);
return ;
}
int d1='9'-s[pos],d2=s[pos]-'0'+1;
if(a>0){
string s1=s;
s1[pos]+=min(a,d1);
dfs(s1,pos+1,a-min(a,d1),b);
}
if(b>=d2){
string s1=s;
s1[pos]='9';
dfs(s1,pos+1,a,b-d2);
}
dfs(s,pos+1,a,b);
}
void solve()
{
string s;
int a,b;
cin>>s>>a>>b;
dfs(s,0,a,b);
cout<<ans<<'\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
题目 2695:
蓝桥杯2022年第十三届决赛真题-出差
题目描述
A 国有 N 个城市,编号为 1 . . . N。小明是编号为 1 的城市中一家公司的员工,今天突然接到了上级通知需要去编号为 N 的城市出差。
由于疫情原因,很多直达的交通方式暂时关闭,小明无法乘坐飞机直接从城市 1 到达城市 N,需要通过其他城市进行陆路交通中转。小明通过交通信息网,查询到了 M 条城市之间仍然还开通的路线信息以及每一条路线需要花费的时间。
同样由于疫情原因,小明到达一个城市后需要隔离观察一段时间才能离开该城市前往其他城市。通过网络,小明也查询到了各个城市的隔离信息。(由于小明之前在城市 1,因此可以直接离开城市 1,不需要隔离)
由于上级要求,小明希望能够尽快赶到城市 N,因此他求助于你,希望你能帮他规划一条路线,能够在最短时间内到达城市 N。
输入格式
第 1 行:两个正整数 N, M, N 表示 A 国的城市数量,M 表示未关闭的路线数量
第 2 行:N 个正整数,第 i 个整数 Ci 表示到达编号为 i 的城市后需要隔离的时间
第 3 . . . M + 2 行:每行 3 个正整数,u, v, c,表示有一条城市 u 到城市 v 的双向路线仍然开通着,通过该路线的时间为 c
输出格式
第 1 行:1 个正整数,表示小明从城市 1 出发到达城市 N 的最短时间(到达城市 N,不需要计算城市 N 的隔离时间)
样例输入
4 4
5 7 3 4
1 2 4
1 3 5
2 4 3
3 4 5
样例输出
13
提示
路线 1:1 -> 2 -> 4,时间为 4+7(隔离)+3=14
路线 2:1 -> 3 -> 4,时间为 5+3(隔离)+5=13
对于 100% 的数据,1 ≤ N ≤ 1000 , 1 ≤ M ≤ 10000, 1 ≤ Ci ≤ 200, 1 ≤ u, v ≤ N, 1 ≤ c ≤ 1000
在以某个点为起点的边上加上延迟然后跑一遍最短路就可以了,注意cpp的堆是大根堆,又一次忘记开了,要是实际比赛这直接jiji
#include <iostream>
#include<vector>
#include<map>
#include<string>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll N = 1e3 + 10;
const ll mod = 1e9 + 7;
vector<int>g[N],w[N];
int c[N],d[N];
bool vis[N];
int dij(int s,int end){
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
q.push({0,s});
while(!q.empty()){
int t=q.top().first,u=q.top().second;
q.pop();
if(vis[u])continue;
d[u]=t;
vis[u]=true;
if(u==end)
return t;
for(int i=0;i<w[u].size();i++){
if(!vis[g[u][i]])
q.push({w[u][i]+d[u],g[u][i]});
}
}
return d[end];
}
void solve()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>c[i];
int u,v,x;
for(int i=1;i<=m;i++){
cin>>u>>v>>x;
g[u].push_back(v);
g[v].push_back(u);
w[u].push_back(x);
w[v].push_back(x);
}
for(int i=2;i<=n;i++)
for(int j=0;j<g[i].size();j++)
w[i][j]+=c[i];
cout<<dij(1,n)<<'\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
题目 2696:
蓝桥杯2022年第十三届决赛真题-费用报销
题目描述
小明在出差结束后返回了公司所在的城市,在填写差旅报销申请时,粗心的小明发现自己弄丢了出差过程中的票据。
为了弥补小明的损失,公司同意小明用别的票据进行报销,但是公司财务要求小明提交的票据中任意两张的日期差不小于 K 天,且总金额不得超过实际差旅费用 M。
比如财务要求 K = 7 时,若小明提交了一张 1 月 8 日的票据,小明就不能提交 1 月 2 日至 1 月 14 日之间的其他票据,1 月 1 日及之前和 1 月 15 日及之后的票据则可以提交。
公司的同事们一起给小明凑了 N 张票据,小明现在想要请你帮他整理一下,从中选取出符合财务要求的票据,并使总金额尽可能接近 M。
需要注意,由于这些票据都是同一年的,因此 12 月底的票据不会影响到 1 月初票据的提交。这一年不是闰年。
输入格式
第 1 行:3 个整数,N, M, K
第 2 . . . N + 1 行:每行 3 个整数 mi , di , vi,第 i + 1 行表示第 i 张票据时间的月份 mi 和日期 di,vi 表示该票据的面值
输出格式
第 1 行:1 个整数,表示小明能够凑出的最大报销金额
样例输入
4 16 3
1 1 1
1 3 2
1 4 4
1 6 8
样例输出
10
提示
选择 1 月 3 日和 1 月 6 日的票据
对于 100% 的评测用例,1 ≤ N ≤ 1000, 1 ≤ M ≤ 5000, 1 ≤ K ≤ 50, 1 ≤ mi ≤ 12, 1 ≤ di ≤ 31, 1 ≤ vi ≤ 400
日期保证合法。
动态规划,dp[i] [j]表示选前i种票据且总票据金额不超过j的可获得的最大金额,因为还需要保证选取第i种票据时在它前面k天以内的票据不能存在,所以还要保证转移时的i1票据所在的日期要满足要求,这个地方二分一下就可以了
#include <iostream>
#include<vector>
#include<map>
#include<string>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll N = 1e3 + 10;
const ll mod = 1e9 + 7;
struct node{
int day;
int money;
bool operator<(const node&a){
return day<a.day;
}
}p[N];
int d[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
int dp[N][5*N];//选前i种且总金额不超过j的最大金额
void solve()
{
for(int i=2;i<=12;i++)
d[i]+=d[i-1];
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
int a,b,c;
cin>>a>>b>>c;
b+=d[a-1];
p[i]={b,c};
}
sort(p+1,p+1+n);
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(j<p[i].money){
dp[i][j]=dp[i-1][j];
continue;
}
int c=p[i].day-k;
int l=1,r=i+1;
while(l<r){
int mid=l+r>>1;
if(p[mid].day<=c)
l=mid+1;
else
r=mid;
}
r--;
dp[i][j]=max(dp[i-1][j],dp[r][j-p[i].money]+p[i].money);
}
ans=max(ans,dp[i][m]);
}
cout<<ans<<'\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
题目 2697:
蓝桥杯2022年第十三届决赛真题-故障
题目描述
在软件或系统开发中,我们会遇到各种各样的故障。为了从故障现象反推故障原因,工程师们会总结一种叫做相关性矩阵的二维表格,来表示故障原因与故障现象之间的关系。比如:
其中每行表示一种故障原因,每一列表示一种故障现象。该矩阵表示故障原因 A 可能产生故障现象 2、3、4,故障原因 B 可能产生故障现象 1、3。
在实际开发过程中,如果出现了故障原因,工程师就可以根据故障现象,去计算每种故障原因产生的概率,并按照概率大小对故障原因进行排查,以达到快速定位故障原因的目的。
现在,我们假设系统开发中同一时间只会出现一种故障原因,并且故障原因引起各故障现象是独立事件。举个例子来说:
假设系统现在发生了故障原因 A,有 1/3 的概率出现故障现象 2,有 1/4 的概率出现故障现象 3,有 1/2 的概率出现故障现象 4。由于 3 种现象是独立发生的,因此有1/(234)的概率同时出现故障 2、3、4。
约定若相关性矩阵中没有 ‘x’ 记号,则表示该故障原因一定不会产生某故障现象,比如故障原因 A,一定不会产生故障现象 1。
根据历史经验数据,我们统计得到了每一种故障原因出现的概率以及每一 种故障原因对应的故障现象产生概率。
现在已知系统出现的故障现象,求问各个故障原因发生的概率。
输入格式
第 1 行:2 个正整数 N, M,N 表示故障原因的个数(编号 1 . . . N),M 表示故障现象的个数(编号 1 . . . M).
第 2 行:N 个整数,第 i 个数表示故障原因 i 产生的概率 Pi .
第 3 . . . N + 2 行:每行 M 个整数,第 i + 2 行第 j 个整数 Pij 表示故障原因 i 出现故障现象 j 的概率(百分比).
第 N + 3 行:1 个正整数 K,表示目前出现的故障现象数量。
第 N + 4 行:K 个正整数,依次为当前出现的故障现象编号,不会重复。
输出格式
第 1 . . . N 行:按概率从高到低输出每种故障原因及其可能的概率,若出现概率相同则优先输出编号小的故障原因。第 1 个数为故障原因编号,第 2 个数为故障概率(百分比),保留 2 位小数。
样例输入
3 5
30 20 50
0 50 33 25 0
30 0 35 0 0
0 0 0 25 60
1
3
样例输出
2 56.89
1 43.11
3 0.00
提示
对于所有测试用例,1 ≤ N ≤ 40, 1 ≤ M ≤ 20, 0 ≤ Pi ≤ 100, ∑ (Pi) = 100, 0 ≤ Pij ≤ 100.
概率学的太烂了,就会一点点简单数论和组合数学,贝叶斯公式不会,贴一份别人的题解
用100减去未出现的现象的对应故障的概率。
令各个现象为Ri, 故障为A,B,C…
则P(R1R2R3..Rm|A) = P(R1|A) * P(R2|A) … * P(Rm|A)
再带入贝叶斯公示就好了。
注意开long double,而且注意浮点数比较时的精度问题。
#include <stdio.h>
#include <algorithm>
#include <math.h>
#include<iostream>
using namespace std;
const double esp = 1e-12;
int n, m, k, P[50][31];
struct node {
long double p;
int i;
inline bool operator<(const node &nd) const
{
return (abs(p - nd.p) < esp) ? i < nd.i : p > nd.p;
}
} ans[50];
int main()
{
scanf("%d %d", &n, &m);
for (int i = 0; i < n; ++i)
scanf("%d", &P[i][0]);
for (int i = 0; i < n; ++i)
for (int j = 1; j <= m; ++j)
scanf("%d", &P[i][j]), P[i][j] = 100 - P[i][j];
scanf("%d", &k);
for (int g = 0, j; g < k; ++g)
{
scanf("%d", &j);
for (int i = 0; i < n; ++i)
P[i][j] = 100 - P[i][j];
}
long double sum = 0;
for (int i = 0; i < n; ++i)
{
ans[i].p = P[i][0] / 100.0;
for (int j = 1; j <= m; ++j)
ans[i].p *= P[i][j] / 100.0;
ans[i].i = i + 1;
sum += ans[i].p;
}
sort(ans, ans + n);
for (int i = 0; i < n; ++i)
{
long double t = (abs(sum) < esp) ? 0.0 : 100 * ans[i].p / sum;
printf("%d %.2LF\n", ans[i].i, t);
}
return 0;
}
题目 2698:
蓝桥杯2022年第十三届决赛真题-机房
题目描述
这天,小明在机房学习。
他发现机房里一共有 n 台电脑,编号为 1 到 n,电脑和电脑之间有网线连接,一共有 n − 1 根网线将 n 台电脑连接起来使得任意两台电脑都直接或者间接地相连。
小明发现每台电脑转发、发送或者接受信息需要的时间取决于这台电脑和多少台电脑直接相连,而信息在网线中的传播时间可以忽略。比如如果某台电脑用网线直接连接了另外 d 台电脑,那么任何经过这台电脑的信息都会延迟 d 单位时间 (发送方和接收方也会产生这样的延迟,当然如果发送方和接收方都是同一台电脑就只会产生一次延迟)。
小明一共产生了 m 个疑问:如果电脑 ui 向电脑 vi 发送信息,那么信息从 ui 传到 vi 的最短时间是多少?
输入格式
输入共 n + m 行,第一行为两个正整数 n, m。
后面 n − 1 行,每行两个正整数 x, y 表示编号为 x 和 y 的两台电脑用网线直接相连。
后面 m 行,每行两个正整数 ui , vi 表示小明的第 i 个疑问。
输出格式
输出共 m 行,第 i 行一个正整数表示小明第 i 个疑问的答案。
样例输入
4 3
1 2
1 3
2 4
2 3
3 4
3 3
样例输出
5
6
1
提示
这四台电脑各自的延迟分别为 2, 2, 1, 1。
对于第一个询问,从 2 到 3 需要经过 2, 1, 3,所以时间和为 2 + 2 + 1 = 5。
对于第二个询问,从 3 到 4 需要经过 3, 1, 2, 4,所以时间和为 1+2+2+1 = 6。
对于第三个询问,从 3 到 3 只会产生一次延迟,所以时间为 1。
对于 30% 的数据,保证 n, m ≤ 1000;
对于 100% 的数据,保证 n, m ≤ 100000 。
思路很简单,先建图后建树,然后跑lca,求lca的过程中顺便把延迟之和求出来,注意要加上终点的延迟
这题wa了几次,st预处理的时候i,j不分wa了,还是熟练度不太高
#include <iostream>
#include<vector>
#include<map>
#include<string>
#include<map>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll N = 1e5 + 10;
const ll mod = 1e9 + 7;
int fa[N] = { 0,1 }, depth[N];
pair<int, int>st[N][15];
vector<int>g[N];
void init(int f, int root) {
depth[root] = depth[f] + 1;
fa[root] = f;
st[root][0] = { g[root].size(),f };
for (int i = 0; i < g[root].size(); i++) {
if (g[root][i] != f)
init(root, g[root][i]);
}
}
int lca(int u, int v) {
if (u == v)
return g[u].size();
int ans = 0;
for (int i = 14; i >= 0; i--) {
if (st[u][i].second != st[v][i].second) {
ans += st[u][i].first + st[v][i].first, u = st[u][i].second, v = st[v][i].second;
}
}
ans += st[u][0].first + st[v][0].first + g[st[u][0].second].size();
return ans;
}
void solve()
{
int n, m;
cin >> n >> m;
int u, v;
for (int i = 1; i < n; i++)
cin >> u >> v, g[u].push_back(v), g[v].push_back(u);
init(0, 1);
for (int i = 1; i <= 14; i++)
for (int j = 1; j <= n; j++)
st[j][i] = { st[j][i - 1].first + st[st[j ][i-1].second][i - 1].first,st[st[j][i - 1].second][i - 1].second };
while (m--) {
cin >> u >> v;
int a = u, b = v;
if (depth[u] < depth[v])
swap(u, v);
int x = depth[u] - depth[v], ans = 0;
for (int i = 0; i <= 14 && x; i++) {
if (x & 1) {
ans += st[u][i].first;
u = st[u][i].second;
}
x >>= 1;
}
ans += lca(u, v);
cout << ans << '\n';
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
题目 2699:
蓝桥杯2022年第十三届决赛真题-齿轮
题目描述
这天,小明在组装齿轮。
他一共有 n 个齿轮,第 i 个齿轮的半径为 ri,他需要把这 n 个齿轮按一定顺序从左到右组装起来,这样最左边的齿轮转起来之后,可以传递到最右边的齿轮,并且这些齿轮能够起到提升或者降低转速 (角速度) 的作用。
小明看着这些齿轮,突然有 Q 个疑问:能否按一定顺序组装这些齿轮使得最右边的齿轮的转速是最左边的齿轮的 qi 倍?
输入格式
输入共 Q + 2 行,第一行为两个正整数 n, Q,表示齿轮数量和询问数量。
第二行为 n 个正整数 r1,r2, …,rn,表示每个齿轮的半径。
后面 Q 行,每行一个正整数 qi 表示询问。
输出格式
Q 行,对于每个询问,如果存在至少一种组装方案满足条件,输出 ‘YES‘,否则输出 ‘NO‘。
样例输入
5 3
4 2 3 3 1
2
4
6
样例输出
YES
YES
NO
提示
询问 1 方案之一:2 3 3 4 1 。
询问 2 方案之一:4 2 3 3 1 。
询问 3 没有方案。
对于 15% 的数据,保证 n, Q ≤ 100 ;
对于 30% 的数据,保证 n, Q ≤ 2000 ;
对于 100% 的数据,保证 n, Q ≤ 2 × 105 ; ri , qi ≤ 2 × 105 。
很容易知道的就是倍数只和头尾转轮有关,所以问题转换成了序列中是否存在两个数a,b使得a/b=x。思路是先预处理一下有哪些x存在,然后查询直接看存不存在这个x就行了。预处理的话,先排序然后对于每个ai,分解因数,看一下这个因数是否在a数组中出现过,假设因子为j,需要判断(ai)/j是否在数组中存在,如果存在就x=j,同时如果j在数组中存在,那么存在x=(ai)/j,复杂度n^1.5。
刚开始写的时候没有想着直接分解因数,而是采取了类似筛法的思路,在dotcpp上面过了,后面觉得有点懵,因为我认为时间复杂度说不去的,后面跑到蓝桥杯官网交了一发,也在规定时间内跑完,但20发数据wa了一发小数据,对了95%,也不知道哪里错了,不过时间复杂度可以过就觉得蛮离谱的
95%的错误代码
#include <iostream>
#include<vector>
#include<map>
#include<string>
#include<map>
#include<queue>
#include<algorithm>
#include<unordered_map>
using namespace std;
typedef long long ll;
const ll N = 2e5 + 10;
const ll mod = 1e9 + 7;
void solve()
{
int n,q;
cin>>n>>q;
int x;
unordered_map<int,int>mp;
unordered_map<int,int>ans;
for(int i=1;i<=n;i++){
cin>>x,mp[x]++;
if(mp[x]>=2)
ans[1]++;
}
for(auto it:mp){
for(int i=2;i*it.first<N;i++)
if(mp[i*it.first]>0)
ans[i]++;
else
mp.erase(i*it.first);
}
while(q--){
cin>>x;
if(ans[x]>0)
cout<<"YES"<<'\n';
else
cout<<"NO"<<'\n';
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
正确代码
#include<bits/stdc++.h>
using namespace std;
int a[200005];
int main(){
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++){
int x;
cin>>x;
a[x]++;
}
while(m--){
int x;
cin>>x;
int f=0;
for(int i=1;i<=n/x;i++){
if(a[i]&&a[i*x]){
if(i!=i*x||i==i*x&&a[i]>1){
f=1;
break;
}
}
}
if(f||(n==1&&x==1)) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
题目 2700:
蓝桥杯2022年第十三届决赛真题-搬砖
题目描述
这天,小明在搬砖。
他一共有 n 块砖,他发现第 i 砖的重量为 wi,价值为 vi。他突然想从这些砖中选一些出来从下到上堆成一座塔,并且对于塔中的每一块砖来说,它上面所有砖的重量和不能超过它自身的价值。
他想知道这样堆成的塔的总价值(即塔中所有砖块的价值和)最大是多少。
输入格式
输入共 n + 1 行,第一行为一个正整数 n,表示砖块的数量。
后面 n 行,每行两个正整数 wi , vi 分别表示每块砖的重量和价值。
输出格式
一行,一个整数表示答案。
样例输入
5
4 4
1 1
5 2
5 5
4 3
样例输出
10
提示
选择第 1、2、4 块砖,从上到下按照 2、1、4 的顺序堆成一座塔,总价值为 4 + 1 + 5 = 10 。
对于 20% 的数据,保证 n ≤ 10;
对于 100% 的数据,保证 n ≤ 1000; wi ≤ 20; vi ≤ 20000 。
印象中第一次做这一类的题,这个应该也是个蛮典的题,本人dp比较烂,做题过程中也算对01背包等一类背包问题有了一些新的感悟,先说感悟再说解法
拿01背包来说,01背包实际有一个隐含条件,对于dpij和dpkj,i小于k,选择i后面可能可以选择k,选择k后面必然不能选择i,在实际场景中如果两者没有实际的制约与时序关系,那么我们就需要构造一个时序,在最基本01背包中,看起来i和k没有什么制约关系,但实际我们设置状态转移方程时也假设了i只会出现在k之前而不会出现在k后面的条件。说的不太清楚,凑合看看吧。
拿这个题来说
对于每个砖块怎么保证,他上面的重量总和小于等于他的价值呢,这个该怎么维护呢。实际上在纸上画一画,思考一下可以先处理上面的砖块,再处理下面的砖块,一点一点的处理,因为你如果先处理下面的砖块,你怎么选对吧,才能保证下面的都满足。所以先考虑上面的,从上往下每个砖块都能线性处理。
定义dp[i] [j]是用了前i个砖块,此时重量恰好为j的时候的最大价值。
但是这样该怎么枚举砖块的顺序呢?实际上这就是要排序,以前就做过这种,比如国王的游戏。这题可以让i在上面,j在下面,我们让wi+sum<vj,wj+sum>vi,也就是让i在上面的时候j可以选,让i在下面的时候,j不可以选了。所以转化一下这个公式,可以把sum丢掉,sum是上面所有的重量。然后公式就是wi<vj,vi<wj。两个想加可以得到wi+vi<wj+vj。所以按这个排序就行。然后对于维护每个砖块上面的重量小于他的价值,其实在dp的时候加个判断条件就够了。然后其实就是01背包,加了一个贪心排序,和多了一个判断条件。可以优化为1维的。
没有写滚动数组版本的,大家自己写写吧
#include <iostream>
#include<vector>
#include<map>
#include<string>
#include<map>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll N = 1e5 + 10;
const ll mod = 1e9 + 7;
struct node {
int w, v;
bool operator<(const node& a) {
return w + v < a.w + a.v;
}
}p[1010];
int dp[1010][20010];
void solve()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> p[i].w >> p[i].v;
int ans = 0;
sort(p + 1, p + 1 + n);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n * 20 && j - p[i].w <= p[i].v; j++) {
dp[i][j] = dp[i - 1][j];
if (j - p[i].w >= 0) {
if (dp[i - 1][j - p[i].w] != 0)
dp[i][j] = max(dp[i][j], dp[i - 1][j - p[i].w] + p[i].v);
if (j - p[i].w == 0)
dp[i][j] = max(p[i].v, dp[i][j]);
}
ans = max(dp[i][j], ans);
}
cout << ans << '\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}