预备队第二周训练题

P1007 独木桥

题目背景

战争已经进入到紧要时间。你是运输小队长,正在率领运输部队向前线运送物资。运输任务像做题一样的无聊。你希望找些刺激,于是命令你的士兵们到前方的一座独木桥上欣赏风景,而你留在桥下欣赏士兵们。士兵们十分愤怒,因为这座独木桥十分狭窄,只能容纳 1 个人通过。假如有 2 个人相向而行在桥上相遇,那么他们 2 个人将无法绕过对方,只能有 1 个人回头下桥,让另一个人先通过。但是,可以有多个人同时呆在同一个位置。

题目描述

突然,你收到从指挥部发来的信息,敌军的轰炸机正朝着你所在的独木桥飞来!为了安全,你的部队必须撤下独木桥。独木桥的长度为 L,士兵们只能呆在坐标为整数的地方。所有士兵的速度都为 1,但一个士兵某一时刻来到了坐标为 0 或 L+1 的位置,他就离开了独木桥。

每个士兵都有一个初始面对的方向,他们会以匀速朝着这个方向行走,中途不会自己改变方向。但是,如果两个士兵面对面相遇,他们无法彼此通过对方,于是就分别转身,继续行走。转身不需要任何的时间。

由于先前的愤怒,你已不能控制你的士兵。甚至,你连每个士兵初始面对的方向都不知道。因此,你想要知道你的部队最少需要多少时间就可能全部撤离独木桥。另外,总部也在安排阻拦敌人的进攻,因此你还需要知道你的部队最多需要多少时间才能全部撤离独木桥。

输入格式

第一行共一个整数 L,表示独木桥的长度。桥上的坐标为 1,2,⋯,L。

第二行共一个整数 N,表示初始时留在桥上的士兵数目。

第三行共有 N 个整数,分别表示每个士兵的初始坐标。

输出格式

共一行,输出 2 个整数,分别表示部队撤离独木桥的最小时间和最大时间。2个整数由一个空格符分开。

由于一个桥上同时只能有一个士兵行走,所以在同一个位置有多个士兵的话,需要等其中一个士兵走到下一个位置了
再开始行走,而且题目中也说了士兵的数量小于桥的长度,所以不妨让有多个士兵的地方其他士兵都往后走一个位置。
最小时间就是所有士兵都是聪明人,离桥的哪端近就往哪边走,但是最短时间取决于聪明人里走的最慢的那一个。最长
时间就是大家都不着急,离桥的哪端远就往哪边走,最大时间就是这些悠哉小兵里走的最慢的那一个。

#include <iostream>
#include <algorithm>
#define MAXN 5010

using namespace std;

int a[MAXN];
int l,n;    //l代表桥的长度,n代表士兵的个数

int minn(int a[])    //等最小距离中的最大距离通过桥才算通过了
{
	int min=0;
	for(int i=1;i<=n;i++)
	{
		int tmp=(a[i]<=l+1-a[i]?a[i]:l+1-a[i]);
		if(tmp>min)  min=tmp;
	}
	return min;
}

int maxx(int a[])
{
	int max=0;
	for(int i=1;i<=n;i++)
	{
		int temp=(a[i]>=l+1-a[i]?a[i]:l+1-a[i]);
		if(temp>max)  max=temp;
	}
	return max;
}

int main()
{
	cin>>l>>n;
	for(int i=1;i<=n;i++)	cin>>a[i];
	sort(a+1,a+1+n);
	for(int i=2;i<=n;i++)
	{
		if(a[i]==a[i-1])    a[i]=a[i]+1;
	}
	sort(a+1,a+1+n);
	int res_min=minn(a);
	int res_max=maxx(a);
	cout<<res_min<<" "<<res_max;
	return 0;
}

P1031 [NOIP2002 提高组] 均分纸牌

题目描述

有 N 堆纸牌,编号分别为 1,2,…,N。每堆上有若干张,但纸牌总数必为 N 的倍数。可以在任一堆上取若干张纸牌,然后移动。

移牌规则为:在编号为 1 堆上取的纸牌,只能移到编号为 2 的堆上;在编号为 N 的堆上取的纸牌,只能移到编号为 N−1 的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。

现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。

例如 N=4 时,4 堆纸牌数分别为 9,8,17,6。

移动 33 次可达到目的:

  • 从第三堆取 4 张牌放到第四堆,此时每堆纸牌数分别为 9,8,13,10。
  • 从第三堆取 3 张牌放到第二堆,此时每堆纸牌数分别为 9,11,10,10。
  • 从第二堆取 1 张牌放到第一堆,此时每堆纸牌数分别为 10,10,10,10。

输入格式

第一行共一个整数 N,表示纸牌堆数。
第二行共 N 个整数 A1​,A2​,…,AN​,表示每堆纸牌初始时的纸牌数。

输出格式

共一行,即所有堆均达到相等时的最少移动次数。

