2022年第十三届蓝桥杯大赛软件类决赛C/C++大学B组(国赛)题解

刷题链接:
https://www.dotcpp.com/oj/train/1038/

题目一:C题 卡牌

看网上更多的是使用的二分,我是自己模拟的,先用大致的模拟思路写,然后再考虑能不能优化
1.对序列进行从小到大排序后,每次抽出一套牌前几个数总会减小,直到减小为0,然后再对前几个牌数为0的牌进行增加,前几个个数为0的牌肯定是同时增加的
2.不进行优化,每次对序列的前几个已经为0的数加一,每次对序列所有数减一,会导致超时
3.优化(贪心):就是想办法每次不加一减一,每次减尽可能最多(减去最小的即排序后的第一个),每次加尽可能最多(满足三个条件:1.bi>0 2.m足够前几个均分 3.最多加到第一个不为0的个数 )
AC代码:

#include<stdio.h>
#include<algorithm>
#include<iostream> 
using namespace std;
typedef long long LL;
int n;
LL m;         //注意这个数据范围 
const int N=200005;
typedef pair<int,int>PII;       //第一维存ai,第二维存下标,便于通过这个下标找对应的bi,就不需要自己写排序函数了 
PII a[N];
int b[N];
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++) 
	{
		int data;
		scanf("%d",&data);
		a[i].first=data;
		a[i].second=i;
	}
	for(int i=1;i<=n;i++) scanf("%d",&b[i]);
	sort(a+1,a+n+1);    //pair排序默认是按照first进行升序排列的 
	LL ans=0;
	while(true)         //m最后不一定为0,可能会有剩余,不适合用于循环结束判断 
	{
		int end=0;
		int t=a[1].first;    //每次减少尽可能的多,减去数列中最小的 
		for(int i=1;i<=n;i++)
		{
			a[i].first-=t;
			if(a[i].first==0) end=i;
		}
		ans+=t;
		int minb=b[a[1].second];   //每次加回去尽可能多,前几个为0的同时加,满足三个条件:1.bi>0  2.m足够前几个均分  3.最多加到第一个不为0的数的数据 
		for(int i=1;i<=end;i++)
		{
			if(b[a[i].second]<minb) minb=b[a[i].second];
		}
		if(minb==0) break;
		int j=minb;
		for(;j>=1;j--)
		{
			if(m>=end*j) break;
		}
		if(j==0) break;    //有边界情况就要考虑特殊判断一下,所有的ai都一样时 
		if(end<n) j=j<a[end+1].first?j:a[end+1].first;
		for(int i=1;i<=end;i++)
		{
			if(b[a[i].second]==0) 
			{
				m=0;
				break;
			}
			a[i].first+=j;
			b[a[i].second]-=j;
			m-=j;
		}
	}
	printf("%lld",ans);
	return 0;
}

题目二:D题 最大数字

1.简单的dp问题,因为序列满足局部最优解,前i个数字构成的数最大其实是前i-1个数字构成的最大数加上最大化的第i个数字,前i个数字构成最大数的状态可以从前i-1个数构成最大数转移过来,但是要保存一下对前i-1个数字进行操作用了多少加和减操作
2.dp[i][j][k] 前i个数字最多进行j次加和最多进行k次减得到的最大数,然后考虑最后一步,第i个数字会进行多少次加和减操作,进行一下枚举

#include<iostream>
using namespace std;
typedef long long LL;
int n,A,B;
int num[20];
LL dp[20][105][105];
int Now(int i,int s,int x)     //对第i位数字进行s次加和x次减后得到的数字
{
	int tmp=num[i]+s-x;
	if(tmp<0) 
	{
		tmp=-tmp;
		return (10-tmp%10)%10;
	}
	else return tmp%10;
}
LL Add(LL pre,LL now)        //将第i个数字拼到后面
{
	return pre*10+now;
} 
int main()
{
	char h=cin.get();
	int ind=1;
	while(h!=' ')
	{
		num[ind++]=h-'0';
		h=cin.get();
	}
	cin>>A>>B;
	dp[0][0][0]=0;
	dp[1][0][0]=num[1];
	
	int n=ind-1;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=A;j++)
		{
			for(int k=0;k<=B;k++)
			{
				for(int s=0;s<=j;s++)
				{
					for(int x=0;x<=k;x++)
					{
						dp[i][j][k]=max(Add(dp[i-1][j-s][k-x],Now(i,s,x)),dp[i][j][k]);
					}
				}
			}
		}
	}
	cout<<dp[n][A][B]<<endl;
	return 0;
} 

