ZUFE题解系列 DP(1) 砝码称重+顺序对齐+任务安排+最大的算式+筷子

版权声明:虽然我依旧蒟蒻,但请你尊重我 :D   ——陈杉菜 https://blog.csdn.net/qq_44702847/article/details/89281512

[1] 6890 砝码称重

题目链接:http://acm.ocrosoft.com/problem.php?cid=1629&pid=21
题目描述

设有1g,2g,3g,5g,10g,20g的砝码各若干枚(其总重≤1000g),编程求出能称出重量的个数。

输入

a1 a2 a3 a4 a5 a6(分别表示1g砝码有a1个,2g砝码有a2个,…20g砝码有a6个)输出
一行:一个整数(表示用这些砝码能称出的不同重量的个数,但不包括一个砝码也不用的情况)

样例输入

1 1 0 0 0 0

样例输出

3

思路:

加一个判断数组和计数器,计算题目里的最大重量不超过1000,所以开大小为1001的数组满足条件
w[6]表示6个不同重量的砝码,c[6]表示6个不同砝码相应的数量

#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<ctype.h>
#include<vector>
#include<map>
#include<set>
#include<queue> 
#include<iomanip>
 
typedef long long ll;
using namespace std;
    int w[6]={1,2,3,5,10,20};
    int c[6]={0};
    int visit[1001]={0}; 
     
int Count(){
     int i=0;
     int j=0;
     int total=0;
     int count=0;
     visit[0]=w[0]*c[0];
     for(i = 1;i<=c[0];i++)
                  visit[w[0]*i] = 1;//初始化visit[1~c[0]],表示已经添加了砝码1
          for(i = 1;i<6;i++)
          {
                  int m = visit[0];
                  for(int k = 1;k<=c[i];k++)
                  {
                          for(j = 0;j<=m;j++)
                          {
                                  if(j+ k*w[i]>1000)
                                          break;
                                   if(visit[j] == 1 && visit[j + k*w[i]] != 1 || j==0)
                                   {
                                          visit[j + k*w[i]] = 1;
                                          total = j+k*w[i];
                                   }
                          }
                  }
                  visit[0] = total;
          }
          for(i = 1;i<=1000;i++)
          {
            if(visit[i]==1) //visit[j]=1表示,重量为j的情况已经存在,否则表示重量为j的情况还未出现
                count ++;
          }
          return count;
}
int main()
{
     for(int i=0;i<6;i++){
        cin>>c[i];
     }
     int count =Count();
     cout<<count<<endl;
}


[2] 6891 顺序对齐

题目链接:http://acm.ocrosoft.com/problem.php?cid=1629&pid=22
题目描述

考虑两个字符串右对齐的最佳解法。例如,有一个右对齐方案中字符串是AADDEFGGHC和ADCDEGH。
AAD_DEFGGHC
 ADCDE__GH_

每一个数值匹配的位置值2分,一段连续的空格值-1分。所以总分是匹配点的2倍减去连续空格的段数,在上述给定的例子中,6个位置(A,D,D,E,G,H)匹配,三段空格,所以得分2*6+(-1)*3=9,注意,我们并不处罚左边的不匹配位置。若匹配的位置是两个不同的字符,则既不得分也不失分。
请你写个程序找出最佳右对齐方案。

输入

每笔测资包含两行,每行一个字符串,最长50个字符。字符全部是大字字母。

输出

每笔测资输出一行,为最佳对齐得分。

样例输入

AADDEFGGHC
ADCDEGH

样例输出

9

思路

