zgs说大家都爱考DP
于是我们就练DP
而且这些DP连升级版(树状啊,状压之类的)都不是
转换一下就是0/1背包
和昨天晚上的T1一样,也可以用回溯法
一下子跳到0/1背包的坑里去了——(i表示状态的总重,j表示到该状态的方案)
0/1背包的状移方程
f i , j = m a x { f i , j − 1 不 放 , f i − v i , j − 1 + w i 放 } f_{i,j}=max\{\mathop{f_{i,j-1}}\limits_{不放},\mathop{f_{i-v_i,j-1}+w_i}\limits_{放}\} fi,j=max{不放fi,j−1,放fi−vi,j−1+wi}
完全背包的状移方程
f i , j = m a x { f i , j 不 放 , f i − v i , j − 1 + w i 放 } f_{i,j}=max\{\mathop{f_{i,j}}\limits_{不放},\mathop{f_{i-v_i,j-1}+w_i}\limits_{放}\} fi,j=max{不放fi,j,放fi−vi,j−1+wi}
减不减1就是区别
关键来了 敲黑板
万一你放不进去呢
于是就有
f i , j = f i , j − 1 f_{i,j}=f_{i,j-1} fi,j=fi,j−1
0/1背包或完全背包都是这样
放上代码
#include
using namespace std;
#define in Read()
#define re register
#define NNN 30
#define NN 30010
int n,m;
int v[NNN],p,w[NNN];
int f[NN][NNN];
inline int in{
int i=0,f=1;char ch;
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
if(ch=='-'){
ch=getchar();f=-1;
}
while(ch>='0'&&ch<='9')i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
int main(){
// freopen("happy.in","r",stdin);
// freopen("happy.out","w",stdout);
n=in,m=in;
for(re int i=1;i<=m;i++)v[i]=in,p=in,w[i]=v[i]*p;
// for(re int i=1;i<=m;i++)printf("%d\n",w[i]);
for(re int i=1;i<=n;i++)
for(re int j=1;j<=m;j++){
if(i-v[j]>=0)
f[i][j]=max(f[i][j-1]/*不加*/,f[i-v[j]][j-1]+w[j]/*加*/);
else f[i][j]=f[i][j-1];
}
printf("%d\n",f[n][m]);
// for(re int i=1;i<=n;i++)
// printf("%d ",f[i][m]);
return 0;
}
然后偷懒粘一个zgs的DFS
#include
using namespace std;
int n,m,s,MAX,i,v[30],w[30];
void serch(int n,int s,int k) //当前剩余总金额n,当前乘积总和s,到第k个物品
{
for(int i=0;i<=1;i++) //第k种物品选与不选
{
if(i==1 && n-v[k]>0) //如果k种物品可以选
{
n=n-v[k];
s=s+v[k]*w[k];
if(s>MAX) MAX=s; //记录此时的最大值
}
if(k<m) serch(n,s,k+1);
}
}
int main()
{
freopen("happy.in","r",stdin);
freopen("happy.out","w",stdout);
scanf("%d %d",&n,&m); //读入允许的总金额n和物品数量m
for (i=1;i<=n;i++)
scanf("%d%d",&v[i],&w[i]); // 读入每个物品的价格v[i]和重要度w[i]
s=0;MAX=0;
serch(n,s,1);
cout<<MAX<<endl;
}
我竟然想出了正解!!!好激动哦
自认为我的代码比zgs的好看一些
#include
using namespace std;
#define in Read()
#define re register
#define NN 50
int n,m;
int f[NN/*传的次数*/][NN/*人的编号*/];
inline int in{
int i=0,f=1;char ch;
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
if(ch=='-'){
ch=getchar();f=-1;
}
while(ch>='0'&&ch<='9')i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
int main(){
// freopen("ball.in","r",stdin);
// freopen("ball.out","w",stdout);
n=in,m=in;
f[0][0]=1;
for(re int i=1;i<=m;i++)
for(re int j=0;j<n;j++){
int j1=j-1,j2=j+1;
if(j1==-1)j1=n-1;
if(j2==n)j2=0;
f[i][j]=max(f[i-1][j1]+f[i-1][j2],f[i][j]);
}
printf("%d",f[m][0]);
return 0;
}
spojP129
没那么复杂——不过就是一个最长不上升子序列长度+最长不下降子序列长度-1
复习一下DP求最长上升子序列(上升跟不下降有区别的哦~)
对于每一个数字,与其前的每一个数字比较
找到合法的,且目前构成的最长上升子序列最长的
把这个数拼到它后面
小心最后求的是从整个队列里面去掉的人
所以要用n减去所有选出来的构成“峰”的人
最后-1,注意一下容斥原理——中间那个人被算了2次
sol
#include
using namespace std;
#define re register
#define in Read()
#define NNN 110
int n,t[NNN];
int f[NNN],g[NNN];
inline int in{
int i=0,f=1;char ch;
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')f=-1,ch=getchar();
while(ch>='0'&&ch<='9')i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
inline void Get(){
for(re int i=1;i<=n;i++)
for(re int j=1;j<i;j++)
if(t[i]>t[j]&&f[i]<f[j]+1)
f[i]=f[j]+1;
for(re int i=n;i>=1;i--)
for(re int j=n;j>i;j--)
if(t[i]>t[j]&&g[i]<g[j]+1)
g[i]=g[j]+1;
}
int main(){
n=in;
for(re int i=1;i<=n;i++)t[i]=in;
Get();
int ans=99999;
for(re int i=1;i<=n;i++)
ans=min(ans,n-f[i]-g[i]-1);
printf("%d\n",ans);
// for(re int i=1;i<=n;i++)printf("%d ",f[i]);
// printf("\n");
// for(re int i=1;i<=n;i++)printf("%d ",g[i]);
return 0;
}
龟棋
我能过样例2不能过1的代码竟然给了我10分
然而记录每一个状态的数组要用5维
f i , x 1 , x 2 , x 3 , x 4 f_{i,x1,x2,x3,x4} fi,x1,x2,x3,x4
于是空间复杂度 O ( N × 4 0 4 ) O(N\times 40^4) O(N×404),要炸
给出两个优化
1
由于 x 4 = ( i − x 1 − x 2 − x 3 ) ÷ 4 x_4=(i-x_1-x_2-x_3)\div4 x4=(i−x1−x2−x3)÷4
那么可以省掉 x 4 x_4 x4一维
于是空间复杂度简化为 O ( N × 4 0 3 ) O(N\times 40_3) O(N×403)
2
利用滚动数组
因为每个i都与i-1,i-2,i-3,i-4有关
然鹅我不会滚动数组
所以使用优化1就是介各亚子的啦
本代码是以前推后的方法
#include
using namespace std;
#define re register
#define in Read()
#define NNN 41
#define NN 400
int n,m,tmp;
int f[NN][NNN][NNN][NNN],p[NN],cnt[5];
inline int in{
int i=0,f=1;char ch;
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')f=-1,ch=getchar();
while(ch>='0'&&ch<='9')i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
int main(){
n=in,m=in;
for(re int i=1;i<=n;i++)p[i]=in;
for(re int i=1;i<=m;i++)tmp=in,cnt[tmp]++;
f[1][0][0][0]=p[1];
for(re int i=1;i<=n;i++){
for(re int x1=0;x1<=cnt[1];x1++)
for(re int x2=0;x2<=cnt[2];x2++)
for(re int x3=0;x3<=cnt[3];x3++){
int x4=(i-x1-x2*2-x3*3)/4;
int k=f[i][x1][x2][x3];
if(i+1<=n&&x1<cnt[1])f[i+1][x1+1][x2][x3]=max(f[i+1][x1+1][x2][x3],k+p[i+1]);
if(i+2<=n&&x2<cnt[2])f[i+2][x1][x2+1][x3]=max(f[i+2][x1][x2+1][x3],k+p[i+2]);
if(i+3<=n&&x3<cnt[3])f[i+3][x1][x2][x3+1]=max(f[i+3][x1][x2][x3+1],k+p[i+3]);
if(i+4<=n&&x4<cnt[4])f[i+4][x1][x2][x3]=max(f[i+4][x1][x2][x3],k+p[i+4]);
}
}
printf("%d\n",f[n][cnt[1]][cnt[2]][cnt[3]]);
return 0;
}
不要越界不要越界不要越界!!!
每一个xn都要枚完,以便于求出x4
每个状态防止越界
枚举每个点,可以不枚举终点因为前面的点已经可以达到它了
下面给出到后瞻前的代码
相比于前面的方法
同样要枚完每个xn
一样要防越界,因为我们在瞻前
可以不枚举起点
#include
using namespace std;
#define re register
#define in Read()
#define NNN 41
#define NN 400
int n,m,tmp;
int f[NN][NNN][NNN][NNN],p[NN],cnt[5];
inline int in{
int i=0,f=1;char ch;
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')f=-1,ch=getchar();
while(ch>='0'&&ch<='9')i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
int main(){
n=in,m=in;
for(re int i=1;i<=n;i++)p[i]=in;
for(re int i=1;i<=m;i++)tmp=in,cnt[tmp]++;
f[1][0][0][0]=p[1];
for(re int i=1;i<=n;i++){
for(re int x1=0;x1<=cnt[1];x1++)
for(re int x2=0;x2<=cnt[2];x2++)
for(re int x3=0;x3<=cnt[3];x3++){
int x4=(i-x1-x2*2-x3*3)/4;
if(i-1>=0&&x1>0)f[i][x1][x2][x3]=max(f[i][x1][x2][x3],f[i-1][x1-1][x2][x3]+p[i]);
if(i-2>=0&&x2>0)f[i][x1][x2][x3]=max(f[i][x1][x2][x3],f[i-2][x1][x2-1][x3]+p[i]);
if(i-3>=0&&x3>0)f[i][x1][x2][x3]=max(f[i][x1][x2][x3],f[i-3][x1][x2][x3-1]+p[i]);
if(i-4>=0&&x4>0)f[i][x1][x2][x3]=max(f[i][x1][x2][x3],f[i-4][x1][x2][x3]+p[i]);
}
}
printf("%d\n",f[n][cnt[1]][cnt[2]][cnt[3]]);
return 0;
}