week13作业

C - TT 的奖励(必做)

在这里插入图片描述
在这里插入图片描述

解题思路:

对于这道题目来讲,感觉算是一道比较经典的dp问题,但是,自己一开始没有想到利用dp解决,在这一点上,说明自己对于dp的问题感触不够深。当看到这道题目时,首先出现在自己脑海中的是之前做过的两道题目——即宇宙射线问题与矩阵选数问题,总感觉其中有些类似的思维与处理办法,因为在我的印象中,在降低时间复杂度方面,增维与标记两种方式给我感触比较深。那么对于这道题目来讲,很显然,模仿宇宙射线的解法,对到达过的状态进行标记,在这里我们需要利用队列先进先出的性质,状态的入队与数值的更新或许是不同步的,但是是保证在出队之前更新为最优的,这个处理我记得在图论中也曾用到。这种做法能够应用于本题在于时间是逐步递增的,在前一秒的所有状态考虑完备之前后一秒的状态是不会进行考虑的。
而其实,这道题利用动态规划思想进行求解更优,这种甩锅式的处理在一定程度上简化了我们的思考时间,省时省力的做法是我们一直追求的,在这道题目上面,我们将状态定位两维的,第一维表示秒数,第二维表示在那个点,这样一个新的状态只能由上一秒的两个或三个状态更新而来,思维大大简化,代码长度也缩短了很多。

代码1:

#include<iostream>
#include<stdio.h>
#include<queue> 
#include<string.h>
#include<cmath>
using namespace std;
int m,a,b;
int sec=0;
bool label[11][100001]={0};
int location[11][100001]={0};
int func[11][100001]={0};
queue<pair<int,int>> qu;
void judge()
{
	while(qu.size())qu.pop();
	qu.push(pair<int,int>(5,1));
	qu.push(pair<int,int>(4,1));
	qu.push(pair<int,int>(6,1));
	label[5][1]=1;
	label[4][1]=1;
	label[6][1]=1;
	func[5][1]=location[5][1];
	func[4][1]=location[4][1];
	func[6][1]=location[6][1];
	while(qu.size())
	{
		int c=qu.front().first;
		int d=qu.front().second;
		qu.pop();
		if(d>=sec)
		break;
		if(c<=9)
		{
			if(label[c+1][d+1]==0)
			{
				func[c+1][d+1]=func[c][d]+location[c+1][d+1];
				label[c+1][d+1]=1;
				qu.push(pair<int,int>(c+1,d+1));
			}
			else
			{
				func[c+1][d+1]=max(func[c+1][d+1],func[c][d]+location[c+1][d+1]);
			}
		}
		if(c>=1)
		{
			if(label[c-1][d+1]==0)
			{
				func[c-1][d+1]=func[c][d]+location[c-1][d+1];
				label[c-1][d+1]=1;
				qu.push(pair<int,int>(c-1,d+1));
			}
			else
			{
				func[c-1][d+1]=max(func[c-1][d+1],func[c][d]+location[c-1][d+1]);
			}
		}
		if(label[c][d+1]==0)
		{
			func[c][d+1]=func[c][d]+location[c][d+1];
			label[c][d+1]=1;
			qu.push(pair<int,int>(c,d+1));
		}
		else
		{
			func[c][d+1]=max(func[c][d+1],func[c][d]+location[c][d+1]);
		}	
	}
	int cc=0;
	for(int i=0;i<=10;i++)
	{
		cc=max(cc,func[i][sec]);
	}
	cout<<cc<<endl;
}
int main()
{
	scanf("%d",&m);
	while(m!=0)
	{
		sec=0;
		memset(label,0,sizeof(label));
		memset(func,0,sizeof(func));
		memset(location,0,sizeof(location));
		for(int i=0;i<m;i++)
		{
			scanf("%d%d",&a,&b);
			location[a][b]++;
			sec=max(sec,b);
		}
		judge();
		scanf("%d",&m);
	}
}在这里插入代码片

代码2:

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int m;
int fun[100005][11]={0};
int dp[100005][11]={0};
int sec=0;
int a,b;
int main()
{
	ios::sync_with_stdio(false);
	while(cin>>m)
	{
		if(m==0)break;
		sec=0;
		memset(fun,0,sizeof(fun));
		memset(dp,0,sizeof(dp));
		for(int i=0;i<m;i++)
		{
			cin>>a>>b;
			fun[b][a]++;
			sec=max(sec,b);
		}
		dp[1][5]=fun[1][5];
		dp[1][4]=fun[1][4];
		dp[1][6]=fun[1][6];
		for(int i=2;i<=sec;i++)
		{
			for(int j=0;j<=10;j++)
			{
				if(j>=1&&j<=9)
				dp[i][j]=max(dp[i-1][j+1]+fun[i][j],max(dp[i-1][j-1]+fun[i][j],dp[i-1][j]+fun[i][j]));
				else if(j==0)
				dp[i][j]=max(dp[i-1][j+1]+fun[i][j],dp[i-1][j]+fun[i][j]);
				else
				dp[i][j]=max(dp[i-1][j-1]+fun[i][j],dp[i-1][j]+fun[i][j]);
			}
		}
		int func=0;
		for(int i=0;i<=10;i++)
		{
			func=max(func,dp[sec][i]);
		}
		cout<<func<<endl;
	}
 } 在这里插入代码片

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

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

输入样例

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

输出样例

5

解题思路:

这个题目的大致思路与思维过程助教已经在课堂进行过讲解,这属于一道树型dp的题目,关于树型dp的题目,其解决方案都比较巧妙,不过,这些都是建立在树这种特殊的结构上的,其性质比较多,层次比较分明,关于很多问题,都存在着绝对最优且省时省力的做法的。在这个方面,我们需要多积累。另外,关于本道题目,其自身还是基于dp的,但这里面有个增维的处理比较巧妙,大大简化了题目解决时的难度,(记不清那道题目了,反正之前一道题目也是这样处理的),我们反复强调过了这种思想的巧妙性与重要性,故我们要有非常深的感触才行。另外,是本题中对于递归函数参数的传递方面,以及过程处理方面的标记,这些应该是一些套路性的做法,我们需要完全掌握。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<cmath>
#include<algorithm>
using namespace std;
struct Edge{
	int to;
	int next;
}edge[6005];
int w[6005];
int head[6005];
int tot=0;
int deg[6005]={0};
int N;
int num[6005][2]={0};
void init()
{
	tot=0;
	memset(w,0,sizeof(w));
	memset(deg,0,sizeof(deg));
	memset(head,-1,sizeof(head));
	memset(num,-1,sizeof(num));
}
void add(int begin,int end)
{
	edge[tot].next=head[begin];
	edge[tot].to=end;
	head[begin]=tot++;
} 
int func()
{
	int fun;
	for(int i=1;i<=N;i++)
	{
		if(deg[i]==0)
		{
			fun=i;
			break;
		}
	}
	return fun;
}
int ff(int root,int label)
{
	if(num[root][label]!=-1)
	{
		return num[root][label];
	}
	if(head[root]==-1)
	{
		if(label==0)
		{
			return num[root][0]=0;
		}
		else
		{
			return num[root][1]=w[root];
		}
	}
	else
	{
		int cc=0,dd=0;
		for(int i=head[root];i!=-1;i=edge[i].next)
		{
			cc+=max(ff(edge[i].to,0),ff(edge[i].to,1));
			dd+=ff(edge[i].to,0);
		}
		if(label==1)
		{
			return num[root][1]=w[root]+dd;
		}
		else
		{
			return num[root][0]=cc;
		}
	}
}
int main()
{
	
	scanf("%d",&N);
	while(N!=0)
	{
		init();
		for(int i=1;i<=N;i++)
		{
			scanf("%d",&w[i]);
		}
		int a,b;
		for(int i=1;i<N;i++)
		{
			scanf("%d%d",&a,&b);
			add(b,a);
			deg[a]=1;
		}
		int fun=func();
		cout<<max(ff(fun,0),ff(fun,1))<<endl;
		scanf("%d",&N);
	}	
} 在这里插入代码片

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

解题思路:

这是一道利用单调队列优化dp的题目,关于具体做法上课的时候助教做了一下解释,其比之前做过的题目求连续最大和多出了要限定在最长长度不超过k的情况下。当时,我们做那道题目的时候,存在两种做法,一种是利用dp求解,另一种是利用前缀和进行求解。对于这道题目,我们采用的正是单调队列控制下的前缀和求解。
其中,令我感触比较深的是前缀和的求解,这样的转化比较巧妙,再一次凸显了前缀和的强大求解能力,前缀和给我的较大感触就是将一些比较离散的问题变得系统化,前缀和维护的变量往往很少,拿最大和的例子来讲,其只需要维护开头与结尾两个点,理解方便,操作也方便。不过这道题目中,也体现出了静态的思想,这与dp中的思维是一样的,其主要体现在我们仍以第i个元素为某一段的最后元素进行求解,其实,这是前缀和解决这道题目的核心内容。在这道题目上,我们利用的是deque双端队列,这是一种自己以前尚未用过的结构,算是知识面上的一种扩展。最后需要注意的是,对于这种题目,形成一个比较好的思路已经不容易,我们一定注意边界问题的处理,边界问题对自己来讲总感觉很棘手,这需要多注意,可以去培养其中的思维方式。

#include<iostream>
#include<stdio.h>
#include<deque>
#include<string.h>
using namespace std;
int T;
int N,K;
int num[200005]={0};
deque<int> deq;
int sum[200005]={0};
int beg,en,ans;
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		memset(num,0,sizeof(num));
		memset(sum,0,sizeof(sum));
		while(deq.size())deq.pop_back();
		scanf("%d%d",&N,&K);
		for(int i=1;i<2*N;i++)
		{
			if(i<=N)
			{
				scanf("%d",&num[i]);
			}
			else
			num[i]=num[i-N];
		}
		sum[1]=num[1];
		for(int i=2;i<2*N;i++)
		{
			sum[i]=sum[i-1]+num[i];
		}
		sum[0]=0;
		ans=sum[0];
		beg=0;
		en=0;
		deq.push_back(0);
		for(int i=1;i<2*N;i++)
		{
			while(!deq.empty()&&sum[deq.back()]>sum[i])
			deq.pop_back();
			deq.push_back(i);
			if(i-K>deq.front())
			deq.pop_front();
			if(ans<sum[deq.back()]-sum[deq.front()])
			{
				ans=sum[deq.back()]-sum[deq.front()];
				beg=deq.front();
				en=deq.back();
			}
		}
		if(!(beg==0&&en==0))
		{
			cout<<ans<<" ";
			cout<<beg+1<<" ";
			if(en>N)
			{
				cout<<en-N<<endl;
			}
			else
			{
				cout<<en<<endl;	
			}	
		}
		else
		{
			int funn=num[1],tt=1;
			for(int i=2;i<=N;i++)
			{
				if(funn<num[i])
				{
					funn=num[i];
					tt=i;
				}
			}
			cout<<funn<<" "<<tt<<" "<<tt<<endl; 
		}
	}
}在这里插入代码片
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值