大声BB:这题目是不是有错别字,当时emm也就WA十几次吧,,,,
f[i][j]表示匹配到i,j时的最大价值。
很明显,a[i]=b[j]时,f[i][j]=f[i-1][j-1]+2
a[i]!=b[j]时,可以直接放在后面,也可以加空格,
加空格的话,要枚举加空格的位置(不一定就是当前位置最好),还有要判断向哪个字符串加空格。

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 110
using namespace std;
int n,m,f[N][N];
char a[N],b[N];
int main(){
	while(cin>>a>>b){//多组数据,有几次wa的点在这
					//注意审题,(虽然题面是错别字)
		for(int i=0;i<N;i++)
			for(int j=0;j<N;j++)
				f[i][j]=0;//初始化,也可以直接用memset
		n=strlen(a);m=strlen(b);
	    for(int i=1;i<=n;i++)
	        for(int j=1;j<=m;j++){
	            if(a[i-1]==b[j-1]) f[i][j]=f[i-1][j-1]+2;//空格加在哪个字符串里
				//因为两个都有可能,就不判断了,直接来两个循环看哪个加空格的效果更好
	            for(int k=1;k<i;k++)//枚举空格的位置
	                f[i][j]=max(f[i][j],f[k][j]-1);
	            for(int k=1;k<j;k++)
	                f[i][j]=max(f[i][j],f[i][k]-1);
	            f[i][j]=max(f[i][j],f[i-1][j-1]);
	        }
	    printf("%d\n",f[n][m]);//!这题wa了十几次居然就是因为没有加回车符,学到老活到老
	}
    return 0;
}
//AADDEFGGHC
//ADCDEGH


[3] 6892 任务安排

题目链接:http://acm.ocrosoft.com/problem.php?cid=1629&pid=23
题目描述

N个任务排成一个序列在一台机器上等待完成(顺序不得改变),这N个任务被分成若干批,每批包含相邻的若干任务。从时刻0开始,这些任务被分批加工,第i个任务单独完成所需的时间是Ti。在每批任务开始前,机器需要启动时间S,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。每个任务的费用是它的完成时刻乘以一个费用系数Fi。请确定一个分组方案,使得总费用最小。
例如:S=1;T={1,3,4,2,1};F={3,2,3,3,4}。如果分组方案是{1,2}、{3}、{4,5},则完成时间分别为{5,5,10,14,14},费用C={15,10,30,42,56},总费用就是153。

输入

第一行是N(1<=N<=5000)。
第二行是S(0<=S<=50)。
下面N行每行有一对数,分别为Ti和Fi,均为不大于100的正整数,表示第i个任务单独完成所需的时间是Ti及其费用系数Fi。

输出

一个整数,最小的总费用。

样例输入

5
1
1 3
3 2
4 3
2 3
1 4

样例输出

153

思路

当前每一步的决策都会对以后的决策造成影响,也就是说,讨论到当前这步时答案最优,那就将会更新答案,所以在计算时就考虑进去以后的计算,即用f[i]表示(完成工作1到i的费用)+(因增加了S导致的后面的工作增加的费用)的总和的最小值。将输入的数组用前缀和记录,分组j+1-i个人,不断进行判断,最后的答案就是f【i】;

#include<iostream>
#include<cstdio> 
using namespace std;
int F[5005],s,t[5005],n;// t[i]表示工作1到i的时间的累加(前缀和);F[i]表示工作1到i的费用系数的累加(前缀和)。
int f[5005];
//5
//1
//1 3
//3 2
//4 3
//2 3
//1 4
int main(){
	cin>>n>>s;
	int i,j,x,y;
	for(i=1;i<=n;i++){
		cin>>x>>y;
		t[i]=t[i-1]+x;
		F[i]=F[i-1]+y;
		f[i]=100010;
	}
	for(i=1;i<=n;i++){
		for(j=0;j<=i;j++){// j用来分组,表示把工作j+1到工作i分到同一组中。
			f[i]=min(f[i],f[j]+t[i]*(F[i]-F[j])+s*(F[n]-F[j]));
			// t[i]*(f[i]-f[j]) 表示完成j+1到i这一组工作的费用
			//s*(f[n]-f[j])表示因为增加了一次开机,导致从工作j+1到工作n增加的费用
			//两者取较小值
		}
	}
	cout<<f[n];
    return 0;
}

[4] 6893 最大的算式

题目链接:https://zufeoj.com/problem.php?cid=1629&pid=24
题目描述

题目很简单,给出N个数字,不改变它们的相对位置,在中间加入K个乘号和N-K-1个加号,(括号随便加)使最终结果尽量大。因为乘号和加号一共就是N-1个了,所以恰好每两个相邻数字之间都有一个符号。例如:
N=5, K=2,5个数字分别为1、2、3、4、5,可以加成:
12(3+4+5)=24
1*(2+3)(4+5)=45
(1
2+3)*(4+5)=45
……

输入