由于题目中提到第一堆纸牌只能由第二堆纸牌移动得到,所以过于纠结是不是只能从某一端移动,
然后就陷入了从两端开始移动,算有多少连续的均值出现之类的。其实1->2和2->1移动是一样的,从第一堆开始,如果前面几堆与平均值的差值的和不为0,说明这一堆就要移动。

#include <bits/stdc++.h>
#define MAXN 105

using namespace std;

int a[MAXN];
int n,sum,ave;

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		sum+=a[i];
	}
	ave=sum/n;
	for(int i=1;i<=n;i++)
	{
		a[i]-=ave;
	}
	int res=0;
	for(int i=1;i<=n-1;i++)
	{
		if(a[i]!=0)
		{
			res++;
			a[i+1]+=a[i];
		}
	}
	cout<<res;
	return 0;
}

P1803 凌乱的yyy / 线段覆盖

题目背景

快 noip 了,yyy 很紧张!

题目描述

现在各大 oj 上有 n 个比赛,每个比赛的开始、结束的时间点是知道的。

yyy 认为,参加越多的比赛,noip 就能考的越好(假的)。

所以,他想知道他最多能参加几个比赛。

由于 yyy 是蒟蒻,如果要参加一个比赛必须善始善终,而且不能同时参加 2 个及以上的比赛。

输入格式

第一行是一个整数 n,接下来 n 行每行是 2 个整数 ai​,bi​ (ai​<bi​),表示比赛开始、结束的时间。

输出格式

一个整数最多参加的比赛数目。

很经典的贪心题目,要参加的比赛数多,那么每一场比赛占用的时间就不能太长。所以先按照比赛结束的时间从小到大
排序。而同一时间结束的比赛,开始的时间越晚越好,之前的时间就可以参加其他比赛

#include <bits/stdc++.h>
#define MAXN 1000005

using namespace std;

struct node
{
	int start;
	int end;
}a[MAXN];

bool cmp(node s,node t)
{
	if(s.end!=t.end)   return s.end<t.end;
	return    s.start>t.start;
}

int main()
{
	int n,cnt=1;
	cin>>n;
	for(int i=1;i<=n;i++)      cin>>a[i].start>>a[i].end;
	sort(a+1,a+1+n,cmp);
	int flag=1;
	for(int i=2;i<=n;i++)
	{
		if(a[i].start>=a[flag].end)
		{
			cnt++;
			flag=i;
		}
	}
	cout<<cnt;
	return 0;
}

P1223 排队接水

题目描述

有 n 个人在一个水龙头前排队接水,假如每个人接水的时间为 Ti​,请编程找出这 n 个人排队的一种顺序,使得 n 个人的平均等待时间最小。

输入格式

第一行为一个整数 n。

第二行 n 个整数,第 i 个整数 Ti​ 表示第 i 个人的等待时间 Ti​。

输出格式

输出文件有两行,第一行为一种平均时间最短的排队顺序;第二行为这种排列方案下的平均等待时间(输出结果精确到小数点后两位)。
这道题同样也是一道贪心的题目,要想让要接水的人平均等待时间最短,就得让接得快的先接 

(第一次写的时候定义了float ave;然后让ave=res*1.0/n;导致精度有问题,测试点4个wa;
改成了直接输出res*1.0/n就过了qwq)

#include <bits/stdc++.h>
#define MAXN 1000005

using namespace std;

struct node
{
	int val;
	int num;
}a[MAXN];
long long sum[MAXN];

bool cmp(node a,node b)
{
	if(a.val!=b.val)   return   a.val<b.val;
	else  return a.num<b.num;
}

int main()
{
	int n;
	long long res=0;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i].val;
		a[i].num=i;
	}
	sort(a+1,a+1+n,cmp);
	sum[1]=0;
	for(int i=2;i<=n;i++)
	{
		sum[i]=sum[i-1]+a[i-1].val;
	}
	for(int i=1;i<=n;i++)
	{
		res+=sum[i];
		cout<<a[i].num<<" ";
	}
	printf("\n");
	printf("%.2f",res*1.0/n);
	return 0;
}

补充题部分

P1199 [NOIP2010 普及组] 三国游戏

题目描述

小涵很喜欢电脑游戏,这些天他正在玩一个叫做《三国》的游戏。

在游戏中,小涵和计算机各执一方,组建各自的军队进行对战。游戏中共有 �N 位武将(�N为偶数且不小于44),任意两个武将之间有一个“默契值”,表示若此两位武将作为一对组合作战时,该组合的威力有多大。游戏开始前,所有武将都是自由的(称为自由武将,一旦某个自由武将被选中作为某方军队的一员,那么他就不再是自由武将了),换句话说,所谓的自由武将不属于任何一方。

游戏开始,小涵和计算机要从自由武将中挑选武将组成自己的军队,规则如下:小涵先从自由武将中选出一个加入自己的军队,然后计算机也从自由武将中选出一个加入计算机方的军队。接下来一直按照“小涵→计算机→小涵→……”的顺序选择武将,直到所有的武将被双方均分完。然后,程序自动从双方军队中各挑出一对默契值最高的武将组合代表自己的军队进行二对二比武,拥有更高默契值的一对武将组合获胜,表示两军交战,拥有获胜武将组合的一方获胜。

