191023-dp练习

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;
}
发布了26 篇原创文章 · 获赞 2 · 访问量 666

猜你喜欢

转载自blog.csdn.net/Daniel__d/article/details/102695106