题目三:E题 出差

简单的单源最短路径,隔离天数和路径天数是同等级别的,直接将到达某个目的地的隔离天数加到路径权值上
int类型的无穷大设置为0x3f3f3f3f (4个)
longlong类型的无穷大设置为0x3f3f3f3f3f3f3f3f(8个)

#include<iostream>
using namespace std;
const int N=1010;
const int INFI=0x3f3f3f3f;   //这个记住了,一定要设置为最大的,1e8都不行 ,
int n,m;
int graph[N][N];
int geli[N];
int dist[N];
int visited[N];
int Findmin()
{
	int min_i=-1,min=INFI;
	for(int i=1;i<=n;i++)
	{
		if(!visited[i]&&dist[i]<min)
		{
			min=dist[i];
			min_i=i;
		}
	}
	return min_i;
}
void Dijistra(int s)
{
	for(int i=1;i<=n;i++)
	{
		dist[i]=graph[s][i];
	}
	visited[s]=1;
	
	while(true)
	{
		int min_i=Findmin();
		if(min_i==-1) break;
		
		visited[min_i]=1;
		for(int i=1;i<=n;i++)
		{
			if(!visited[i]&&graph[min_i][i]!=INFI&&dist[min_i]+graph[min_i][i]<dist[i])
			{
				dist[i]=dist[min_i]+graph[min_i][i];
			}
		}
	}	
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>geli[i];
	geli[1]=0;
	geli[n]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++) 
		{
			if(i==j) graph[i][j]=0;
			else graph[i][j]=INFI;
		}
	}
	for(int i=0;i<m;i++)
	{
		int v1,v2,w;cin>>v1>>v2>>w;
		if(v1==v2)    //要注意考虑自环的情况,这道题多重边的好像没有涉及
		{
			graph[v1][v2]=0;
			graph[v2][v1]=0; 
		}
		else
		{
			graph[v1][v2]=w+geli[v2]; 
			graph[v2][v1]=w+geli[v1]; 
		}
		
	}

	Dijistra(1);
	cout<<dist[n];
	return 0;
}

题目四:F题 费用报销

简单的0/1背包变题
dp[i]表示前i张票据能凑成的最大费用
题目说的面值尽可能接近M其实就是面值最大
判断最后一个票据能不能要不要,如果不能要:dp[i][j]=dp[i-1][j];
如果能要(必须满足第i张票据的面值小于实际费用),再考虑要了和更大还是不要和更大
并且前一个票据必须与当前票据时间相差大于等于k天 dp[i][j]=max(dp[i][j],dp[h][j-w[num[i]]]+w[num[i]]);
错误思想:求与当前票据相差大于等于k天的所有dp[h][j-w[num[i]]]的最大值,再加上w[num[i]]
只需要从前一个状态转移过来,即dp[j],j就是前一个状态,j满足与与当前票据时间相差大于等于k天
dp[i]不是看最后一个选的哪一个,而是前个(与最长递增子序列的问题区别开)
AC代码:

#include<iostream>
#include<algorithm>
using namespace std;
int n,m,k;
const int N=1005;
int num[N];
int dp[N][5005];
int w[N];
int month[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
int main()
{
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++)
	{
		int y,d,wi;cin>>y>>d>>wi;
		for(int j=1;j<=y-1;j++)
		num[i]+=month[j];  //将日期转换为距离1月1日的天数 
		num[i]+=d;
		w[num[i]]=max(w[num[i]],wi);   //按照日期存储当天的面值,如果有当天的重复票据,选最大的那个 
	}
	sort(num+1,num+n+1);   //将日期从小到大排序 
	for(int i=1;i<=n;i++)
	{
		int h=i-1;
		while(h>0&&num[i]-num[h]<k) h--;  //求与当前票据相差大于等于k天的最后一个状态 
		for(int j=1;j<=m;j++)
		{
			dp[i][j]=dp[i-1][j];    
			if(w[num[i]]<=j)  //第i天的费用没有超过实际费用,可以选择是否要
			{
				dp[i][j]=max(dp[i][j],dp[h][j-w[num[i]]]+w[num[i]]);
			}
		} 
	} 
	cout<<dp[n][m];
	return 0;
	
}

