程序设计思维与实践 Week13 作业

程序设计思维与实践 Week13 作业

  • A - TT 的神秘任务1(必做)
    • 题目
    • 题目分析
    • 代码
  • B - TT 的神秘任务2(必做)
    • 题目
    • 题目分析
    • 代码
  • C - TT 的奖励(必做)
    • 题目
    • 题目分析
    • 代码
  • D - TT 的苹果树(选做)
    • 题目分析
    • 代码
  • E - TT 的神秘任务3(选做)
    • 题目
    • 分析
    • 代码

程序设计思维与实践 Week13 作业)

A - TT 的神秘任务1(必做)

题目

这一天,TT 遇到了一个神秘人。

神秘人给了两个数字,分别表示 n 和 k,并要求 TT 给出 k 个奇偶性相同的正整数,使得其和等于 n。

例如 n = 10,k = 3,答案可以为 [4 2 4]。

TT 觉得这个任务太简单了,不愿意做,你能帮他完成吗?

本题是SPJ
第一行一个整数 T,表示数据组数,不超过 1000。

之后 T 行,每一行给出两个正整数,分别表示 n(1 ≤ n ≤ 1e9)、k(1 ≤ k ≤ 100)。
    如果存在这样 k 个数字,则第一行输出 "YES",第二行输出 k 个数字。

如果不存在,则输出 "NO"。
  • Input
8
10 3
100 4
8 7
97 2
8 8
3 10
5 3
1000000000 9
  • Output
YES
4 2 4
YES
55 5 5 35
NO
NO
YES
1 1 1 1 1 1 1 1
NO
YES
3 1 1
YES
111111110 111111110 111111110 111111110 111111110 111111110 111111110 111111110 111111120

题目分析

这道题目是将数字拆分,本来我的思路是判断要拆的数是奇数还是偶数,然后1或者2,但是实现了一段时间,后发现这个解法就不对,比如8 8,8是偶数按照拆2是不可能的,但是可以拆8个1,所以,我还是延续之前的方法,先拆1试试,再拆2试试,都拆不出就真的拆不出了,当我都拆1或者2的时候,由于都是偶数或者奇数,不同的项可以互相加减偶数转化,所以我拆1能成,拆别的奇数也能成,这两个是充要条件,偶数也是一样,思考到这之后,思路就明显了,就是硬拆,拆出k-1个来,剩下的看是不是奇偶性一致

代码

#include

using namespace std;
int main(){
	int n,k;
	int num;
	scanf("%d",&num);
	while(num--){
		scanf("%d%d",&n,&k);
		if(n<k) {cout<<"NO"<<endl;continue;}
		//
		int p1=n-(k-1);
		if(p1&1){
			cout<<"YES"<<endl;
			for(int i=0;i<k-1;++i){
				printf("1 ");
			}
			cout<<p1<<endl;
			continue;
		}
		int p2=n-2*(k-1);
		if(p2%2==0&&p2>0){
			cout<<"YES"<<endl;
			for(int i=0;i<k-1;i++){
				printf("2 ");
			}
			cout<<p2<<endl;
			continue;
		}
		cout<<"NO"<<endl;
	}
	return 0;
}

B - TT 的神秘任务2(必做)

题目

在你们的帮助下,TT 轻松地完成了上一个神秘任务。

但是令人没有想到的是,几天后,TT 再次遇到了那个神秘人。

而这一次,神秘人决定加大难度,并许诺 TT,如果能够完成便给他一个奖励。

任务依旧只给了两个数字,分别表示 n 和 k,不过这一次是要求 TT 给出无法被 n 整除的第 k 大的正整数。

例如 n = 3,k = 7,则前 7 个无法被 n 整除的正整数为 [1 2 4 5 7 8 10],答案为 10。

好奇的 TT 想要知道奖励究竟是什么,你能帮帮他吗?
第一行一个整数 T,表示数据组数,不超过 1000。

