2020.2.16【NOIP提高组】模拟B组7题解兼反思

7 篇文章 0 订阅
4 篇文章 0 订阅

这已经是我连续崩掉的第n场比赛了,170分,Rank20.
做这套题是感觉不是很难,但是就是完全的崩掉了(我太弱了QAQ)。
不说这么多不开心😒的事,留着泪写题解吧。(觉得我太蒟就当是反思吧)
T1:
题目大意
在一个长方型框子里,最多有N(0≤N≤6)个相异的点。在其中任何一个点上放一个很小的油滴,那么这个油滴会一直扩展,直到接触到其它油滴或者框子的边界。必须等一个油滴扩展完毕才能放置下一个油滴。那么应该按照怎样的顺序在这N个点上放置油滴,才能使放置完毕后所有油滴占据的总体积最大呢?(不同的油滴不会相互融合)
注:圆的面积公式V=pirr,其中r为圆的半径。
输入
第一行一个整数N。
第二行为长方形边框一个顶点及其对角顶点的坐标,x,y,x’,y’。
接下去N行,每行两个整数xi,yi,表示盒子内N个点的坐标。
以上所有的整数都在[-1000, 1000]内。
输出
一行,一个整数,长方体盒子剩余的最小空间(结果四舍五入输出)。
样例输入
2
0 0 10 10
3 3
7 7
样例输出
50
题解
这是我比赛里唯一没有做的题,因为我根本就不知道油滴怎么拓展(原来是拓展成圆)。知道了之后,很简单能想到用dfs,因为n只有6,那么就可以枚举每一种排列,统计答案,只是精度很烦(能一次打出来的都是巨佬)。用勾股定理来求两圆心的距离,也就是两个圆半径之和,然后取一个最小的半径。
不说这么多,不要装巨佬,上代码:

#include<cstdio>
#include<cmath>
#define db double
using namespace std;
int n,x,y,x2,y2;
db ans=0.0,r[7],MAX;
bool bz[7];
struct node {
	int x,y;
}a[7];
db min(db u,db v) {
	if(u<v)return u;
	return v;
}
//核心代码 
void dfs(int now,db sum) {
	if(now>n) {//找出一个排列 
		if(sum>ans)ans=sum;//找出最大的拓展面积 
		return;
	}
	for(int i=1;i<=n;i++)
		if(!bz[i]) {//没有被加入排列 
			bz[i]=1;
			r[i]=min((db)abs(x2-a[i].x),(db)abs(a[i].y-y2));
			r[i]=min(r[i],min((db)abs(a[i].x-x),(db)abs(y-a[i].y)));
			//这两步是于边界的距离 
			for(int j=1;j<=n;j++) {
				if(bz[j]&&i!=j) {
					db l=sqrt((a[i].x-a[j].x)*(a[i].x-a[j].x)+(a[i].y-a[j].y)*(a[i].y-a[j].y));//勾股定理 
					l-=r[j];//求出半径 
					if(l<=0.0)l=0.0;//有可能已经被覆盖掉了 
					r[i]=min(r[i],l);
					if(r[i]==0.0)break;
				}
			}
			dfs(now+1,sum+3.14159265358*r[i]*r[i]);//pai要开大点,不然会中精度的招 
			bz[i]=0;
		}
}
int main() {
	scanf("%d\n",&n);
	scanf("%d%d%d%d",&x,&y,&x2,&y2);
	x+=1000;y+=1000;x2+=1000;y2+=1000;//判断负数 
	MAX=(db)abs(x2-x)*(db)abs(y-y2);
	for(int i=1;i<=n;i++) {
		scanf("%d%d",&a[i].x,&a[i].y);
		a[i].x+=1000;a[i].y+=1000;
	}
	dfs(1,0.0);
	printf("%.0lf",MAX-ans); 
}

