191023-dp练习
T1 洛谷 砝码称重
题解
代码
#include<bits/stdc++.h>//背包
using namespace std;
const int b[6]={1,2,3,5,10,20};
int a[10],f[1001],ans;
int main()
{
for(int i=0;i<=5;i++)
scanf("%d",&a[i]);
f[0]=1;//初始化 使重量为0的情况为已有情况(即成立)
for(int i=0;i<=5;i++)// 第一层循环枚举砝码种类
for(int j=1;j<=a[i];j++)//第二层循环枚举每种砝码个数
for(int k=1000;k>=0;k--)//第三层循环 如果重量k成立(即能被称量出来)那么k+b[i]也成立
if(f[k]) //注意不能 for(int k=0;k<=1000;k++) 否则一个砝码会被多次使用(变成完全背包问题)
f[k+b[i]]=1;
for(int i=1;i<=1000;i++)
if(f[i]) ans++;
printf("Total=%d",ans);
return 0;
}
T2 洛谷 栈
题解
代码
#include<bits/stdc++.h> //递归转递推 递推做法
using namespace std;
long long f[20][20],n;//i表示已经进栈的数量,j表示已经出栈的数量 ,f[i,j]表示此种情况的方案数
int main()
{
scanf("%lld",&n);
for(int i=0;i<=n;i++)//可知定义的 f[i,j]中 i=0 时这个数组的值都为1,同时,这也是递推边界。
f[0][i]=1;//并且,我们用 i表示队列里的数,j表示出栈数,f[i,j]表示情况数;
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
{
if(i==j) f[i][j]=f[i-1][j];
else f[i][j]=f[i-1][j]+f[i][j-1];//当 i个数进栈,j-1个数出栈的时候,只要再出一个数,便是i个数进栈,j个数出栈的情况
//同理,对于进栈 i-1个数,出栈 j个数,再进栈一个数便是f[i,j]了
}//于是就有了递归式:f[i,j]=f[i-1,j]+f[i,j-1]
printf("%lld",f[n][n]);
return 0;
}
T3 花匠
其他详细解析
1
2
代码
#include<iostream>
#include<cstdio>
#define II int
#define R register
#define I 123456
using namespace std;
II a[I],d_1[I],d_2[I];
II n;
int main()
{
scanf("%d",&n);
R II x;
for(R II i=1;i<=n;i++) scanf("%d",&x), a[i]=x;
d_1[1]=d_2[1]=1;
//d_1[]代表的是当前元素是以当前元素为中心的三个相邻的元素中最大的;
//同理,d_2[]代表的是当前元素是以当前元素为中心的三个相邻的元素中最小的;
//但是当前元素不一定选,可能是继承上一个元素的信息;
for(R II i=2;i<=n;i++)
{
if(a[i]>a[i-1]) d_1[i]=max(d_1[i-1],d_2[i-1]+1), d_2[i]=d_2[i-1];
//如果当前元素大于这个前一个元素;
//那么当前元素若果选,则是前一个元素作为最低点时的长度+1;
//如果不选,就继承前一个元素作为最高点,等价于当前元素作为最高点;
//当前元素作为最低点就只能继承前一个点作为最低点;
if(a[i]<a[i-1]) d_1[i]=d_1[i-1], d_2[i]=max(d_1[i-1]+1,d_2[i-1]);
//如果当前元素小于前一个元素,则道理同上,只是反过来;
if(a[i]==a[i-1]) d_1[i]=d_1[i-1], d_2[i]=d_2[i-1];
//如果当前元素等于前一个元素,那么这个元素直接继承前一个元素的所有信息;
//因为这两个点是完全等价的;
}
}
R II ans=max(d_1[n],d_2[n]);
//我们在最后时取两种状态的最大值作为答案;
cout<<ans<<endl;
return 0;
}
T4 摆花
代码
#include<bits/stdc++.h>
using namespace std;
int f[200][200],a[201],n,m;//f[i,j]中 i表示当前摆放到第i种花,j表示当前摆放到了第i个盆,f[i][j]表示此种情况的最大方案数;
int main()
{
scanf("%d%d",&n,&m);
f[0][0]=1;//初始值f[0][0]=1;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)//第一层循环枚举当前摆放到第i种花
for(int j=0;j<=a[i];j++)// 第二层循环枚举当前第i种花 所需的盆数j
for(int k=m;k>=j;k--)//对于第i种花可以使用0、1...a[i]盆,对应的前i-1种花摆放的盆数为j-0、j-1、j-2、...j-a[i],
f[i][k]=(f[i][k]+f[i-1][k-j])%1000007;//即f[i][j]=f[i-1][j]+f[i-1][j-1]+f[i-1][j-2]+...+f[i-1][j-a[i]] (j>a[i])
printf("%d",f[n][m]);
return 0;
}
T5 乌龟棋
代码
#include<bits/stdc++.h>
using namespace std;
int n,m,b[10009],a[5],f[50][50][50][50];
int main()
{
int x;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
for(int i=1;i<=m;i++)
{
scanf("%d",&x);
a[x]++;
}
f[0][0][0][0]=b[1];//初始值为起点的分值;
for(int i=0;i<=a[1];i++)
for(int j=0;j<=a[2];j++)
for(int k=0;k<=a[3];k++)
for(int v=0;v<=a[4];v++)
{
int vis=1+i+2*j+3*k+4*v; //注意要加上起点的1;
if(i) f[i][j][k][v]=max(f[i][j][k][v],f[i-1][j][k][v]+b[vis]);
if(j) f[i][j][k][v]=max(f[i][j][k][v],f[i][j-1][k][v]+b[vis]);
if(k) f[i][j][k][v]=max(f[i][j][k][v],f[i][j][k-1][v]+b[vis]);
if(v) f[i][j][k][v]=max(f[i][j][k][v],f[i][j][k][v-1]+b[vis]);
//f[i][j][k][v]+=b[vis] 不能写在这里,必须写在上面;
}
printf("%d",f[a[1]][a[2]][a[3]][a[4]]);
return 0;
}
T6 合唱队形
分析
求出每个点左侧的最大不下降序列和右侧的最大不上升序列
再全部扫一遍,取最大值。
代码
#include<bits/stdc++.h>
using namespace std;
int a[1009],f1[1009],f2[1009],maxx,n;
bool bj[1009];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
f1[i]=1;
f2[i]=1;
}
for(int i=2;i<=n;i++)//i左侧的最长上升子序列(包含i)
{
maxx=0;
for(int j=1;j<=i-1;j++)
if(a[j]<a[i]&&f1[j]>maxx)
maxx=f1[j];
f1[i]+=maxx;
}
for(int i=n-1;i>=1;i--)//i右侧的最长下降子序列(包含i)
{
maxx=0;
for(int j=i+1;j<=n;j++)
if(a[j]<a[i]&&f2[j]>maxx)
maxx=f2[j];
f2[i]+=maxx;
}
maxx=0;
for(int i=1;i<=n;i++)
maxx=max(maxx,f1[i]+f2[i]-1);
printf("%d",n-maxx);
return 0;
}
T7 石子归并
代码(针对小数据 n^2)石子归并1
#include<bits/stdc++.h>//这类问题一般先枚举长度(而不是起始位置和初始位置)
using namespace std;//(GarsiaWachs算法专门解决石子合并类似问题 https://blog.csdn.net/lycheng1215/article/details/73290279)
int n,a[1009],f2[1009][1009],s[1009],f1[1009][1009],ans1=-10,ans2=(1<<30);
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[n+i]=a[i];
}
for(int i=1;i<=2*n;i++)//2*n 对环的处理
{
s[i]=s[i-1]+a[i];//前缀和
f1[i][i]=0;
f2[i][i]=0;
}
for(int l=2;l<=n;l++)//阶段:合并堆数
for(int i=1;i<=2*n-l+1;i++)//状态:合并的起始位置
{
int j=i+l-1;//合并的结束位置
f1[i][j]=0;
f2[i][j]=(1<<30);
for(int k=i;k<j;k++)//枚举中间位置 (k必须取到i,因为有一种情况为左边一堆石子与右边所有合成的一堆石子合并,而j可取可不取)
{
f1[i][j]=max(f1[i][j],f1[i][k]+f1[k+1][j]);//决策
f2[i][j]=min(f2[i][j],f2[i][k]+f2[k+1][j]);
}
f1[i][j]+=(s[j]-s[i-1]);
f2[i][j]+=(s[j]-s[i-1]);
}
for(int i=1;i<=n;i++)
{
ans1=max(f1[i][i+n-1],ans1);
ans2=min(f2[i][i+n-1],ans2);
}
printf("%d\n%d",ans2,ans1);
return 0;
}
补充:对于环的处理方法
方法 1
由于石子围成一个圈,因此我们可以枚举分开的位置,首先将这个圈转化为链, 由于要枚举 n 次,所以时间复杂度为:O(n4)。
方法 2
我们可以将这条链延长 2 倍,扩展成 2n-1 堆,其中第 1 堆与 n+1 堆完全相同, 第 i 堆与 n+i 堆完全相同,这样我们只要对这 2n 堆动态规划后,枚举: f[1][n],f[2][n+1],…,f[n][2n-1)取最优值即可。 时间复杂度为 O(8n3),如下图:
代码(针对大数据 n*logn GarsiaWachs算法)
石子归并2
#include<bits/stdc++.h>
using namespace std;
long long ans,n;
vector<int> l;
inline int read()
{
int i=0; char c=getchar();
while(c>'9'||c<'0')c=getchar();
while(c>='0'&&c<='9')i=i*10+c-'0',c=getchar();
return i;
}
int merge()
{
int k=l.size()-1-1;//如果我们在A[0]到A[n-3]找不到A[k]<=A[k+2],那么k的值应该为n-2
for(int i=0;i<l.size()-2;++i)
{
if(l[i]<=l[i+2])
{
k=i;
break;
}
}
int tmp=l[k]+l[k+1];
l.erase(l.begin()+k);
l.erase(l.begin()+k);//删除
int in=-1;
for(int i=k-1;i>=0;--i) //从右往左找第一个
{
if(l[i]>tmp)
{
in=i;
break;
}
}
l.insert(l.begin()+in+1,tmp);//因为是在后面插入,所以要+1
return tmp;
}
int main()
{
cin>>n;
for(int i=1; i<=n;++i)
l.push_back(read());
for(int i=0;i<n-1;i++)
ans+=merge();
cout<<ans;
return 0;
}
T8 能量项链(类似于石子合并)
代码
#include<bits/stdc++.h>//与石子合并类似,只是不能用前缀和
using namespace std;
int n,vis;
struct zb
{
int x,y;
}a[1009];
long long ans,f[1009][1009];
int main()
{
int x;
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&x);
a[i].x=a[i+n].x=a[(i-1+n)%n].y=a[((i-1+n)%n)+n].y=x;
}
for(int l=2;l<=n;l++)
for(int i=0;i<n*2-l+1;i++)
{
int j=i+l-1;
for(int k=i;k<j;k++)
f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+a[i].x*a[k].y*a[j].y);
}
for(int i=0;i<n;i++)
ans=max(ans,f[i][i+n-1]);
printf("%lld",ans);
return 0;
}
T9 跳房子(二分答案+单调队列dp验证)
50分代码(会t)
#include<bits/stdc++.h>//时间复杂度 n*nlogn(50分)
using namespace std;
int f[100009],lmin,rmax,d[500009],n,k,dis,g[500009],maxxx=-1000,l,ri;
bool bj;
bool check(int r)
{
memset(f,0,sizeof(f));
if(dis<=r) lmin=1,rmax=dis+r;
if(dis>r) lmin=dis-r,rmax=dis+r;
for(int i=1;i<=d[n];i++)
{
f[i]=1-(1<<30);
for(int j=lmin;j<=rmax;j++)
{
if(i-j<0) continue;
if(!g[i-j]&&i!=j) continue;
f[i]=max(f[i],f[i-j]+g[i]);
}
if(f[i]>=k) return true;
}
return false;
}
int main()
{
int x;
scanf("%d%d%d",&n,&dis,&k);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&d[i],&x);
g[d[i]]=x;
maxxx=max(maxxx,abs(d[i]-d[i-1]));
}
if(maxxx>dis) l=maxxx-dis;
else l=0;
ri=100009;
while(l<ri)
{
int mid=(l+ri)/2;
if(check(mid))
{
bj=1;
ri=mid;
}
else
l=mid+1;
}
if(!bj) printf("-1\n");
else printf("%d\n",ri);
return 0;
}
正解
#include<bits/stdc++.h>
using namespace std;
long long f[500090],a[500090][2],n,d,k,ok,lmin,rmax;
bool check(int g)
{
lmin = d-g;
rmax = d+g;
if(lmin<=0)
lmin = 1;
memset(f,128,sizeof(f));
f[0]=0;
for(int i=1; i<=n; i++)
for(int j=i-1; j>=0; j--)
{
if(a[i][0]-a[j][0]<lmin) continue;
if(a[i][0]-a[j][0]>rmax) break;
f[i]=max(f[i],f[j]+a[i][1]);
if(f[i]>=k)
return 1;
}
return 0;
}
int main()
{
int i,ans=-1,l,r,m;
scanf("%lld%lld%lld",&n,&d,&k);
for(i=1; i<=n; i++)
scanf("%lld%lld",&a[i][0],&a[i][1]);
l=0, r=1005;//数据有点水
m=(l+r)/2;
while(l<=r)
{
if(check(m))
{
ans=m;
r=m-1;
}
else
l=m+1;
m=(l+r)/2;
}
printf("%lld\n",ans);
return 0;
}
T10 守望者的逃离(非传统dp)
代码
#include<bits/stdc++.h>//总的来说就是能闪则闪,闪烁在能闪时一定比跑的快;分批进行,判断哪个更快;
using namespace std;
int m,s,t,s2,s1;//s2存储闪现走的路程,s1存储走路走的路程。
int main()
{
scanf("%d%d%d",&m,&s,&t);
for(int i=1;i<=t;i++)//1s1s地推
{
s1+=17;
if(m>=10) m-=10,s2+=60; //蓝够
else m+=4;//蓝不够
if(s1<s2) s1=s2;//如果原来闪现的比走路的路程多,就把s1替换为s2;
if(s1>s)
{
printf("Yes\n%d\n",i);
return 0;
}
}
printf("No\n%d\n",s1);
return 0;
}