623(动态规划入门练习题)

再次模拟赛爆炸。。。
在这里插入图片描述
倒数第三。。。

A

A
1.1 题目描述
给定一个 n 个点的树,在树上选出最多的点使得没有两个选出的点之间有连边。
1.2 输入格式
第一行一个整数 n。
接下来 n − 1 行,每行两个正整数 a, b,表示有一条无向边连接标号为 a, b 的两
个节点。
1.3 输出格式
输出一行一个整数,表示能够选出的最多的点数。
1.4 样例输入
5
1 2
2 3
1 4
4 5
1.5 样例输出
3
1.6 样例解释
只需要选择点 {1, 3, 5} 即可达到最优。
1.7 数据规模与约定
对于 40% 的测试数据满足 n ≤ 10。
对于 70% 的测试数据满足 n ≤ 1000。
对于 100% 的测试数据满足 n ≤ 100000。
1.8 资源限制
每个测试点空间限制 256MB,时间限制 1s。
共 10 个测试点,满分 100 分。
输入文件为 a.in输出文件为 a.out,提交源文件为 a.pas/c/cpp。

思路

  1. 正解:树上DP。
  2. 之前的一些想法:
    对于题A而言,我觉得可以是二分图的最大匹配来做。
    满足0元素:每个集合里没有连边;
    满足1元素:要求每个元素里的点数最大,那么也就是要求两个集合中有最大匹配。
    但是,似乎我好像忘了最大匹配怎么求。。。
    好像二分图的染色就可以诶。。。。
    那这样的话,dfs标记就可以诶。。。不知道dfs的复杂度是多少。。。
    染色大法好啊!!!!(但是只得到了40分)
    错因:对于一张图而言,有可能同时不选相邻的两个,而选择他们的子树,所以这个思路就挂了。
  3. 题解思路(如图所示)在这里插入图片描述

code

//Author:melody
#include<bits/stdc++.h>
using namespace std;

const int nn=100010;

int n;
int h[nn];
int vis[nn];
int f[nn][2];

inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}

struct edge
{
	int to,nex;
}e[2*nn];
int num=0;
int last[nn];

void add(int x,int y)
{
	e[++num].to=y; e[num].nex=last[x];
	last[x]=num;
}

void dp(int x)
{
	vis[x]=1;
	f[x][0]=0;
	f[x][1]=h[x];
	for(int i=last[x];i;i=e[i].nex)
	{
		int y=e[i].to;
		if(!vis[y])
		{
			dp(y);
			f[x][0]+=max(f[y][1],f[y][0]);
			f[x][1]+=f[y][0];
		}
	}
	return ;
}

int main()
{	
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	n=read();
	for(int i=1;i<=n;++i)	h[i]=1;
	for(int i=1;i<n;++i)
	{
		int a=read();
		int b=read();
		add(a,b); add(b,a);
	}
	dp(1);
	cout<<max(f[1][0],f[1][1])<<endl;
	return 0;
}

B

2.1 题目描述
给定 n 颗能量珠,第 i 个能量珠有能量值 vi,这 n 颗能量珠呈环状排列。
每次我们可以合并两个相邻的能量珠 i, j,并产生一颗新的能量珠,其能量为
vi + vj,同时在这个过程中会释放 vi + vj 的能量。
现在我们要将这 n 颗能量珠合并为 1 颗,求在这个过程中释放的总能量之和最
大是多少。
2.2 输入格式
第一行一个整数 n,表示能量珠的个数。
接下来一行 n 个整数,第 i 个整数表示第 i 颗能量珠的能量,保证对于 1 ≤ i < n,
第 i 颗能量珠和第 i + 1 颗能量珠在环上是相邻的,同时第 n 颗能量珠和第 1
颗能量珠在环上是相邻的。
2.3 输出格式
输出一行一个整数,表示将所有能量珠合并为一颗的过程中释放的总能量的最
大值。
2.4 样例输入
4
8 7 9 5
2.5 样例输出
69
2.6 样例解释
先合并 7, 9,再合并 16, 8,再合并 24, 5 就能得到答案了。
2.7 数据规模与约定
对于 10% 的数据,n = 1。
对于 20% 的数据,n ≤ 2。
对于 30% 的数据,n ≤ 3。
对于 50% 的数据,n ≤ 6。
对于 70% 的数据,n ≤ 50。
对于 100% 的数据,n ≤ 200,1 ≤ vi ≤ 100。
2.8 资源限制
每个测试点空间限制 256MB,时间限制 1s。
共 10 个测试点,满分 100 分。
输入文件 b.in输出文件 b.out,提交源程序 b.pas/c/cpp。

