原题链接 https://www.luogu.com.cn/problem/P2725
题目大意
给你 m 个数,你可以从中任选不超过 n 个数(每个数可以重复选择),求最大的 k 使得 1~k 内的所有数都能被表示;
题解
70pts:
既然让求每个数能否被表示,那么我们可以顺着它的思路来设状态:
dp [ i ][ k ]:用 j 个数能否表达 k;
考虑怎么转移:
我们枚举所有的数 a [ j ],如果说 i - a [ j ] 能用 k-1 个数来表达,那么我们再加上 a [ j ] 这个数就实现了用 k 个数表达 i;
给出状态转移方程的代码:
for(int i=1;i<=n*maxn;i++) //maxn是最大面值再+1 { for(int j=1;j<=m;j++) { if(i-a[j]<0) continue; for(int k=1;k<=n;k++) { dp[i][k]|=dp[i-a[j]][k-1]; } } }
考虑到空间复杂度 O ( nm * 最大面值 ),会 MLE 的;
我们可以考虑滚动数组优化!
我们看这个状态转移方程,可以发现更新 i 的时候只会用到 i - a [ j ] 的数据,而 a [ j ] 最大为 10000,所以我们可以将第一维降到 10000;
注意要随时清空之前的信息;
#include<iostream> #include<cstdio> using namespace std; int read() { char ch=getchar(); int a=0,x=1; while(ch<‘0‘||ch>‘9‘) { if(ch==‘-‘) x=-x; ch=getchar(); } while(ch>=‘0‘&&ch<=‘9‘) { a=(a<<1)+(a<<3)+(ch-‘0‘); ch=getchar(); } return a*x; } const int N=205; int n,m,maxn; int a[N]; bool dp[10005][55]; //dp[i][j]:能否用j张邮票表示i int main() { n=read();m=read(); for(int i=1;i<=m;i++) { a[i]=read(); maxn=max(maxn,a[i]); //求最大面值 } dp[0][0]=1; maxn++; //这里maxn要再+1,不然i与i-maxn不会同时出现的 for(int i=1;i<=n*maxn;i++) { int can=0; //这个数能否被表示 for(int k=0;k<=n;k++) //清空原有的信息 dp[i%maxn][k]=0; for(int j=1;j<=m;j++) //枚举每个数 { if(i-a[j]<0) continue; for(int k=1;k<=n;k++)//枚举用多少个数表示i { dp[i%maxn][k]|=dp[(i-a[j])%maxn][k-1]; //状态转移方程 can|=dp[i%maxn][k]; } } if(can==0) //如果当前数不能被表示,输出前一个数并结束程序 { printf("%d\n",i-1); return 0; } } return 0; }
100pts:
这 100pts 的做法就有点巧妙了。
一个显然贪心策略:
用尽量少的数来表示 i 更优。
举个例子:
假如我们可以用 3 个数,有 2 种不同的面值:1,2;
对于 3,我们可以将其表示为:3 = 1 + 1 + 1,这样的表示方法用了 3 个数;
但我们还可以这么表示:3 = 2 + 1,这样的表示方法只用了 2 个数;
如果我们采用第一种表示方法的话,我们再继续表示 4 的话就要用到 4 个数了,是不合法的;
而如果我们采用第二种表示方法的话,我们可以合法的表示了:4 = 2 + 1 + 1;
甚至有一个更优解:4 = 2 + 2;
所以说,用越少的数来表示一个数,就会留出更多的空间来表示后面的数;
那么,我们就无需去记录那些用很多数来表示的情况,只记录每个数最少能用几个数表示就好了;
状态设置
dp [ i ]:i 最少能用几个数表示;
状态转移
和上面的思路一样,只不过数组维护的东西改了而已;
dp[i]=min(dp[i],dp[i-a[j]]+1);
答案输出
如果一个数 i,最少表示 i 的数超过了 n 个,那么就说明 i 这个数无法被表示,我们输出 i-1 并结束程序;
那么,这个题就被我们转化成了完全背包问题;
Code:
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int read() { char ch=getchar(); int a=0,x=1; while(ch<‘0‘||ch>‘9‘) { if(ch==‘-‘) x=-x; ch=getchar(); } while(ch>=‘0‘&&ch<=‘9‘) { a=(a<<1)+(a<<3)+(ch-‘0‘); ch=getchar(); } return a*x; } const int N=205; int n,m; int a[N]; int dp[2000005]; //dp[i]:最少能用多少个数来表示i int main() { n=read();m=read(); for(int i=1;i<=m;i++) a[i]=read(); memset(dp,0x3f,sizeof(dp)); //初始化 dp[0]=0; //边界条件 for(int i=1;i<=n*10000;i++) //枚举的最大范围 { for(int j=1;j<=m;j++) if(i-a[j]>=0) dp[i]=min(dp[i],dp[i-a[j]]+1); //状态转移方程 if(dp[i]>n) //i这个数无法被表示 { printf("%d\n",i-1); return 0; } } return 0; }
原文地址:https://www.cnblogs.com/xcg123/p/12150488.html