题目五:G题 故障

这是一道概率题,首先要读懂题目,用概率公式来做
题目读懂后剩下的就是简单的模拟了
P(Ni):故障原因i发生的概率
P(Mj/Ni) :故障原因i导致故障现象j的概率
P(Mj) :故障现象j的概率
P(MjNi):出现故障现象i并且原因是j的概率
P(MjNi) = P(Ni) * P(Mj/Ni)

分析样例:
首先求P(Ni),由于故障原因3不可能导致故障现象3的发生,因此故障原因3不考虑了,
注意一点: 只要故障现象中存在一个现象是该故障原因不可能产生的,就都不考虑该故障原因了
P(N1)=30/(30+20) = 0.6 P(N2)=20/(30+20)=0.4
再求每个故障原因导致题目要求的所有故障现象发生的概率P(Mj/Ni):
(注意这个j是可以取多个值的,表示这多个故障现象同时发生,此时由于各个故障现象是独立分布的,直接概率相乘即可)
P(M3/N1)= (现象1不发生的概率 * 现象2不发生的概率 * 现象3发生的概率 * 现象4不发生的概率 * 现象5不发生的概率),即1 * (1-50/100) * 33/100 * (1-25/100) * 1 = 0.12375
P(M3/N2)=(现象1不发生的概率* 现象2不发生的概率* 现象3发生的概率* 现象4不发生的概率* 现象5不发生的概率)=(1-30/100)* 1* 35/100 *1 *1= 0.245
最后
P(M3 N1) =0.6 *0.12375=0.07425
P(M3 N2)=0.4 * 0.245=0.098
这两个概率相加要为1,进行归一化:
P(M3 N1) =0.07425/(0.07425+0.098)= 0.4310595
P(M3 N2)=0.098/(0.07425+0.098)=0.56894
P(M3 N3)=0
AC代码:

#include<iostream>
#include<algorithm>
#include<iomanip>
#include<vector>
using namespace std;
int N,M,K;
const int CN=105;
double y[CN];
double yx[CN][CN];
int x[CN];
typedef pair<int,double>PID; 
bool cmp(PID a,PID b)
{
	if(a.second>b.second) return true;
	else if(a.second==b.second)  
	{
		if(a.first<b.first) return true;
	}
	return false;
} 
vector<PID>ans;  //输出结果,排序后输出 
int main()
{
	cin>>N>>M;
	for(int i=1;i<=N;i++)  cin>>y[i];
	for(int i=1;i<=N;i++)
	{
		for(int j=1;j<=M;j++) cin>>yx[i][j];
	}
	cin>>K;
	for(int i=1;i<=K;i++) 
	{
		int t;cin>>t;x[t]=1;  //做标记不存储 
	}
	double sum=0;
	for(int i=1;i<=N;i++)
	{
		bool have=true;
		for(int j=1;j<=M;j++)
		{
			if(x[j]&&yx[i][j]==0) 
			{
				have=false;break;
			}
		}
		if(!have) y[i]=0;
		sum+=y[i];
	}
	
	//求P(Ni),归一化
	for(int i=1;i<=N;i++) 
	{
		if(sum!=0) y[i]/=sum;
	}
	sum=0;
	
	//求P(Mj/Ni)
	 for(int i=1;i<=N;i++)
	{
		if(!y[i]) continue;
		double tmp=1;
		for(int j=1;j<=M;j++) 
		{
			if(yx[i][j]==0) tmp*=1;
			//有这个现象 
			else if(x[j])   tmp*=yx[i][j]/100;
			else  tmp*=(1-yx[i][j]/100) ;
		}
		y[i]=tmp*y[i];
		sum+=y[i];
	}
	
	for(int i=1;i<=N;i++)
	{
		if(sum!=0) y[i]/=sum; 
		ans.push_back({i,y[i]});
	}
	sort(ans.begin(),ans.end(),cmp);
	for(int i=0;i<ans.size();i++)
	{
		cout<<ans[i].first<<" "<<fixed<<setprecision(2)<<ans[i].second*100<<endl;
	}
	return 0;
}