T2:
题目大意
虽然msh长大了,但她还是很喜欢找点游戏自娱自乐。有一天,她在纸上写了一串数字:1,1,2,5,4。接着她擦掉了一个1,结果发现剩下1,2,4都在自己所在的位置上,即1在第1位,2在第2位,4在第4位。她希望擦掉某些数后,剩下的数列中在自己位置上的数尽量多。她发现这个游戏很好玩,于是开始乐此不疲地玩起来……不过她不能确定最多能有多少个数在自己的位置上,所以找到你,请你帮忙计算一下!
输入
第一行为一个数n,表示数列的长度。
接下来一行为n个用空格隔开的正整数,第i行表示数Ai。
输出
一行一个整数,表示擦掉某些数后,最后剩下的数列中最多能有多少个数在自己的位置上,即Ai=i最多能有多少。
样例输入
5
1 1 2 5 4
输出
3
题解
dp大水题,设f[i][j]表示第i个数这时已经选了j个数的最大匹配

f[i][j]=max(f[i-1][j],f[i-1][j-1]);
if(a[i]==j)++f[i][j];

就不写注释了,太简单了。
CODE

#include<cstdio>
using namespace std;
int n,a[1001],f[1001][1001],ans=0;
inline int max(int x,int y) {
	if(x>y)return x;
	return y;
}
int main() {
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=n;i++)
		for(int j=0;j<=i;j++) {
			int x=0;
			if(a[i]==i-j)x=1;
			f[i][j]=f[i-1][j]+x;
			if(j-1>=0)f[i][j]=max(f[i][j],f[i-1][j-1]);
			if(f[i][j]>ans)ans=f[i][j];
		}
	printf("%d",ans);
}

T3:
题目大意
一个软件开发公司同时要开发两个软件,并且要同时交付给用户,现在公司为了尽快完成这一任务,将每个软件划分成m个模块,由公司里的技术人员分工完成,每个技术人员完成同一软件的不同模块的所用的天数是相同的,并且是已知的,但完成不同软件的一个模块的时间是不同的,每个技术人员在同一时刻只能做一个模块,一个模块只能由一个人独立完成而不能由多人协同完成。一个技术人员在整个开发期内完成一个模块以后可以接着做任一软件的任一模块。写一个程序,求出公司最早能在什么时候交付软件。
输入
输入文件第一行包含两个由空格隔开的整数n和m,其中1<=n<=100,1<=m<=100,接下来的n行每行包含两个用空格隔开的整数d1和d2,d1表示该技术人员完成第一个软件中的一个模块所需的天数,d2表示该技术人员完成第二个软件中的一个模块所需的天数,其中1<= d1,d2<=100。
输出
输出文件仅有一行包含一个整数d,表示公司最早能于d天后交付软件。
样例输入
3 20
1 1
2 4
1 6
样例输出
18
题解
一看到这道题是求最优解,马上就能想到一个n的五次方的dp,设f[i][j][k]表示到第i个人已经做了软件一的j个模块,软件2的k个模块,那么状态转移方程就是:

f[i][j][k]=min(f[i][j][k],max(f[i-1][j-s][k-s2],d1[i]*s+d2[i]*s2);

可是这样很明显会TLE,所以我们考虑二分答案
假设现在二分出来一个mid,用dp判断它是否可行
设一个状态,f[i][j]表示到第i个人已经做了软件1的j个模块,所做了的最多的软件2的模块数。
那么状态转移方程就是:

f[i][j]=max(f[i][j],f[i-1][j-k]+(mid-d1[i]*k)/d2[i]);
mid-d1[i]*k表示用去做软件1的剩余时间,那么除以d2[i]就是求出最多能做的软件2的模块数

最后判断mid是否可行就是看f[n][m]是否>=m,是的话就合法,否则不合法
Code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int n,m,l=0,r=0,f[101][101];
struct node {
	int x,y;
}a[101];
int main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) {
		scanf("%d%d",&a[i].x,&a[i].y);
		r=max(r,(a[i].x+a[i].y)*m);//最大需用的天数 
	}
	while(l<=r) {//二分答案 
		int mid=(l+r)>>1;
		memset(f,-0x3f3f3f3f,sizeof(f));
		f[0][0]=0;
		for(int i=1;i<=n;i++)
			for(int j=0;j<=m;j++)
				for(int k=0;k<=min(j,mid/a[i].x);k++)
				f[i][j]=max(f[i][j],f[i-1][j-k]+(mid-k*a[i].x)/a[i].y);
		if(f[n][m]>=m)r=mid-1;
		else l=mid+1;
	}
	printf("%d",l);//最后答案就是l 
} 

