///关于下标问题,当在计算时运用到i-1的时候,可以使用i从1开始,就没有越界的风险
///如果没有,一般从0开始比价好;
1.要想明白动态规划路线 ->第一步写出动态集合,第二步开始动态计算;
1-1 0-1背包问题:
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;//物品数量,最多载重;
int v[N],w[N];//物体的价值,重量;
int dp[N][N];//二维数组:表示存放第几个物品时,此时的总重量;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>w[i]>>v[i];
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)//重量从0-m;
{
dp[i][j]=dp[i-1][j];
if(j>=w[i])
dp[i][j]=max(dp[i][j],dp[i-1][j-w[i]]+v[i]);
}
}
cout<<dp[n][m]<<endl;
return 0;
}
1-1 0-1背包优化:一维数组
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;//物品数量,最多载重;
int v[N],w[N];//物体的价值,重量;
int dp[N];//二维数组:表示存放第几个物品时,此时的总重量;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>w[i]>>v[i];
for(int i=1;i<=n;i++)
for(int j=m;j>=w[i];j--)//重量从0-m;
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
cout<<dp[m]<<endl;
return 0;
}
1-3 0-1背包输出背包
///难点在于如何输出将哪个物品装入背包,可利用回溯法进行实现
///更加考研对背包和动态规划的理解
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n,m;
int v[N],w[N],f[N][N],x[N];///x[i]标记用过的背包;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>v[i];
for(int i=1;i<=n;i++)
cin>>w[i];
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
f[i][j]=f[i-1][j];
if(j>=w[i])
f[i][j]=max(f[i][j],f[i-1][j-w[i]]+v[i]);///普通0-1背包;
/// 每一个j都对应一个状态;后面回溯会根据这个状态来进行判断;
}
cout<<"Optimal value is"<<endl;
cout<<f[n][m]<<endl;
for(int i=n;i>=1;i--)///利用回溯,从后向前走;
{
if(f[i][m]!=f[i-1][m])///如果两者相等,也就是在i背包和i-1背包之间没有装任何物品;
{
m-=w[i];
x[i]=1;
}
}
for(int i=1;i<=n;i++)
cout<<x[i]<<" ";
return 0;
}
2-1 完全背包
与0-1背包不同,完全背包里物品可以多次使用,但大体思路相同;
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int dp[N][N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>w[i]>>v[i];
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k=0;k*w[i]<=j;k++)
dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]+k*v[i]);
cout<<dp[n][m]<<endl;
return 0;
}
2-2 完全背包优化:
1.根据分析,可以发现在循环时,如果是dp[i][j-w[i]]开头的话,后续的循环与dp[i][j]对比
两者的关系是dp[i][j]=dp[i][j-w[i]]+v[i];
故我们可以少一层k循环;
2.利用0-1背包思路,优化成一维数组.
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int dp[N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>w[i]>>v[i];
for(int i=1;i<=n;i++)
for(int j=w[i];j<=m;j++)///这里不需要逆序,因为0-1是看i-1的状态,而完全背包是i状态,不需要改变.
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
cout<<dp[m]<<endl;
return 0;
}
///看好0-1背包和完全背包问题非常的相似,想清楚这两者只差一行代码的区别在哪!!!!
3 多重背包问题-暴力
本质与完全背包问题没有区别,只是k循环最多循环到每个物品最高限制次,即s[i]次;
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int dp[N][N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>w[i]>>v[i];
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k=0;k<=s[i]&&k*v[i]<=j;k++)//就多出来一个判断条件;
dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]+k*v[i]);
cout<<dp[n][m]<<endl;
return 0;
}
3-2 多重背包问题-优化
详解://https://blog.csdn.net/weixin_72060925/article/details/128404378?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-128404378-blog-109363826.235%5Ev30%5Epc_relevant_default_base3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-128404378-blog-109363826.235%5Ev30%5Epc_relevant_default_base3&utm_relevant_index=2
#include<bits/stdc++.h>
using namespace std;
const int N=25000,M=1010;
int n,m;
int v[N],w[N];
int dp[N];
int main()
{
cin>>n>>m;
int cnt=0;
for(int i=1;i<=n;i++)
{
int a,b,s;
cin>>a>>b>>s;
int k=1;
while(k<=s)//使用二进制计数法,例如s=200,那么我们的k就可以循环到64,此时可以表示出1-128的所有数字
{ //那么剩下的72个怎么表示呢,剩下的72个
cnt++;
w[cnt]=k*a;
v[cnt]=k*b;
s-=k;
k*=2;
}
if(s>0)
{
cnt++;
w[cnt]=a*s;
v[cnt]=b*s;
}
}
n=cnt;//更新n的值,接下来就当0-1背包问题做就可以;
for(int i=1;i<=n;i++)
for(int j=m;j>=w[i];j--)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
cout<<dp[m]<<endl;
return 0;
}
4-1 多组背包
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,m;
int w[N][N],v[N][N],s[N];
int dp[N];
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
{
cin>>s[i];
for(int j=0;j<s[i];j++)
cin>>w[i][j]>>v[i][j];
}
for(int i=0;i<n;i++)
for(int j=m;j>0;j--)
for(int k=0;k<s[i];k++)
if(j>=w[i][k])
dp[j]=max(dp[j],dp[j-w[i][k]]+v[i][k]);//还是老方法,就是多了重循环哈
cout<<dp[m]<<endl;
return 0;
}
5- 依赖背包,存在主附,要买附就要现有主
//P1064 [NOIP2006 提高组] 金明的预算方案
//链接:https://www.luogu.com.cn/problem/P1064
///当时做这个题时,满脑子都是思路,一遍遍确认一遍遍不对,最后的大体思路是对了
///但没想到分情况讨论.
///依赖背包在附件少的情况下完全可以分情况讨论,等我再看看算法,应该有个通用方法.
#include<iostream>
using namespace std;
int m,n,mw[33333],mv[33333],fw[33333][3],fv[33333][3],f[33333],v,p,q;
//mw主件重量,mv主件价值,fw主件对应的附件重量,fv主副价值,n总重量,m总个数
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>v>>p>>q;
if(!q){//如果是主件
mw[i]=v;//主件重量
mv[i]=v*p;//主件价值与重量乘积
}
else{//如果是附件
fw[q][0]++;//记录主件的附件个数(只记录在fw就行,fv那里没用
fw[q][fw[q][0]]=v;//主件的个数是用来确定该附件应该填在第一个还是第二个格子里
fv[q][fw[q][0]]=v*p;//(是第一个还是第二个附件)
}
}
for(int i=1;i<=m;i++)
for(int j=n;j>=mw[i];j--){//01背包模板
//每一个if的前提是背包能不能装下该物品
//情况1:只要主件 和啥都不要比较
f[j]=max(f[j],f[j-mw[i]]+mv[i]);
//情况2:主件和附件1 和上面选出的较大值比较
if(j>=mw[i]+fw[i][1])f[j]=max(f[j],f[j-mw[i]-fw[i][1]]+mv[i]+fv[i][1]);
//情况3:主件和附件2 和上面选出的较大值比较
if(j>=mw[i]+fw[i][2])f[j]=max(f[j],f[j-mw[i]-fw[i][2]]+mv[i]+fv[i][2]);
//情况4:都要
if(j>=mw[i]+fw[i][1]+fw[i][2])
f[j]=max(f[j],f[j-mw[i]-fw[i][1]-fw[i][2]]+mv[i]+fv[i][1]+fv[i][2]);
}
//输出在价值为n时能得到的最大值
cout<<f[n]<<endl;
return 0;
}
///线性dp
6-数字三角形
#include<bits/stdc++.h>
#define INF -1e9
using namespace std;
const int N=505;
int n,a[N][N],f[N][N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin>>a[i][j];
for(int i=0;i<=n+1;i++)
for(int j=0;j<=i+1;j++)
f[i][j]=INF;
f[1][1]=a[1][1];//一定要记得从起点开始初始化,不然会变成负无穷
for(int i=2;i<=n;i++)//一定要从第二层开始,如果从第一层开始,第一次会访问到负无穷区域;
for(int j=1;j<=i;j++)
f[i][j]=max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j]);
int res=INF;
for(int i=1;i<=n;i++)
res=max(res,f[n][i]);
cout<<res;
return 0;
}
7-1 最长上升/下降 子序列
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n;
int a[N],f[N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
{
f[i]=1;//每个i位置初始都是1,没循环之前就a[i]一个数字
for(int j=1;j<i;j++)
if(a[i]>a[j])//如果i位置比之前的大
f[i]=max(f[i],f[j]+1);
}
int res=-1;
for(int i=1;i<=n;i++)
res=max(res,f[i]);
cout<<res;
}
7-2 最长子序列输出顺序
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n;
int a[N],g[N],f[N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
{
f[i]=1;
g[i]=0;
for(int j=1;j<i;j++)
if(f[i]<f[j]+1)
{
f[i]=f[j]+1;
g[i]=j;
}
}
int res=0,k=0;
for(int i=1;i<=n;i++)
if(f[i]>res)
{
res=f[i];
k=i;
}
cout<<res<<endl;
for(int i=0,len=f[k];i<len;i++)
{
cout<<a[k]<<' ';
k=g[k];
}
return 0;
}
7-3 最长上升子序列优化
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int res=0,x,a[N],q[N],q1[N],len=0;
int main()
{
while(cin>>x) a[res++]=x;
q[0]=2e9;
q1[0]=-2e9;
for(int i=0;i<res;i++)
{
if(q[len]>=a[i]) q[++len]=a[i];
else
{
int l=0,r=len;
while(l<=r)
{
int mid=l+r>>1;
if(q[mid]<a[i]) r=mid-1;
else l=mid+1;
}
q[l]=a[i];
}
}
cout<<len<<endl;//最长不上升
len=0;
for(int i=0;i<res;i++)
{
if(q1[len]<a[i]) q1[++len]=a[i];
else
{
int l=0,r=len;
while(l<=r)
{
int mid=l+r>>1;
if(q1[mid]>=a[i]) r=mid-1;
else l=mid+1;
}
q1[l]=a[i];
}
}
cout<<len;//最长上升
return 0;
}
8-1 最长公共子序列
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;
char a[N],b[N];
int f[N][N];
int main()
{
cin>>n>>m;
cin>>a+1>>b+1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
f[i][j]=max(f[i-1][j],f[i][j-1]);///状态转移说简单些就是做选择,比如说这个问题,是求 s1 和 s2 的最长公共子序列,不妨称这个子序列为 lcs.
///那么对于 s1 和 s2 中的每个字符,有什么选择?很简单,两种选择,要么在 lcs 中,要么不在。
///如果某个字符应该在 lcs 中,那么这个字符肯定同时存在于 s1 和 s2 中;
///如果 s1[i]==s2[j],那么这个字符一定在 lcs 中;否则的话,s1[i] 和 s2[j] 这两个字符至少有一个不在 lcs 中,需要丢弃一个.
///保留一个的原因是,i从小到大遍历,i后面有可能会出现与这时保留相同的字节
if(a[i]==b[j])
f[i][j]=max(f[i][j],f[i-1][j-1]+1);
}
cout<<f[n][m];
}
8-2 最长公共子序列优化: https://www.luogu.com.cn/problem/P1439
/// 运用离散化来解决问题;
///我们可以以第一个串为标准,用第二个串来匹配第一个串,看能匹配多少,所以,其实第一个串的每个数字其实影响不大,只有知道它对应了第二串的哪个数字就好了,那么我们为什么不把他给的串重新定义一下?
///比如他的样例:3 2 1 4 5 我们把他变成 1 2 3 4 5 用一个数组记录一下每个数字变成了什么,相当于离散化了一下3-1;2-2;1-3;4-4;5-5;
///现在我们的第二串1 2 3 4 5 按我们离散化的表示:3 2 1 4 5
///可能有些人已经懂了,我们把第一个串离散化后的数组是满足上升,反过来,满足上升的也就是满足原串的排列顺序的,(如果你不懂的话可以多看几遍这个例子)O(∩_∩)O~
///好了 ,现在的问题就变成了求一个最长不下降序列!好了!解决完成!
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n;
int a[N],b[N],belong[N+10],q[N],len=0;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
belong[a[i]]=i;
}
for(int i=1;i<=n;i++)
cin>>b[i];
q[0]=-2e9;
for(int i=1;i<=n;i++)
{
if(belong[b[i]]>q[len])
{
q[++len]=belong[b[i]];
continue;
}
else
{
int l=0,r=len;
while(l<r)
{
int mid=l+r>>1;
if(q[mid]<belong[b[i]]) l=mid+1;
else r=mid;
}
q[l]=belong[b[i]];
}
}
cout<<len;
return 0;
}
9-1 区间DP
石子合成问题://https://blog.csdn.net/qq_45069496/article/details/123159802?ops_request_misc=&request_id=&biz_id=102&utm_term=%E7%9F%B3%E5%AD%90%E5%90%88%E5%B9%B6%E9%97%AE%E9%A2%98%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-123159802.142^v86^control,239^v2^insert_chatgpt&spm=1018.2226.3001.4187
区间dp难点在于循环,个人认为
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n;
int s[N],f[N][N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>s[i];
for(int i=1;i<=n;i++)
s[i]+=s[i-1];
for(int len=2;len<=n;len++)
{
for(int i=1;i+len-1<=n;i++)
{
int j=i+len-1;
for(int k=i;k<j;k++)
f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
}
}
cout<<f[1][n];
return 0;
}
9-2 用单调队列实现动态规划,优化效率
#include<bits/stdc++.h>
using namespace std;
const int N=1000010;
int use[2*N],l,r,n,hh,tt,q[N],a[2*N],f[2*N],res=-2e9;
int main()
{
cin>>n>>l>>r;
for(int i=1;i<=2*n;i++)
f[i]=-1e9;
for(int i=l;i<=r;i++) use[i]=1;///标记,当i循环到l,就是第一步
for(int i=0;i<=n;i++)
cin>>a[i];
for(int i=0;i<=n;i++)
{
while(hh<=tt&&q[hh]<i-r+l) hh++;///检查是否出列
while(hh<=tt&&f[i]>f[q[tt]]) tt--;///队尾出列,维护最大值;
q[++tt]=i;
f[i+l]=f[q[hh]]+a[i+l];
if(use[i]) use[i+l]=1;///标记作用,
}
for(int i=n+1;i<=n+l;i++)
if(use[i]) res=max(res,f[i]);
cout<<res;
return 0;
}
/**
https://www.luogu.com.cn/problem/CF414B
本题考察思维,状态表示为f[i][j],i是当前的长度,j是最大的数
那么此时本题有两种说法,第一种:f[i][j]+=f[i-1][j|k],即k是j的因数
第二种方法是:f[i+1][j]+=f[i][k|j],即k是j的倍数;
第一种方法可以看出来,我们需要枚举出j所有的因数,时间复杂度较高
而第二种方法,我们则可以通过k+=j来实现
**/
#include<bits/stdc++.h>
using namespace std;
const int N=2020,mod=1000000007;
int n,k,f[N][N];
long long res;
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++) f[1][i]=1;
for(int i=1;i<=k-1;i++)
for(int j=1;j<=n;j++)
for(int k=j;k<=n;k+=j)
{
f[i+1][k]+=f[i][j];///这样一来每一次,k都是j的倍数,
f[i+1][k]%=mod;///每一步都要取模
}
for(int i=1;i<=n;i++)
{
res+=f[k][i];
res%=mod;
}
cout<<res;
return 0;
}
/**
https://www.luogu.com.cn/problem/P1077
芜~,这次状态想对了,状态转移方程也是差不多,但是呢,在处理k的时候出问题了
看了题解才会,唉,任重而道远啊;
**/
#include<bits/stdc++.h>
using namespace std;
const int N=2020,mod=1e6+7;
int n,m,f[N][N],a[N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
f[0][0]=1;
for(int j=0;j<=m;j++)
{
for(int i=1;i<=n;i++)
{
for(int k=0;k<=min(j,a[i]);k++)
f[i][j]=(f[i][j]+f[i-1][j-k])%mod;
}
}
cout<<f[n][m];
return 0;
}
/**
https://www.luogu.com.cn/problem/P1115
我无语啦,为啥就是要看题解,好好想想啊喂;
枚举每个从i开始,左到右的最长不上升子序列就OK了;
**/
#include<bits/stdc++.h>
using namespace std;
const int N=500;
int n,zuo[N],you[N],res,a[N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
zuo[i]=1;///初始化放在哪都可以;
you[i]=1;
}
for(int i=1;i<=n;i++)
for(int j=1;j<i;j++)
if(a[i]>a[j])
zuo[i]=max(zuo[i],zuo[j]+1);
for(int i=n;i>=1;i--)
for(int j=i+1;j<=n;j++)
if(a[i]>a[j])
you[i]=max(you[i],you[j]+1);
for(int i=1;i<=n;i++)
res=max(res,zuo[i]+you[i]-1);///我怎么感觉这是贪心(恼)
cout<<n-res;
return 0;
}
/**
https://www.luogu.com.cn/problem/P1510
气死我啦,又是背包问题,关键是还不会,哎哟,烦死了
估计今天状态不对
**/
#include<bits/stdc++.h>
using namespace std;
const int N=1000010;
int n,m,t,f[N],w[N],v[N];
int main()
{
cin>>m>>n>>t;
for(int i=1;i<=n;i++) cin>>w[i]>>v[i];
for(int i=1;i<=n;i++)
for(int j=t;j>=v[i];j--)///必须枚举体力,如果枚举体积的话,要开个sum,从sum开始遍历,会超时
f[j]=max(f[j],f[j-v[i]]+w[i]);
if(f[t]<m) cout<<"Impossible";
else
{
int x=t;
while(f[x]>=m) x--;///当第一个体积不大于m时,停止;
cout<<t-x-1;
}
return 0;
}
/**
https://www.luogu.com.cn/problem/P1018
还是经典动态规划,详细思路放代码里
**/
#include<bits/stdc++.h>
using namespace std;
const int N=2020;
int n,k;
long long f[N][N],a[N],s[N][N];///a表示存储每一个数字,s表示从i开始,往后数j个位置代表的数(例:123456,s[1][3]=123)
///f表示第i位时,局部最优解是什么,k表示用乘法的次数;
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
char c;
cin>>c;
a[i]=c-'0';
}
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
s[i][j]=s[i][j-1]*(long long)10+a[j];///首先表示出s数组,要存longlong
for(int i=1;i<=n;i++) f[i][0]=s[1][i];///数组初始化
for(int j=1;j<=k;j++)
for(int i=1;i<=n;i++)
for(int l=1;l<i;l++)
f[i][j]=max(f[i][j],f[l][j-1]*s[l+1][i]);///从l为1开始遍历,寻找局部最优解,再反馈给最优解;
cout<<f[n][k];
return 0;
}
/**
区间DP
狠狠的拷打
**/
///区间dp1 石子合并:https://www.luogu.com.cn/problem/P1775
///这个就是典型的区间dp啊,要知道区间dp是枚举的每个区间,区间是1的那就是本身了,就不需要再枚举了
///如果时至今日还不会的话再去csdn或者acwing上看看课程吧qaq
///不说了我要手打一遍了
#include<bits/stdc++.h>
using namespace std;
const int N=2020;
int n,a[N],s[N],f[N][N];///f[i][j]表示从i到j合并这个区间所用的最小值;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
s[i]=s[i-1]+a[i];///求前缀和,因为我们要枚举每个区间,而每个区间的总和在这里要预处理一下
}
for(int len=2;len<=n;len++)///枚举区间,因为len=1的时候就是自己搬自己,不消耗体力,没必要枚举;
for(int i=1;i+len-1<=n;i++)///区间左端点是i,区间右端点是i+len-1,所以右端点不能超过n
{
int l=i,r=i+len-1;
f[l][r]=1e9;///因为要求最小值,所以先把这个区间值赋的很大;
for(int k=l;k<r;k++)
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);///从l到k的合并区间+从k+1到r的合并区间,再加上合并这俩区间所需要的体力;
}
/**
这里补充一个方法,可以从len=1开始;
memset(f,0x3f,sizeof f);
for(int len=2;len<=n;len++)
for(int i=1;i+len-1<=n;i++)
{
int l=i,r=i+len-1;
if(len==1) f[l][r]=0;///这里不同;
else
for(int k=l;k<r;k++)
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
}
**/
cout<<f[1][n];///从1合并到n的最小值
return 0;
}
///区间dp2-合并果子加强版:https://www.luogu.com.cn/problem/P1880
///没什么说法,想象一下,把这个环拆开,然后再补一个从1到n的链,那么我们每次断开的时候是不是就是从断点到区间那个长度?
///经典具体说法请看acwing,我要手打代码了呜呜呜
#include<bits/stdc++.h>
using namespace std;
const int N=2020;
int n,s[N],f[N][N],g[N][N],w[N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>w[i];
w[i+n]=w[i];
}
for(int i=1;i<=2*n;i++) s[i]=s[i-1]+w[i];
for(int len=2;len<=n;len++)
for(int i=1;i+len-1<=2*n;i++)
{
int l=i,r=i+len-1;
f[l][r]=1e9,g[l][r]=-1e9;///注意,赋值一定要赋的足够大;
for(int k=l;k<r;k++)
{
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
g[l][r]=max(g[l][r],g[l][k]+g[k+1][r]+s[r]-s[l-1]);
}
}
int res=2e6,ans=-2e6;///记录最小值最大值;
for(int i=1;i<=n;i++)
{
res=min(res,f[i][i+n-1]);///枚举所有区间为n的,找出最小值;
ans=max(ans,g[i][i+n-1]);
}
cout<<res<<endl<<ans;
return 0;
}
/*
https://www.luogu.com.cn/problem/P1164
经典的dp问题,我真是服了,一定要多想想这种类型的;
一眼就是背包;
先定义状态,脑袋里不能只有二维状态;
f[i]表示有i元时的方案数;
那么就可以转换为经典的背包问题了;
*/
#include<bits/stdc++.h>
using namespace std;
const int N=2020;
int n,m,w[N],f[N],res;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i];
f[0]=1;
for(int i=1;i<=n;i++)
for(int j=m;j>=w[i];j--) f[j]+=f[j-w[i]];
cout<<f[m];
return 0;
}