之后 T 行,每一行给出两个正整数,分别表示 n(2 ≤ n ≤ 1e9)、k(1 ≤ k ≤ 1e9)。
对于每一组数据,输出无法被 n 整除的第 k 大的正整数。

Input

6
3 7
4 12
2 1000000000
7 97
1000000000 1000000000
2 1

Output

10
15
1999999999
113
1000000001
1

题目分析

这道题题意好理解,也很容易想出思路,就是如果整除n,那么就有n-1个连续的数形成一组出现,跨过一个能整除的之后,又出现一组连续的数,难点在于数据序号的处理上,首先我们算出他出现了多少组由于数据如果是一组的最后一个,就会出现余数为0的情况,不好处理,所以我们先减去一个最后答案再加上,那么

a=(k-1)%(n-1),b=(k-1)/(n-1);

这里的a代表除去前面完整的组数还有多少组,b代表有多少组完整的数,那么我们再答案中要算的数是要乘真实的n的,答案也由此而来

代码

#include

using namespace std;
int main(){
	int n,k;
	int num;
	scanf("%d",&num);
	while(num--){
		scanf("%d%d",&n,&k);
		if(n>k){printf("%d\n",k); continue;}
		else if(n==k){
			printf("%d\n",n+1);continue;
		}
		int a=(k-1)%(n-1),b=(k-1)/(n-1);
		printf("%d\n",b*n+a+1);
	}
	return 0;
}

C - TT 的奖励(必做)

题目

在大家不辞辛劳的帮助下,TT 顺利地完成了所有的神秘任务。

神秘人很高兴,决定给 TT 一个奖励,即白日做梦之捡猫咪游戏。

捡猫咪游戏是这样的,猫咪从天上往下掉,且只会掉在 [0, 10] 范围内,具体的坐标范围如下图所示。(自行想象)

TT 初始站在位置五上,且每秒只能在移动不超过一米的范围内接住掉落的猫咪,如果没有接住,猫咪就会跑掉。例如,在刚开始的一秒内,TT 只能接到四、五、六这三个位置其中一个位置的猫咪。

喜爱猫咪的 TT 想要接住尽可能多的猫咪,你能帮帮他吗?

    多组样例。每组样例输入一个 m (0 < m < 100000),表示有 m 只猫咪。

在接下来的 m 行中,每行有两个整数 a b (0 < b < 100000),表示在第 b 秒的时候有一只猫咪掉落在 a 点上。

注意,同一个点上同一秒可能掉落多只猫咪。m = 0 时输入结束。 
输出一个整数 x,表示 TT 可能接住的最多的猫咪数。
  • intput
    6
    5 1
    4 1
    6 1
    7 2
    7 2
    8 3
    0

  • output
    4

题目分析

这道题目是一道明显的动态规划,状态就是TT所处的位置和时间,这些状态决定了下一个时刻和相邻时刻的状态值,每一个位置和时刻都有一个猫咪数,我们就从dp[pos][time]状态值取决于上一时刻的临近的三个位置的接的个数加上当前位置的猫咪数
状态转移:dp[pos][i]=max(dp[pos][i],dp[k][i-1]+cats[pos][i])( k=pos-1,pos,pos+1,当然首先要在有效范围内),如果从任何位置都可以开始,那么就很容易,不需要加任何限制条件,现在我们要从5开始接猫咪,想到在第i步TT跑到最远的距离就是i,那么,我再前i步只更新能够得到的位置就可以,所以需要判断abs(5-pos)<=i(主要是前5步有用,本来我就是写了两个部分),之后输出所有位置上的猫咪最大值就可以

  • 还在别的同学那里看到一种倒着找的办法,就是时间从time~0来遍历,这样最后答案只要找dp[0][5]就可以了,也挺好,下面附代码
for(int i=time;i>=0;i--)
	for(int j=0;j<=10;j++)
		dp[i][j]+=max(dp[i+1][j+1],max(dp[i+1][j],dp[i+1][j-1]));//这里不会越界么
