第一题
题目要求两部分尽可能的相同,差值尽可能的小,这也就告诉我们,我们要尽可能的匹配sum/2,所以我们把sum/2作为总体积,对各个物体进行背包。
dp[sum/2]就是最接近一半的结果,我们用总和减去这个值,就得到了另一半。
【以下背包采用了01背包压维 *** 作】
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int v[1000],dp[50000*10];
int main()
{
int t;
cin>>t;
while(t--)
{
int sum=0,n,half;
memset(dp,0,sizeof(dp));
if(n==1)
{
int x;
cin>>x;
cout<>v[i];
sum+=v[i];
}
half=sum/2;
for(int i=1; i<=n; i++)
{
for(int j=half; j>=v[i]; j--)
{
dp[j]=max(dp[j],dp[j-v[i]]+v[i]);
}
}
cout<
第二题,是常见的优先队列贪心问题;
涉及知识点有,priority_queue,结构体重载运算符,贪心;
优先队列嵌套结构体时,有一个规律,我们想让某一个变量大的在上面,则我们结构体内部要写小于号
struct node
{
int pos,sum;
friend bool operator<(node a,node b)
{
return a.sum>b.sum;
}
};
将问题微观化,假如数列就只有一个,那么显然,前k个最小和就是各个元素
n==2时,我们在一个数列基础上进行扩展,对第二个数列b进行排序,首先,b[1]相对于同数列其他元素来说,肯定最优,因此我们对第一次筛选出的k个元素全部加b1,也就是a1+b1,a2+b1....
把它们全部放入优先队列,这样我们头部先取第一个,再进行扩展 *** 作;第一个一定是a1+b1,我们直接把它作为第一小即可。
可是,如果我们直接这样遍历,我们的第二个元素(a2+b1)不一定是第二小,那么谁会有可能取代它呢? a3+b1?肯定不是,之后更不是!a3+b2?更差了,之后不用在考虑。
是 a1+b2!是a1+b3!是之后种种,我们不知道其大小关系,我们先把a1+b2压入队列,能在紧接着第二次循环“浮上来”,那么它一定是第二小的,否则,我们还有最优的来承接,如果真的浮上来,那么再压入下一个即可。
也就是ax+b1变成ax+b2再放进队列中。
意思大概是,既然ax+b1能成为最优的情况,我们不防把ax次优的情况,也加入队列。
如果加入之后还能"浮上来“,再进行这种 *** 作,如果不能浮上来,那就对第二个进行扩展,以此类推,进行k次,一定能筛选出前k个最小的。
以上 *** 作对应【普及-提高】洛谷橙色难度,本题是其扩展,应该能达到较容易的绿题难度。
那么我们既然在两队数列基础上筛选出了前k小,不防把这新的前k小看成新的数列,对第三队数列再进行以上 *** 作。
#include
#include
#include
#include
#include
using namespace std;
struct node
{
int pos,sum;
friend bool operator<(node a,node b)
{
return a.sum>b.sum;
}
};
priority_queueq;
int val[1010][1010], bestsum[1010];
int main()
{
int k;
while(cin>>k)
{
for(int i=1;i<=k;i++)
{
for(int j=1;j<=k;j++)
{
cin>>val[i][j];
}
sort(val[i]+1,val[i]+1+k);//排序
}
for(int i=1;i<=k;i++)
{
bestsum[i]=val[1][i]; //一个数列时前k小
}
for(int i=2;i<=k;i++)
{
while(!q.empty()) //清空,防止上一步影响
{
q.pop();
}
for(int j=1;j<=k;j++)
{
struct node head;
head.pos=1;
head.sum=bestsum[j]+val[i][1]; //"加b1"
q.push(head);//入队
}
//筛选前k小元素
for(int j=1;j<=k;j++)
{
struct node now=q.top(); //头部
bestsum[j]=now.sum; //前小
q.pop(); //删头
struct node tail;
tail.pos=now.pos+1; //扩展操作
if(tail.pos<=k) //不要越界
{
tail.sum=now.sum-val[i][now.pos]+val[i][tail.pos]; //"去掉原来的b1,换成b2"
q.push(tail); //别管能不能浮上来,先压进去
}
}
}
for(int i=1;i<=k;i++)
{
cout<
第三题
设dp[i][j]为前i个唱片,且第i个播放j分钟时的最大歌曲数。
题目要求按照时间顺序来安排,那么我们直接顺序遍历即可。
对于每一首歌,只能放进一个唱片。
我们只需要在遍历一首歌时,枚举出放进各个唱片的情况即可。
枚举时,对于每一个唱片内部,我们倒序枚举时间,是为了防止重复放入一首歌。
对于每一个唱片,我们仍然倒序枚举,是为了防止一首歌放在了两个不同唱片。
一个新歌,可以仅仅放在唱片内部 f[m][j-t[i]]+1,也可以承接前i-1部唱片,利用当前时间全部放置这一首。
以
20 20 5 4 9 2 19 5 3 10 15 18 4 3 9 14 17 1 20 15 19 12 6
为例
放置第四首19时,第四部唱片之所以不是1而是4,是因为承接了前三部的放法,本首歌单独放在第四部唱片,其余歌曲全部放在前三唱片里面。
#include
#include
#include
#define maxn 21
using namespace std;
int N,T,M;
int f[110][110];
int t[110];
int max(int i,int j,int k)
{
i=max(i,j);
i=max(i,k);
return i;
}
int main()
{
cin>>N>>T>>M;
for(int i=1; i<=N; i++)
{
cin>>t[i];
}
for(int i=1; i<=N; i++)
{
for(int m=M; m>=1; m--)
{
for(int j=T; j>=t[i]; j--)
{
f[m][j]=max(f[m][j],f[m-1][T]+1,f[m][j-t[i]]+1);
}
}
}
cout<
;
第四题没读懂
第五题,压了一维
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int dp[100];
int main()
{
dp[0]=1;
for(int i=1; i<=50; i++)
{
for(int j=i; j<=50; j++)
{
dp[j]+=dp[j-i];
}
}
int n;
cin>>n;
cout<
第六题,dp[i]代表前i个餐馆能获得的最大利益,状态转移当且仅当二者距离大于k.
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int dis[1010],w[1010];
int dp[1010];
int main()
{
int t;
cin>>t;
while(t--)
{
int n,k;
cin>>n>>k;
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
{
cin>>dis[i];
}
for(int i=1;i<=n;i++)
{
cin>>w[i];
}
for(int i=1;i<=n;i++)
{
dp[i]=w[i];
for(int j=1;jk)
{
dp[i]=max(dp[i],dp[j]+w[i]);
}
}
}
cout<
第七题不适合用dp,可以采用深度优先搜索,枚举全部和为x的组合,而非排列,能大大减少时间,不会超时。
每凑成一次x,提取出全部元素,标记次数加1,一个数必须使用,当且仅当它和x出现的次数是相同的。
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int cnt[10000+10], temp[10000+10],timecnt=0,n, x, a[1000], book[1000];
void dfs(int step,int nowsum)
{
if(nowsum>x)
{
return ;
}
if(step>n)
{
return ;
}
if(nowsum==x)
{
timecnt++;
for(int i=1; i>n;
cin>>x;
for(int i=1; i<=n; i++)
{
cin>>a[i];
}
sort(a+1,a+1+n);
dfs(1,0);
int ans=0;
for(int i=1; i<=n; i++)
{
if(cnt[a[i]]==timecnt)
{
ans++;
}
}
if(ans==0)
{
cout<<0<
第八题是二维费用01背包,两维度均需倒序枚举,能使数量增加就放入。
注意体力不能为0,也就是这一维容量只能是M-1,最后找到了dp[N][M-1]代表使用最大体力和能量球所能得到的最大数目,但要求体力最小,我们可以遍历整个dp数组,找到和dp[N][M-1]一样数量的结果,取消耗血量的最小值,用M减去,就是剩下的。
但遍历i=N这一层就够了,其余情况,都不如这一种情况更优。
#include
#include
const int maxn=1010;
int N,M,K;
int n[maxn],m[maxn]; //储存消耗精灵球数量ni 和 体力值mi
int dp[maxn][maxn]; //动态规划数组
int main()
{
scanf("%d%d%d",&N,&M,&K);
for(int i=1; i<=K; i++)
{
scanf("%d%d",&n[i],&m[i]);
}
for(int i=1; i<=K; i++)
{
for(int j=N; j>=n[i]; j--)
{
for(int k=M-1; k>=m[i]; k--)
{
if(dp[j-n[i]][k-m[i]]+1>dp[j][k])
{
dp[j][k]=dp[j-n[i]][k-m[i]]+1;
}
}
}
}
int temp=M;
for(int i=0; i<=M-1; i++)
{
if(dp[N][M]==dp[N][i])
{
temp-=i;
break;
}
}
printf("%d %d\n",dp[N][M-1],temp);
return 0;
}
第九题
#include
#include
# include
# include
int dp1[100010],dp2[100010];
int a[100010];
using namespace std;
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
//dp1代表前i填卖出时获得的最大利润,本次可以卖也可以不卖
int min1=99999999;
memset(dp1,0,sizeof(dp1));
memset(dp2,0,sizeof(dp2));
for(int i=1;i<=n;i++)
{
cin>>a[i];
min1=min(min1,a[i]);
dp1[i]=max(dp1[i-1],a[i]-min1);
}
//dp2代表i开始后几天买入的最大利润,
int max1=-99999999;
for(int i=n;i>=1;i--)
{
max1=max(max1,a[i]);
dp2[i]=max(dp2[i+1],max1-a[i]);
}
int ans=-9999999999;
for(int i=1;i<=n;i++)
{
ans=max(ans,dp1[i]+dp2[i]);
}
cout<
第十题,每一个位置往前推,当且仅当推移位置之间是回文时才能状态转移
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
char ch[2000];
int len;
int f[2000];
bool check(int s, int e)
{
bool flag = true;
for (int i = s; i < s + (e - s + 1) / 2; i++)
{
if (ch[i] != ch[e - (i - s)])
{
flag = false;
break;
}
}
return flag;
}
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
cin >> ch;
len = strlen(ch);
for (int i = 1; i < len; i++)
f[i] = i;
for (int i = 1; i < len; i++)
for(int j=0; j<=i; j++)
{
if (check(j, i) && j == 0)
{
f[i] = 0;
break;
}
if (check(j, i))
f[i] = min(f[i], f[j - 1] + 1);
}
cout<
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)