已知计算机一方选择武将的原则是尽量破坏对手下一步将形成的最强组合,它采取的具体策略如下:任何时刻,轮到计算机挑选时,它会尝试将对手军队中的每个武将与当前每个自由武将进行一一配对,找出所有配对中默契值最高的那对武将组合,并将该组合中的自由武将选入自己的军队。 下面举例说明计算机的选将策略,例如,游戏中一共有66个武将,他们相互之间的默契值如下表所示:

双方选将过程如下所示:

小涵想知道,如果计算机在一局游戏中始终坚持上面这个策略,那么自己有没有可能必胜?如果有,在所有可能的胜利结局中,自己那对用于比武的武将组合的默契值最大是多少?

假设整个游戏过程中,对战双方任何时候均能看到自由武将队中的武将和对方军队的武将。为了简化问题,保证对于不同的武将组合,其默契值均不相同。

输入格式

共 N 行。

第一行为一个偶数 N,表示武将的个数。

第 2行到第 N行里,第i+1行有Ni​个非负整数,每两个数之间用一个空格隔开,表示i号武将和i+1,i+2,…,N号武将之间的默契值(0≤0≤默契值≤1,000,000,000≤1,000,000,000)。

输出格式

共一或二行。

若对于给定的游戏输入,存在可以让小涵获胜的选将顺序,则输出1,并另起一行输出所有获胜的情况中,小涵最终选出的武将组合的最大默契值。如果不存在可以让小涵获胜的选将顺序,则输出 0。

想到了是要取每一行的第二大的值,然后再从这些第二大的值中取最大的值。但是一直没有想通其实电脑是不可能赢的,
因为如果我取了第二大中的最大值,只要电脑取不到比这个值更大的值,那么我就赢了。电脑每次都会预判我们下一步的
选择,实际上只要我们每次都取比“最大值”小一点的“次大值”那么“最大值”永远会被拆掉,电脑不可能赢。
也算是浅浅浅浅地了解了一下博弈论。
#include <bits/stdc++.h>
#define MAXN  505

using namespace std;
long long a[MAXN][MAXN];
int n;

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		for(int j=i;j<=n;j++)
		{
			if(j==i) 	a[i][j]=a[j][i]=-1;
			else
			{
				cin>>a[i][j];
				a[j][i]=a[i][j];
			}
		}
	}
	long long ans=-1;
	for(int i=1;i<=n;i++)
	{
		sort(a[i]+1,a[i]+1+n);
		if(a[i][n-1]>ans)   ans=a[i][n-1];
	}
	cout<<1<<endl<<ans;
	return 0;
}

P3382 【模板】三分

题目描述

如题,给出一个 N 次函数,保证在范围 [l,r] 内存在一点 x,使得 [l,x] 上单调增,[x,r] 上单调减。试求出 x 的值。

输入格式

第一行一次包含一个正整数 N 和两个实数 l,r,含义如题目描述所示。

第二行包含 N+1 个实数,从高到低依次表示该 N 次函数各项的系数。

输出格式

输出为一行,包含一个实数,即为 x 的值。若你的答案与标准答案的相对或绝对误差不超过 10−5 则算正确。

看到这道题的第一反应就是用导数做,但是写的代码样例能过,一提交就全是wa。然后去研究了一下题解的思路,发现其实用导数做是可行的,错误出在不知道如何用题目给的精度去判断while循环结束的条件,然后就会陷入死循环。

#include <bits/stdc++.h>
#define MAXN 15

using namespace std;

double a[MAXN];     //存各项系数,下标从0开始
int n;
double eps=0.000001;

double calcu(double x)    //求导
{
	double ans=0;
	for(int i=n;i>=1;i--)
	{
		ans+=a[i]*i*pow(x,i-1);
	}
	return ans;
}

int main()
{
	double l,r,mid;
	cin>>n>>l>>r;
	for(int i=n;i>=0;i--)
	{
		cin>>a[i];
	}
	while(r-l>eps)
	{
		mid=(l+r)/2;
		if(calcu(mid)<0.000000)   r=mid;
		else  l=mid;
	}
	printf("%f",mid);
	return 0;
}

第二种方法就是直接通过对值的求解来判断此处是不是最大值,学到了秦九昭方法,降低算一个多项式的值的时间复杂度 

#include <bits/stdc++.h>
#define MAXN 15

using namespace std;

double a[MAXN];     //存各项系数,下标从0开始
int n;
double eps=0.000001;

double calcu(double x)    //秦九昭算法降时间复杂度
{
	double ans=0;
	for(int i=n;i>=0;i--)
	{
		ans=ans*x+a[i];
	}
	return ans;
}

int main()
{
	double l,r,mid;
	cin>>n>>l>>r;
	for(int i=n;i>=0;i--)
	{
		cin>>a[i];
	}
	while(r-l>eps)
	{
		mid=(l+r)/2;
		if(calcu(mid-eps)>calcu(mid))   r=mid;
		else  l=mid;
	}
	printf("%f",mid);
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值