T4:
题目大意
Black Box是一种原始的数据库。它可以储存一个整数数组,还有一个特别的变量 i 。最开始的时候Black Box是空的,而 i 等于 0。这个 Black Box 要处理一串命令。
命令只有两种:
ADD(x): 把 x 元素放进 Black Box;
GET: i 加 1 ,然后输出 Black box 中第 i 小的数。
记住:第 i 小的数,就是 Black Box里的数的按从小到大的顺序排序后的第 i 个元素。
现在要求找出对于给定的命令串的最好的处理方法。ADD 和 GET 命令分别最多有200000个。
现在用两个整数数组来表示命令串:

  1. A(1), A(2), …, A(M): 一串将要被放进Black Box的元素。每个数都是绝对值不超过2000000000的整数,M <=200000。例如上面的例子就是A=(3, 1, -4, 2, 8, -1000, 2).
  2. u(1), u(2), …, u(N): 表示第u(j)个元素被放进了Black Box里后就出现一个GET命令。例如上面的例子中u=(1, 2, 6, 6)。 输入数据不用判错。

输入
第一行,两个整数,M,N,
第二行,M个整数,表示A(1)……A(M),
第三行,N个整数,表示u(1)……u(N)。
输出
输出 Black Box 根据命令串所得出的输出串,一个数字一行。
样例输入
7 4
3 1 -4 2 8 -1000 2
1 2 6 6
样例输出
3
3
1
2
题解
这道题有多种方法,不过我太弱了,只会用对顶堆,下面来主要讲一下对顶堆。
我们维护两个堆,一个是大根堆,另一个是小根堆。
首先枚举每一个询问,遇到一个数,就把他加进大根堆,当大根堆的数量=i(第i个询问)时,就把当前的大根堆堆顶放进小根堆,如此这样下来,最后答案就会是小根堆的堆顶。做完之后,我们就要把小根堆的堆顶放回大根堆,因为我们已经把它用过了,那么这两个堆就会有一个性质:小根堆里的所有数都大于等于大根堆里的所有数。
Code

#include<cstdio>
#include<algorithm>
using namespace std;
const int inf=2100000000;
int m,n,a[200001],q[200001],num=0,d[200001],d2[200001],num2=0;
void up(int x) {//这里采用手打堆(因为我不会用STL优先队列) 
	while(x>1&&d[x]>d[x>>1]) {
		swap(d[x],d[x>>1]);
		x>>=1;
	}
}
void down(int x) {
	while((x<<1<=num&&d[x]<d[x<<1])||(((x<<1)|1)<=num&&d[x]<d[(x<<1)|1])) {
		int y=x<<1;
		if(y|1<=num&&d[y]<d[y|1])y|=1;
		swap(d[x],d[y]);
		x=y;
	}
}
void up2(int x) {
	while(x>1&&d2[x]<d2[x>>1]) {
		swap(d2[x],d2[x>>1]);
		x>>=1;
	}
}
void down2(int x) {
	while((x<<1<=num2&&d2[x]>d2[x<<1])||(((x<<1)|1)<=num2&&d2[x]>d2[(x<<1)|1])) {
		int y=x<<1;
		if(y|1<=num2&&d2[y]>d2[y|1])y|=1;
		swap(d2[x],d2[y]);
		x=y;
	}
}
int main() {
	scanf("%d%d",&m,&n);
	for(int i=1;i<=m;i++)scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)scanf("%d",&q[i]);
	for(int i=1;i<=n;i++) 
	{
		for(int j=q[i-1]+1;j<=q[i];j++)
		{
			d[++num]=a[j];
			up(num);//加入大根堆 
			if(num==i)//数量已满 
			{
				d2[++num2]=d[1];
				up2(num2);
				d[1]=d[num--];
				down(1);
			}
		}
		printf("%d\n",d2[1]);//小根堆堆顶就是答案 
		d[++num]=d2[1];//把小根堆堆顶返回大根堆 
		up(num);
		d2[1]=d2[num2--];
		down2(1);
	} 
}

请各位大佬多多指教

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值