题目六:H题 机房

  1. 用简单的求最短路径的方法,比如dijstra、floyd、SPFA等由于是多次查询,属于是多源最短路径,数据达到100000,最快的SPFA求多源最短路径的时间复杂度是O(KE)*V,都会超时,因此这些方法都不行。
  2. 因此采用先求最近公共祖先,然后利用公式来求两个结点之间的最短距离的方法来求,求最近公共祖先有多种方法,包括向上标记法,倍增法和tarjan离线算法,这道题也就属于是模板题了
    向上标记法最坏的情况下,时间复杂度为O(n*m),会超时
    倍增法,时间复杂度为O(nlogn+mlogn),不会超时
    tarjan离线算法,时间复杂度为O(n+m),不会超时
    实际上这三个算法是求树里面的任意两个结点的最近公共祖先,进而根据公式求出两个结点间的最短路径。但图是特殊的树,将图中的任意一个结点作为根节点,从而将图转化为树,遍历与当前结点相连接的子节点时将自定义的父节点排除在外就行。后两者算法差不多,对于这道题的时间耗费都差不多。
    求LCA的详细讲解,见博客:https://blog.csdn.net/m0_58642116/article/details/128550161?spm=1001.2014.3001.5501
  3. 数据达到100000,最好是用scanf和printf进行输入输出,减小时间损耗

倍增法AC代码:

#include<iostream>
#include<vector>
#include<queue>
#include<string.h> 
#include<stdio.h>
using namespace std;
const int N=100005,INF=0x3f3f3f3f;
int n,m,x,y;
typedef pair<int,int>PII;
vector<int>tr[N];   //邻接表
queue<int>que;
int depth[N],dist[N],degree[N];
int fa[N][16];
void bfs(int s)
{
	dist[s]=degree[s];
	memset(depth,INF,sizeof(depth));
	depth[0]=0;depth[s]=1;
	que.push(s);
	while(!que.empty())
	{
		int u=que.front();
		que.pop();
		for(int i=0;i<tr[u].size();i++)
		{
			int j=tr[u][i];
			if(depth[j]==INF)
			{
				depth[j]=depth[u]+1;
				dist[j]=dist[u]+degree[j];
				que.push(j);
				fa[j][0]=u;
				for(int k=1;k<=15;k++)
				{
					fa[j][k]=fa[fa[j][k-1]][k-1];
				}
			}
		}
	}
}

int lca(int x,int y)
{
	if(depth[x]<depth[y]) swap(x,y);
	for(int k=15;k>=0;k--)
	{
		if(depth[fa[x][k]]>=depth[y])
		{
			x=fa[x][k];
		}
	}
	
	if(x==y) return x;
	
	for(int k=15;k>=0;k--)
	{
		if(fa[x][k]!=fa[y][k])
		{
			x=fa[x][k];
			y=fa[y][k];
		}
	}
	return fa[x][0];
}
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<n;i++)
	{
		scanf("%d %d",&x,&y);
		tr[x].push_back(y);
		tr[y].push_back(x);
		degree[x]++;
		degree[y]++;
	}
	bfs(1);
	for(int i=0;i<m;i++)
	{
		scanf("%d %d",&x,&y);
		if(x==y) printf("%d\n",degree[x]);
		else
		{
			int p=lca(x,y);
			printf("%d\n",dist[x]+dist[y]-2*dist[p]+degree[p]);
		}
	}
	return 0;
} 

tarjan算法AC代码:

#include<iostream>
#include<vector>
#include<queue>
#include<string.h> 
using namespace std;
const int N=100005,INF=0x3f3f3f3f;
int n,m,x,y;
typedef pair<int,int>PII;
vector<int>tr[N];   //邻接表
vector<PII>query[N];   //存储查询 ,first存与下标index相关查询的另一个结点值,second存询问编号用于存储结果 
int p[N];          //并查集
int ans[N],dist[N],st[N],degree[N];
void dfs(int u,int fa)
{
	for(int i=0;i<tr[u].size();i++)
	{
		int j=tr[u][i];
		if(j==fa) continue;    //遍历与u相连的除了u的父亲以外的结点
		dist[j]=dist[u]+ degree[j];
		dfs(j,u);
	}
}
int find(int j)
{
	if(p[j]!=j) p[j]=find(p[j]);   //往上查找的同时合并并查集 
	return p[j];
}
void tarjan(int u)
{
	st[u]=1;    //正在遍历
	for(int i=0;i<tr[u].size();i++)
	{
		int j=tr[u][i];
		if(!st[j])   //没有遍历过 
		{
			tarjan(j);
			p[j]=u;   //遍历完了一个子节点 
		}
	}
	
	//子树都遍历完,回溯到u结点,求与u相关的查询
	for(int i=0;i<query[u].size();i++)
	{
		int j=query[u][i].first;
		if(st[j]==2)   //只有已经遍历完并回溯完的才能求值 
		{
			int lca=find(j);   //找到j所属并查集的祖先,进行合并O(1) 
			int id=query[u][i].second;
			ans[id]=dist[u]+dist[j]-2*dist[lca]+degree[lca];
		}
	}
	st[u]=2;  //遍历并回溯完了 
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<n;i++)
	{
		cin>>x>>y;
		tr[x].push_back(y);
		tr[y].push_back(x);
		degree[x]++;
		degree[y]++;
	}
	for(int i=0;i<m;i++)
	{
		cin>>x>>y;
		if(x==y) ans[i]=degree[x];
		else
		{
			query[x].push_back({y,i});
			query[y].push_back({x,i});
		}
	}
	
	for(int i=1;i<=n;i++) p[i]=i;
	
	dist[1]=degree[1];
	dfs(1,-1);  //随便令一个为根节点,求每个点到根节点的距离,根节点的父节点就令为-1 
	tarjan(1);
	for(int i=0;i<m;i++)
	{
		cout<<ans[i]<<endl;
	} 
	return 0;
} 

题目七:I题 齿轮

  1. 根据相邻齿轮的线速度相同,可以推出第一个齿轮和最后一个齿轮的线速度也是相同的,则有
    w1 * r1 = wn * rn ,则wn=w1*r1/rn =w1 * qi
    因此只需要每次询问时遍历所有齿轮,查看是否存在两个齿轮使得商为qi,这样的时间复杂度会是O(Q * n *n)绝对超时;
  2. 采用离线标记,先遍历所有的齿轮半径,将这些半径可能组成的商标记一下,这样在询问的时候直接看该qi是否被标记就可以了,离线标记时有两种方法:
    第一种对于每个齿轮半径,判断该半径值的所有倍数是否为齿轮半径,如果是就将他俩的商标记(有些题解的思路)
    标记代码:
    //标记数组中任意两个数的商值 
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=a[i];j<=N;j+=a[i])   //N根据题目数据范围最大为200005;
    		{
    			if(st[j]) flag[j/a[i]]=1;
    		}
    	}
    
    但是分析代码这种标记方法是会超时的,考虑最坏情况,所有齿轮半径为1,那么a[i]就等于1,外层最大2e5,内层每次j+1,为2e5,最终耗时2e5 * 2e5,肯定会超时的。就算所有半径不是1而是2,最终耗时也是2e5*2e5/2,还是会超时,因此网上有些题解在dotcpp上过不去。
    第二种对于每个齿轮半径,判断该半径值的所有约数是否为齿轮半径,如果是就将他俩的商标记
    标记代码:
    //标记数组中任意两个数的商值,分解每个数的约数,将在数组中的约数标记为1就行 
    	for(int i=1;i<=n;i++)
    	{
    		int up=sqrt(a[i]);
    		
    		for(int j=1;j<=up;j++)
    		{
    			if(a[i]%j==0) 
    			{
    				//j在数组里,则商a[i]/j就标记为1 
    				if(st[j])  flag[a[i]/j]=1;
    				if(st[a[i]/j])   flag[j]=1;
    			}
    		}
    	}
    
    标记约束的最坏时间为2e5*sqrt(2e5)=1e7左右,不会超时

完整AC代码:

