目录
开心的金明
解析:
这道题给出n(总钱数)和m(物品个数),每个物品给出价钱和重要程度,求不超过总钱数的物品价格与重要度乘积的总和,即v1*p1+v2*p2+……。这乍一看是不是很像01背包问题。
01背包问题问的是给出一个背包的物品个数和总体积,每个物品给出所占体积及价值,求在不超过背包总体积的价值之和。
设f[i][j]表示选前i个物品花费j个金币的值。分析一下状态转移方程:这道题求的是最大值,所以是max,每个物品只有选或不选。不选:f[i][j]=f[i-1][j],选:f[i][j]=f[i-1][j-v[i]],所以状态转移方程就是f[i][j]=max(f[i-1][j], f[i-1][j-v[i]]*p[i]),答案就是f[n][m]
然后我们发现选前i个物品后的f是由选i-1物品所决定的,所以我们可以降为一维,则f[j]=max(f[j], f[j-v[i]]*p[i]),答案是f[m]
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long LL;
LL dp[30][30000];//前i个花费价格为j的最大
int n,m;
int v[30],p[30];
int main() {
cin>>n>>m;
for(int i=1; i<=m; i++)
cin>>v[i]>>p[i];
for(int i=1; i<=m; i++)
for(int j=1; j<=n; j++) {
if(j<v[i]) dp[i][j]=dp[i-1][j];
else dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+p[i]*v[i]);
}
cout<<dp[m][n];
return 0;
}
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long LL;
LL dp[30000];//前i个花费价格为j的最大
int n,m;
int v[30],p[30];
//快读,可以不用管
int read() {
char c=getchar();
int date=0,w=1;
while(c<'0' || c>'9') {
if(c=='-') w=-1;
c=getchar();
}
while(c>='0' && c<='9') {
date=date*10+(c-'0');
c=getchar();
}
return date*w;
}
int main() {
n=read(),m=read();
for(int i=1; i<=m; i++)
v[i]=read(),p[i]=read();
for(int i=1; i<=m; i++)
for(int j=n; j>=v[i]; j--)
dp[j]=max(dp[j],dp[j-v[i]]+v[i]*p[i]);
cout<<dp[n];
return 0;
}
榨取kkksc03
解析:
这道题与上面的题类似,不过这是一个二维费用流,上一题是体积限制,这题是金币和时间限制,所以这题是三维,可以压缩成二维。设f[i][j][k]表示选前i个物品金币为j时间为k时实现的愿望个数,不选的话:f[i-1][j][k],选的话:f[i-1][j-v[i]][k-w[i]],状态转移方程:f[i][j][k]=max(f[i-1][j][k],f[i-1][j-v[i]][k-w[i]]);压缩成二维就是去掉i那一维。
#include <bits/stdc++.h>
#define MOD 1000000007
#define INF 0x7f7f7f7f
using namespace std;
typedef long long LL;
int n,m,t;
int f[205][205],v[105],w[105];
int main() {
cin>>n>>m>>t;
for(int i=1; i<=n; i++)
cin>>v[i]>>w[i];
for(int i=1; i<=n; i++)
for(int j=m; j>=v[i]; j--)
for(int k=t; k>=w[i]; k--)
f[j][k]=max(f[j][k],f[j-v[i]][k-w[i]]+1);
cout<<f[m][t];
return 0;
}
合唱队形
解析:
题中给出了一堆数构成的无序数列,问我们至少去掉其中n-k个使得该数列符合给出的式子,我们暂且就认为该数列是一个类抛物线,且两个元素之间不能相等。下面的该数列指的是取出k个元素后的数列,ti是峰值且1 ≤ i ≤ k,所以ti这个峰值可以在最左边或者在最右边。当它在最左边的时候,该数列是严格单调递减数列;当它在最右边时,该数列是严格单调递增数列;当它在中间某个位置时,该数列先严格单调递增再严格单调递减。
我们用f[]和g[]来存储符合严格单增和严格单减的最多元素个数,用样例为例186 186 150 200 160 130 197 220,我们至少去掉4个元素才能使得它和题目的式子一样,哪4个?186 150 197 220.
我们使用f[]来存储符合严格单增的最多元素个数,f[1]当然等于1,做完for后,可以得到f[2] = 1;f[3] = 1;f[4] = 2 = f[1]+1 = f[2]+1 = f[3]+1:形成的严格单增数列有186 200,186 200,150 200 ,f[5] = 1;f[6] = 1;f[7] = 2 = f[1]+1 = f[2]+1 = f[3]+1 = f[5]+1 = f[6]+1:186 197,186 197,150 197,160 197,130 197;f[8] = 3 = f[4]+1 = f[7] + 1:186 200 220,186 197 220……从这里可以的出结论:设前面的元素是i,后面的元素是j,当a[i]<a[j]时,判定f[i]+1>=f[j](j这个元素可以被纳入数列中,所以需要判定时+1),如果成立则做,否则继续遍历。
我们用g[]来存储符合严格单减的最多元素个数,与上面同理推导,不过这里有个陷阱。也许很多人会说:上面是单增且后面所表示元素个数<=前面所表示元素个数+1(即f[i]+1>=f[j]),这里是单减且后面所表示元素个数<=前面所表示元素个数+1(即g[i]+1>=g[j]),那么是不是把上面的两重for挪到下面将小于改大于即可,理论上是可行的,这样的确可以得出前i个元素中符合严格单减的最大元素个数,可是这样做没有意义,为什么呢?
回到题目中去,它让我们求最少几位同学出列使得数列符合题目中的式子,我们求出当前数列前面严格单增的最多元素个数及后面严格单减的最多元素个数,相加后就可以得到符合题目中的式子的数列的最多元素个数k,n-k即是题目答案。假如单减这里顺着来,g[]表示的是前面的数从左往右严格单减,而题目需要的是后面的数从左往右严格单减(即从右往左严格单减),所以我们需要逆着来计算。
由于g[]是逆着来算,即g[i]是从第i个往后符合严格单调递减的最多元素个数,那么f[i]+g[i]就表示第1个到第i个符合严格单调递增的最多元素个数+第i个到第n个符合严格单调递减的最多元素个数,即题目中给到的式子,不过需要-1,因为第i个元素被算了两遍,所以f[i]+g[i]-1就是第i个元素为峰值的符合式子的元素个数。求n-k的最小值,只要保证k是最大值即可。
#include <bits/stdc++.h>
#define MOD 1000000007
#define INF 0x7f7f7f7f
using namespace std;
typedef long long LL;
int n,maxn;
int a[105],f[105],g[105];
int main() {
cin>>n;
for(int i=1; i<=n; i++) {
cin>>a[i];
f[i]=g[i]=1;
}
//从左往右单增(i右j左,a[i]为较大的)
for(int i=2; i<=n; i++)
for(int j=1; j<i; j++)
if(a[j]<a[i] && f[i]<=f[j]+1)
f[i]=f[j]+1;
//从左往右单减(i左j右,a[i]为较大的)(从右往左单增)
for(int i=n-1; i>=1; i--)
for(int j=n; j>i; j--)
if(a[i]>a[j] && g[i]<=g[j]+1)
g[i]=g[j]+1;
for(int i=1; i<=n; i++)
maxn=max(maxn,f[i]+g[i]-1);
cout<<n-maxn;
return 0;
}
导弹拦截
解析:
这道题有两个问,第一个问的知识点是最长不上升子序列,第二个问问的是最长上升子序列。我们可以直接用dp来做,不过这个复杂度是n^2,只能获得100分(我想应该不用给出dp的做法了)。要想获得200分,复杂度需要降为nlogn,那么我们就需要用到二分+贪心。(下面的分析是之前的博客里面的)具体证明过程
第一问:最长不上升子序列。如果小于等于low中最小的元素就进low,如果大于就找low中第一个小于等于n(需要进low的元素)的位置。由于这个是最长不上升子序列,所以从左到右是从大到小(左边最大,右边最小),第一个小于等于n应该是往小的找(右边),所以我们判定条件时尽量将n放在右边,也就是说low[mid] >= n,mid是中间的位置,n比小于等于的话就在右边,在右边就应该往右边找,也就是l = mid + 1。
第二问:最长上升子序列。如果大于low中最大的元素就进low,如果小于等于的话就找low中第一个大于n(需要进low的元素)的位置。由于这是最长上升子序列(严格单增),所以左小右大,我们需要找第一个大于n的值,应该往大的找(右边),所以我们判定条件时就应该将n置于右边,也就是low[mid] < n,mid是中间的位置,n比它大的话就在右边,在右边也就往右边找,即l = mid+1.
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define MOD 1000000009
using namespace std;
typedef long long LL;
int n,low[100010],a[100010],b[100010];
int ans,cnt;
int main() {
int s=0;
memset(low,0,sizeof(low));
while(scanf("%d",&n)!=EOF && n)
a[++s]=n;
ans=cnt=1;
low[1]=a[1];
for(int i=2; i<=s; i++) {
if(a[i]<=low[ans]) low[++ans]=a[i];
else {
int l=1,r=ans;
while(l<r) {
int mid=(l+r)/2;
if(low[mid]>=a[i])
l=mid+1;
else r=mid;
}
low[r]=a[i];
}
}
memset(low,0,sizeof(low));
low[1]=a[1];
for(int i=2; i<=s; i++) {
if(a[i]>low[cnt]) low[++cnt]=a[i];
else {
int l=1,r=cnt;
while(l<r) {
int mid=(l+r)/2;
if(low[mid]<a[i]) l=mid+1;
else r=mid;
}
low[r]=a[i];
}
}
cout<<ans<<endl<<cnt;
return 0;
}
纪念品分组
解析:
这题的考点是贪心+双指针。首先题目问的是最少分组数目,可以得到用贪心算法;其次,题目中说道一个礼盒中最多放两件物品,所以我们应该尽量在每个礼盒中放两个物品且不超过总价格的上上限,这里就是需要用到双指针。
这道题如何贪呢?放到同一个盒子里面的两个物品应该尽量大和尽量小的,或者放当前最大价格的物品进去,那么我们需要进行排序,排完后就开始双指针操作。用l指向第一个物品(价格最小的),用r指向最后一个物品(价格最大的),然后尽量将被指向的两个物品放到同一个盒子里。题目中的输入保证每个物品小于等于单个盒子价格的上上限。假如被指向的两个物品在上上限之内,那么就将该两个物品放进去,双指针往里面夹;假如被指向的两个物品大于上上限,那么我们就将最大价格的物品放进盒子里,r就需要更新。
#include <bits/stdc++.h>
#define MOD 1000000007
#define INF 0x3f3f3f3f
using namespace std;
typedef long long LL;
int n,w,a[100005];
LL s;
int main() {
cin>>w>>n;
for(int i=1; i<=n; i++)
cin>>a[i];
sort(a+1,a+1+n);
int l=1,r=n;
while(l<=r) {
if(l!=r && a[l]+a[r]<=w) {
s++;
l++;
r--;
}
else {
s++;
r--;
}
}
cout<<s;
return 0;
}
本人大一,功底较差,如有错误请指正,必将耐心听取。