输入文件共有二行,第一行为两个有空格隔开的整数,表示N和K,其中(2<=N<=15, 0<=K<=N-1)。第二行为 N个用空格隔开的数字(每个数字在0到9之间)。

输出

输出文件仅一行包含一个整数,表示要求的最大的结果

样例输入

5 2
1 2 3 4 5

样例输出

120

思路

这题抱的队友大腿,嗯,,,很粗
dp[i][j]表示在前i个数中插入j个乘号所能达到的最大运算和.详细见代码

#include<bits/stdc++.h>
using namespace std;
long long dp[16][16] = {0};   //dp[i][j]表示前i个数中有j个乘号时,所得最大值
int sum[16] = {0};    //sum[i]表示前i个数之和
int main()
{
    int N, K, i = 1, j, k, t;
    scanf("%d %d", &N, &K);
    int num[16];
    for (; i <= N; i++)
    {
        scanf("%d", &num[i]);
        sum[i] = sum[i - 1] + num[i];
    }
    //如果没有乘号的情况/连加情况
    for (i = 1; i <= N; i++)
    {
        dp[i][0] = sum[i];
    }
    //dp
    for (i = 2; i <= N; i++)
    {
        for (j = 1; j <=i-1; j++)//最大有i-1个*号
        {
            for (k = 2; k <= i; k++)    //k为这个乘号的位置(第k个数)
            {
                dp[i][j] = max(dp[i][j], dp[k - 1][j - 1] * (sum[i] - sum[k - 1])); 
                //求前i个数有j个乘号的情况中最大的情况
                //假设j个乘号中最后一个乘号的位置是第t个数和第t+1个数之间,则若要取最大值,须
				//把第t个数之后的数加在一起再乘上dp[t][j-1],由此得出状态转移方程dp[i][j]=max(dp[t][j-1]*(sum[i]-sum[t])),
            }
        }
    }
    printf("%lld\n", dp[N][K]);
    return 0;
}

[5] 6894 筷子

题目链接:http://acm.ocrosoft.com/problem.php?cid=1629&pid=25
题目描述

A先生有很多双筷子。确切的说应该是很多根,因为筷子的长度不一,很难判断出哪两根是一双的。这天,A先生家里来了K个客人,A先生留下他们吃晚饭。加上A先生,A夫人和他们的孩子小A,共K+3个人。每人需要用一双筷子。A先生只好清理了一下筷子,共N根,长度为T1,T2,T3,……,TN.现在他想用这些筷子组合成K+3双,使每双的筷子长度差的平方和最小。(怎么不是和最小??这要去问A先生了,呵呵)

输入

共有两行,第一行为两个用空格隔开的整数,表示N,K(1≤N≤100, 0<K<50),第二行共有N个用空格隔开的整数,为Ti.每个整数为1~50之间的数。

输出

仅一行。如果凑不齐K+3双,输出-1,否则输出长度差平方和的最小值。

样例输入

10 1
1 1 2 3 3 3 4 6 10 20

样例输出
5

思路
先排序,这里直接用sort安排下,简单粗暴,然后初始化
f[i][j]表示i根筷子里面拿j双的最小平方和,然后一直更新最小值就得到答案了
动态转移方程(就是更新最小值的方法):f[i][j]=min(f[i-2][j-1]+(a[i]-a[i-1])*(a[i]-a[i-1]),f[i-1][j]);

#include<iostream>
#include<cstdio> 
#include<cstring>
#include<algorithm>
using namespace std;
int main(){
	int a[101],f[101][101]={0};
	int k,i,j,n,t;
	cin>>n>>k;
	k=k+3;
	for(i=1;i<=n;i++){
		cin>>a[i];
	}
	
	//升序排序
	sort(a+1,a+i);

	//判断筷子是否足够
	if(k*2>n)cout<<-1<<endl;
	
	//初始化
	else{
		for(i=0;i<=n;i++){
			f[i][0]=0;
			for(j=1;j<=k;j++)f[i][j]=100010;
		}
		for(i=2;i<=n;i++){//一双筷子最少2根,所以从2开始算起
			for(j=1;j<=k;j++){//更新最小值
				f[i][j]=min(f[i-2][j-1]+(a[i]-a[i-1])*(a[i]-a[i-1]),f[i-1][j]);
			}
		}
		cout<<f[n][k]<<endl;
	}
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_44702847/article/details/89281512