#include<iostream>
#include<stdio.h>
#include<math.h>
using namespace std;
const int N=200005;
int n,q,t;
int st[N];
int a[N],flag[N];   //flag存当前数组中的数能构成哪些商值,作为qi 
int main()
{
	scanf("%d %d",&n,&q);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&t);
		st[t]=1;
		a[i]=t;
	}
	//标记数组中任意两个数的商值,分解每个数的约数,将在数组中的约数标记为1就行 
	for(int i=1;i<=n;i++)
	{
		int up=sqrt(a[i]);
		
		for(int j=1;j<=up;j++)
		{
			if(a[i]%j==0) 
			{
				//j在数组里,则商a[i]/j就标记为1 
				if(st[j])  flag[a[i]/j]=1;
				if(st[a[i]/j])   flag[j]=1;
			}
		}
	}
	for(int i=0;i<q;i++)
	{
		scanf("%d",&t);
		if(flag[t]) printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}

题目八:J题 搬砖

  1. 读题就能感觉出来是个0/1背包的问题,只不过约束不是背包的容量,而是上面i-1个砖块的重量不能超过第i个砖块的价值
  2. 于是定义dp[i][j] 就表示前i个砖块组成的塔重量为j的最大价值
    假设前i个砖块组成的塔的重量为j,则前i-1个砖块组成的塔的重量就为j-wi,并且这前i-1个砖块组成的塔的重量j-wi还不能超过vi,实际就是加个判断就行
  3. 然后就是该按照什么顺序来枚举砖块?简单的0/1背包问题中每个物品之间是独立的,都只是受限于背包的容量,因此他们每个物品之间没有直接制约关系,但是这道题的砖块相互间有制约关系,因此要考虑选择砖块的顺序,实际上这就是要排序。这题我们可以这样强制定义让i在上面,j在下面,让wi+sum<vj(满足题意可采纳),wj+sum>vi(不满足题意不能采纳),也就是让i在上面的时候j可以选,让i在下面的时候j不可以选了。转化一下这个公式,可以把sum丢掉,sum是上面所有的重量。然后公式就是wi<vj,vi<wj。两个想加可以得到wi+vi<wj+vj。所以按这个排序就行。
  4. 这是一个经典的01背包+贪心的问题,贪心的排序处理原因也可以参考博客:
    https://blog.csdn.net/m0_58642116/article/details/128647612?spm=1001.2014.3001.5501
    AC代码:
#include<iostream>
#include<vector>
#include<algorithm>
#include<map>
using namespace std;
const int N=1005,M=20005;
struct Node
{
	int w;
	int v;
}a[N];

int n,m,w,v,dp[N][M],ans=0;    //dp[i][j]表示前i个物品中选出来重量为j的最大价值 
bool cmp(Node a,Node b)
{
	return a.w+a.v<b.w+b.v;   //注意是要加起来,而不是直接按照w排序 
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++) 
	{
		cin>>a[i].w>>a[i].v; m+=a[i].w;
	}
	
	sort(a+1,a+n+1,cmp);
	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			dp[i][j]=dp[i-1][j];
			if(j>=a[i].w&&(j-a[i].w<=a[i].v))     //加个判断,限制一下前i-1个物品的重量就行了 
			{
				dp[i][j]=max(dp[i][j],dp[i-1][j-a[i].w]+a[i].v);
			}
		}
	}
	for(int i=1;i<=m;i++)  ans=max(ans,dp[n][i]);
	cout<<ans;
	return 0;
} 

进行一下空间优化:

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1005,M=20005;
struct Node
{
	int w;
	int v;
}a[N];
int n,w,v,dp[M],ans=0;    //dp[i][j]表示前i个物品中选出来重量为j的最大价值 
bool cmp(Node a,Node b)
{
	return a.w+a.v<b.w+b.v;   //注意是要加起来,而不是直接按照w排序 
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++) 
	{
		cin>>a[i].w>>a[i].v; 
	}
	sort(a+1,a+n+1,cmp);
	
	for(int i=1;i<=n;i++)
	{
		for(int j=a[i].w+a[i].v;j>=a[i].w;j--)
		{
			dp[j]=max(dp[j],dp[j-a[i].w]+a[i].v);
			ans=max(ans,dp[j]);
		}
	}
	cout<<ans;
	return 0;
} 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值