printf("%d\n",dp[0[5]);

代码

#include
#include
#include
const int nmax=1E5+10;
int dp[15][nmax];//可以滚动
int cats[12][nmax];
int mov[]={-1,0,1};
using namespace std;
int main(){
	int m;
	while(scanf("%d",&m)&&m){
		memset(cats,0,sizeof(cats));
		memset(dp,0,sizeof(dp));
		int e=-1;
		for(int i=0;i<m;++i){
			int p,q;scanf("%d%d",&p,&q);e=max(e,q);
			cats[p][q]++;
		}
		for(int i=1;i<=e;i++){
			for(int pos=0;pos<=10;pos++){
				if(abs(5-pos)<=i){
					for(int l=0;l<3;++l){
						int k=mov[l]+pos;
						if(k>=0&&k<=10){
							dp[pos][i]=max(dp[pos][i],dp[k][i-1]+cats[pos][i]);
						}
					}
				}
			}
		}
		int res=-1;
		/*
		for(int i=0;i<=10;i++){
			printf("%d ",dp[i][2]);
		}
		*/
		for(int i=0;i<=10;i++){
			res=max(res,dp[i][e]);
		}
		printf("%d\n",res);
	}
	return 0;
}

D - TT 的苹果树(选做)

在大家的三连助攻下,TT 一举获得了超级多的猫咪,因此决定开一间猫咖,将快乐与大家一同分享。并且在开业的那一天,为了纪念这个日子,TT 在猫咖门口种了一棵苹果树。

一年后,苹果熟了,到了该摘苹果的日子了。

已知树上共有 N 个节点,每个节点对应一个快乐值为 w[i] 的苹果,为了可持续发展,TT 要求摘了某个苹果后,不能摘它父节点处的苹果。

TT 想要令快乐值总和尽可能地大,你们能帮帮他吗?
    结点按 1~N 编号。

第一行为 N (1 ≤ N ≤ 6000) ,代表结点个数。

接下来 N 行分别代表每个结点上苹果的快乐值 w[i](-128 ≤ w[i] ≤ 127)。

接下来 N-1 行,每行两个数 L K,代表 K 是 L 的一个父节点。

输入有多组,以 0 0 结束。
每组数据输出一个整数,代表所选苹果快乐值总和的最大值。
- input

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

  • output
    5

题目分析

这道题目和上课讲的那个和上司跳舞的题完全一样,只是语境改了,课上的思路也是每个节点分成两种情况,dp[1][node]是选了当前子树根节点的最大值,dp[0][node]是没选当前子树根节点的最大值,那么如何更新呢,假如更新dp[1][node],由于这个点已经选了,所以我们的下面的节点不能选,所以dp[1][node]=Σdp[0][kid],kid是根节点的孩子节点,而对于dp[0][node],对于每个孩子,对于任意一个孩子节点,孩子节点选不选不重要了,但是哪个最大很重要,我们每个孩子节点选那个最大的,把所有孩子结点的累加起来,并且加上本来根节点的权重,就可以了,在dp完数后

  • max(dp[0][root],dp[1][root]) 来求出最大值.
  • 课上普及的知识,手动扩大运行栈的方法,防止递归的时候爆栈
    #pragma comment(linker, “/STACK:102400000,102400000”)
    (没用的情况好像也能过)

代码

#pragma comment(linker, "/STACK:102400000,102400000")
#include
#include
#include
using namespace std;
const int nmax=6e3+10;
int head[nmax],tot=1;
struct chain{
	int to,parent,next;
}edges[nmax];
int wht[nmax],pa[nmax],dp[2][nmax];//dp[1][]选中,dp[0][]没有
void add(int par,int kid){
	pa[kid]=par;//zhaobaba
	edges[++tot].to=kid;
	edges[tot].parent=par,edges[tot].next=head[par];
	head[par]=tot;
}
void solve(int p){
	for(int i=head[p];i;i=edges[i].next){
		int kid=edges[i].to;
		solve(kid);
		dp[1][p]+=dp[0][kid];
		dp[0][p]+=max(dp[0][kid],dp[1][kid]);
	}
	dp[1][p]+=wht[p];
}
int main(){
	int num,t1,t2;
	while(scanf("%d",&num)&&num){
		memset(head,0,sizeof(head));
		memset(dp,0,sizeof(dp));
		memset(pa,0,sizeof(pa));
		tot=1;
		for(int i=1;i<=num;++i)scanf("%d",wht+i);
		for(int i=0;i<num-1;++i){
			scanf("%d%d",&t1,&t2);
			add(t2,t1);
		}
		//找到根
		int root=1;
		while(pa[root]){
			root=pa[root];
		}
		solve(root);
		printf("%d\n",max(dp[0][root],dp[1][root]));
	}
	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

分析

这道题重启了我对单调队列的记忆,以及用贪心的思路解决一道相似的问题,还有上个周的化环为链的思想

  • 所谓化环为链就是除了最后一个元素,把其他元素在后面再写一遍,可以保证包括所有的环上序列,而复杂度不变(逐点拆环需要乘上O(n)的复杂度),
  • 贪心的思路是指首先我是用前缀和来计算出2*n-1的元素的前缀和,那么连续子序列[l,r]=sum[r]-sum[l-1],在每一个r上,我只需要求出最小的范围内的l-1就可以.
  • 单调队列是一个以O(n)的复杂度,计算每一个窗口范围内的最大(小)值得办法,首先把大于窗口范围得值踢出去,之后从另一个边把大于当前值得值请出去,再加入自己,这时在窗口内的第一个值就是窗口内最小值一直维护就一直得到当前窗口最小值,这正与贪心的要求吻合,所以使用双向队列deque来维护单调队列,每次以常数代价获得最小值并且与当前最小值比较,大于就换,并且记录两个端点
  • 当时在课上的时候就看见学长的代码里有一句deq.push_front(0);当时还没明白,自己写的时候还没写这句,导致样例能过,就是wa,这句话是保证有解从1开始因为sum[0]=0;而这个就是用压入0体现的

代码

#include
#include
#include
#include
typedef long long LL;
using namespace std;
const int inf=1E9;
const int nmax=1e5+10;
const int mmax=2*nmax;
int nn[mmax],sum[mmax];
int main(){
	int num,n,k;
	scanf("%d",&num);
	while(num--){
		scanf("%d%d",&n,&k);
		memset(nn,0,sizeof(nn));
		memset(sum,0,sizeof(sum));
		for(int i=1;i<=n;++i){
			scanf("%d",nn+i);
		}
		deque<int> deq;
		for(int i=1,pos=n+1;i<n;++i,++pos){
			nn[pos]=nn[i];
		}
		int end=2*n-1;//计算前缀和
		for(int i=1;i<=end;++i){
			sum[i]=sum[i-1]+nn[i];
		}
		LL mymax=sum[1];
		int l=0,r=1;
		deq.push_front(0);
		for(int i=1;i<=end;++i){
			while(!deq.empty()&&i-deq.back()>k) deq.pop_back();
			if(sum[i]-sum[deq.back()]>mymax){
				mymax=sum[i]-sum[deq.back()];
				l=deq.back(),r=i;
			}
			else if(sum[i]-sum[deq.back()]==mymax){
				if(l==deq.back()) {if(r-l>i-deq.back()) l=deq.back(),r=i;}
				else if(l>deq.back()) l=deq.back(),r=i;
			}
			//printf("%d,%d,%d,%d\n",l,r,sum[l],sum[r]);
			while(!deq.empty()&&sum[i]<sum[deq.front()])deq.pop_front();
			deq.push_front(i);
		}
		l++;
		if(l>n) l=l%n;
		if(r>n) r=r%n;
		printf("%lld %d %d\n",mymax,l,r);
	}
	return 0;
}
/*
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


//
7 1 3
7 1 3
7 6 2
-1 1 1
*/

你可能感兴趣的:(程序设计思维与实践 Week13 作业)