思路

  1. 正解:区间DP。
  2. 之前的一些想法:
    对于题B而言,部分分给的比较详细,我觉得可以从部分分突破。(准备拿满50分的暴力)(结果就真的是只有50分。。。)
  3. 题解思路(如图所示)【参考石子合并在这里插入图片描述

code

#include<bits/stdc++.h>
using namespace std;

const int nn=210;

int n,ans1=0;
int a[nn], b[nn];
int sum[nn];
int f[nn][nn];

inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}

int main()
{
	freopen("b.in","r",stdin);
	freopen("b.out","w",stdout);
	n=read();
	for(int i=1;i<=n;++i)	a[i]=read();
	
	for(int i=1;i<=n;++i)/*枚举从哪个点开始断*/
	{
		int cnt=i;
		memset(b,0,sizeof(b));
		memset(sum,0,sizeof(sum)); 
		memset(f,0,sizeof(f));/*求的是最大值*/
		for(int j=1;j<=n;++j)
		{
			b[j]=a[cnt],cnt++;
			sum[j]=sum[j-1]+b[j];
			if(cnt==n+1)	cnt=1;
		}
		for(int len=2;len<=n;++len)
			for(int l=1;l<=n-len+1;++l)
			{
				int r=l+len-1;
				for(int k=l;k<r;++k)
					f[l][r]=max(f[l][k]+f[k+1][r]+sum[r]-sum[l-1],f[l][r]);
			}
		ans1=max(ans1,f[1][n]);
	}
	cout<<ans1<<endl;
	return 0;
}

C

3.1 题目描述
给定 n 个水晶,第 i 个水晶的高度为 Hi。
现在要求将这些水晶排成一排,要求相邻两个水晶之间的高度差均大于 k,问
有多少种排法。
3.2 输入格式
第一行两个整数 n, k。
接下来 n 行,每行一个整数,第 i 行的整数表示 Hi。
3.3 输出格式
输出一行一个整数表示排法的种类数。
3.4 样例输入
4 1
3
4
2
1
3.5 样例输出
2
3.6 样例解释
合法的两种排列方式为:
3 1 4 2
2 4 1 3
排列方式 1 2 4 3 是不合法的是因为前两个水晶的高度之差不超过 k = 1。
3.7 数据规模与约定
对于 50% 的数据,满足 n ≤ 10。
对于 100% 的数据,满足 n ≤ 16, 1 ≤ Hi ≤ 25000, 1 ≤ k ≤ 3400,保证答案不超
过 1018。
3.8 资源限制
每个测试点空间限制 256MB,时间限制 1s。
共 10 个测试点,满分 100 分。
输入文件 c.in,输出文件 c.out,提交源程序 c.pas/c/cpp。

思路

  1. 正解:状压DP。
  2. 之前的一些想法:用dfs暴力枚举,结果只能过70分,之后就tle了。
  3. 题解思路(如图所示)在这里插入图片描述

code

#include<bits/stdc++.h>
using namespace std;

const int nn=20;

int n,k;
int h[nn];
vector<int> vis[nn];
long long f[1<<16][nn];

inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	n=read(); k=read();
	for(int i=0;i<n;++i)	h[i]=read(),f[1<<i][i]=1;
	for(int i=0;i<n;++i)
		for(int j=0;j<n;++j)
			if(abs(h[i]-h[j])>k)
				vis[i].push_back(j);/*错因:存图存了两遍*/
	for(int s=0;s<1<<n;++s)
		for(int i=0;i<n;++i)
			if((s>>i)&1)
				for(int k=0;k<vis[i].size();++k)
					if((s>>vis[i][k])&1)
						f[s][i]+=f[s-(1<<i)][vis[i][k]];
	long long ans=0;
	for(int i=0;i<n;++i)
		ans+=f[(1<<n)-1][i];
	cout<<ans<<endl;
	return 0;
}

按照王辉老师说的话,错误不可怕,要有越挫越勇的勇气,而不能被打趴下!!

Fighting!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值