week13-动态规划(四)

D-TT的苹果树

题目:

在大家的三连助攻下,TT 一举获得了超级多的猫咪,因此决定开一间猫咖,将快乐与大家一同分享。并且在开业的那一天,为了纪念这个日子,TT 在猫咖门口种了一棵苹果树。
一年后,苹果熟了,到了该摘苹果的日子了。
已知树上共有 N 个节点,每个节点对应一个快乐值为 w[i] 的苹果,为了可持续发展,TT 要求摘了某个苹果后,不能摘它父节点处的苹果。
TT 想要令快乐值总和尽可能地大,你们能帮帮他吗?

Input:

结点按 1~N 编号。
第一行为 N (1 ≤ N ≤ 6000) ,代表结点个数。
接下来 N 行分别代表每个结点上苹果的快乐值 w[i](-128 ≤ w[i] ≤ 127)。
接下来 N-1 行,每行两个数 L K,代表 K 是 L 的一个父节点。
输入有多组,以 0 0 结束。

Output:

每组数据输出一个整数,代表所选苹果快乐值总和的最大值。

Sample Input:

7
1
1
1
1
1
1
1
1 3
7 4
2 3
4 5
6 4
3 5
0 0

Sample Output:

5

题目分析:

因为存在父节点和子节点,所以数据结构是树,又因为要使得苹果快乐总值最多,所以利用动态规划思想(树型DP)。

  • 采用链式前向星存储树结构,定义dp[i][1/0]表示以i节点为子树所能到的最大苹果快乐值 ,dp[i][0]表示以i节点为子树且未选择i号节点,dp[i][1]表示以i节点为子树且选择了i号节点。
  • 首先根据输入找到根节点root,然后从根节点出发,递归枚举当前节点i到能直接到达的所有的子节点,每个节点都有选或者不选两种情况:当当前节点未被选择的时候,其子节点可以被选择也可以不被选择,但当当前节点被选择的时候,其子节点一定不能被选择。
  • 所以转移方程为:
    dp[x][0]+=max(dp[y][1],dp[y][0]);
    dp[x][1]+=dp[y][0];

    最后输出结果ans=max(dp[root][0],dp[root][1])即可。

代码:

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
const int maxn=6e4+5;
struct edge
{
	int to,next;
}e[maxn];

int head[maxn],tol;
int a[maxn];
int dp[maxn][2];//dp[i][1/0]表示以i节点为子树所能到的最多数 
bool vis[maxn];

void addedge(int x,int y)
{
	e[++tol].to=y;
	e[tol].next=head[x];
	head[x]=tol;
}
void solve(int x)
{
	dp[x][1]=a[x];
	dp[x][0]=0;
	for(int i=head[x];i;i=e[i].next)
	{
		int y=e[i].to;
		solve(y);
		dp[x][0]+=max(dp[y][1],dp[y][0]);
		dp[x][1]+=dp[y][0];	
	}
}
int main()
{
	int n;
	cin>>n;
	int l,k;
	memset(head,0,sizeof(head));
	memset(vis,false,sizeof(vis));
	tol=0;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	while(cin>>l>>k)
	{
		if(l==0&&k==0)
		{
			break;
		}
		addedge(k,l);
		vis[l]=true;//用来记录有没有父节点 
	} 
	int root=1;
	while(vis[root])//找到根节点 
	{
		root++;
	} 
	solve(root);;
	int ans=max(dp[root][0],dp[root][1]);
	cout<<ans<<endl; 	
	return 0;
} 

E-TT的神秘任务3

题目:

TT 猫咖的生意越来越红火,人越来越多,也越来越拥挤。为了解决这个问题,TT 决定扩大营业规模,但猫从哪里来呢?TT 第一时间想到了神秘人,想要再次通过完成任务的方式获得猫咪。
而这一次,神秘人决定加大难度。
给定一个环,A[1], A[2], A[3], … , A[n],其中 A[1] 的左边是 A[n]。要求从环上找出一段长度不超过 K 的连续序列,使其和最大。
这一次,TT 陷入了沉思,他需要你们的帮助。

Input:

第一行一个整数 T,表示数据组数,不超过 100。
每组数据第一行给定两个整数 N K。(1 ≤ N ≤ 100000, 1 ≤ K ≤ N)
接下来一行,给出 N 个整数。(-1000 ≤ A[i] ≤ 1000)。

Output:

对于每一组数据,输出满足条件的最大连续和以及起始位置和终止位置。
如果有多个结果,输出起始位置最小的,如果还是有多组结果,输出长度最短的。

Sample Input:

4
6 3
6 -1 2 -6 5 -5
6 4
6 -1 2 -6 5 -5
6 3
-1 2 -6 5 -5 6
6 6
-1 -1 -1 -1 -1 -1

Sample Output:

7 1 3
7 1 3
7 6 2
-1 1 1

题目分析:

题意简述为要从环上找出一段长度不超过 K 的连续序列,使其和最大。因为有长度k的限制且要求使得和最大的结果,所以在这里可以用到动态规划(单调队列优化的DP)。

  • 首先因为是一个环结构,所以需要先把环结构变为链结构存于数组中,即以某个点为中心将环两边展开a[i+n]=a[i]。
  • 然后因为要求和的最大,所以先对数组a进行前缀和求解,sum[i]=sum[i-1]+a[i]。
  • 维护一个最大容量为k的单调递增队列,转移方程为ans=max(ans,sum[i]-sum[q.front()]); 且因为题目要求输出起始位置和终止位置,每当ans更新的时候,也需要同步更新起始位置l和终止位置r。
  • 最后输出最大连续和ans以及起始位置l,终止位置r即可。(注意因为一开始进行了环-链的转换,所以输出位置的时候需要变换回去)。

(注意题给数据范围,a[i]的输入需要用到scanf("%lld",&a[i]);直接cin会报错Time Limit Exceeded!)

代码:

#include<iostream>
#include<stdio.h>
#include<deque> 
#include<string.h>
#include<algorithm> 
using namespace std;
const int maxn= 100005;
const int inf=1e8+8;

long long int a[2*maxn],sum[2*maxn];
deque<int> q; 
int main()
{
	int T;
	cin>>T;
	int n,k;
	int l,r;
	while(T--)
	{
		cin>>n>>k;
		int m=2*n-1;
		memset(sum,0,sizeof(sum));
		for(int i=1;i<=n;i++)//环-链 
		{
			scanf("%lld",&a[i]);
			a[i+n]=a[i];
		}
		for(int i=1;i<=m;i++)//前缀和 
		{
			sum[i]=sum[i-1]+a[i];
		}
		
		long long int ans=-inf;
		l=r=0; 
		while(q.size()) q.pop_back();
		q.push_back(0);
		for(int i=1;i<=m;i++)//维护一个最大容量为k的单调增队列 
		{	
			while(!q.empty() && i-k>q.front())
			{
				q.pop_front(); 
			}
			if(ans<sum[i]-sum[q.front()])
			{
				
				l=q.front()+1;
				r=i;
			}
			ans=max(ans,sum[i]-sum[q.front()]); 
			while(!q.empty() && sum[q.back()] > sum[i])
			{
				q.pop_back();
			}
			q.push_back(i);	
		}
		if(l>n)//链-环
		{
			l=l-n;
		}
		if(r>n)
		{
			r=r-n;
		}
		cout<<ans<<" "<<l<<" "<<r<<endl;
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值