【蓝桥杯国赛备赛题单笔记1】 DFS+二分

1.[蓝桥杯 2016 国 B] 机器人塔

题目描述

X 星球的机器人表演拉拉队有两种服装,A 和 B。

他们这次表演的是搭机器人塔。

类似:

     A
    B B
   A B A
  A A B B
 B B B A B
A B A B B A

队内的组塔规则是:

A 只能站在 AA 或 BB 的肩上。

B 只能站在 AB 或 BA 的肩上。

你的任务是帮助拉拉队计算一下,在给定 A 与 B 的人数时,可以组成多少种花样的塔。

输入格式

输入一行两个整数 M M M N N N,空格分开( 0 < M , N < N + M < 231 0<M,N<N+M<231 0<M,N<N+M<231,保证存在 k ∈ N k\in \mathbb{N} kN N + M = k ( k − 1 ) 2 N+M=\frac{k(k-1)}{2} N+M=2k(k1)),分别表示 A、B 的人数。

输出格式

要求输出一个整数,表示可以产生的花样种数。

样例 #1

样例输入 #1

1 2

样例输出 #1

3

样例 #2

样例输入 #2

3 3

样例输出 #2

4

提示

时限 1 秒, 256M。蓝桥杯 2016 年第七届

我的想法:一开始以为是DP之类的东西(看状态很好转移,其实是最近 DP看的多
思路:本题类似于费解的开关
在这里插入图片描述
注意到数据范围很小(塔最多21层),有本题题意可知最后一层确定那么上面的都确定了(和费解的开关类似),那么暴力枚举最后一层的情况再逐一判断是否符合条件即可,主要还是位运算要熟练(自己敲不顺畅,敲不太出来)

2. 油滴扩展

题目描述

在一个长方形框子里,最多有 N N N 个相异的点,在其中任何一个点上放一个很小的油滴,那么这个油滴会一直扩展,直到接触到其他油滴或者框子的边界。必须等一个油滴扩展完毕才能放置下一个油滴。那么应该按照怎样的顺序在这 N N N 个点上放置油滴,才能使放置完毕后所有油滴占据的总体积最大呢?(不同的油滴不会相互融合)

注:圆的面积公式 V = π r 2 V = \pi r^2 V=πr2,其中 r r r 为圆的半径。

输入格式

第一行,一个整数 N N N

第二行,四个整数 x , y , x ′ , y ′ x, y, x', y' x,y,x,y,表示长方形边框一个顶点及其对角顶点的坐标。

接下来 N N N 行,第 i i i 行两个整数 x i , y i x_i, y_i xi,yi,表示盒子内第 i i i 个点的坐标。

输出格式

一行,一个整数,长方形盒子剩余的最小空间(结果四舍五入输出)。

样例 #1

样例输入 #1

2
20 0 10 10
13 3
17 7

样例输出 #1

50

提示

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 6 1 \le N \le 6 1N6,坐标范围在 [ − 1000 , 1000 ] [-1000, 1000] [1000,1000] 内。

思路: 题目询问什么顺序放置的油滴面积最大,注意数据范围<=10,那么直接暴力枚举放置油滴的顺序统计答案即可。本步骤可以DFS也可以用更简单的next_permutation实现,值得注意的需要使用的一个那个数组为结构体数组,next_permutation无法直接对其进行排列,那么可以创建一个idx数组,初始化为

for(int i=1;i<=n;i++)	idx[i]=i;

然后直接对idx进行next_permutation排列即可,使用时将对应的下标ij变为idx[i]idx[j]

其次就是如何计算的问题(方法和如何计算比较清晰和简便):

  1. pai的值可以使用acos(-1)获取
  2. 对于给出了上下两个顶点但是不确定左右时,那么这样表示会更清晰:
double left=min(fx,ex),right=max(fx,ex),down=min(fy,ey),up=max(fy,ey);
  1. 提前预处理出每个点之间的距离
  2. 对于第一个放置的点,它只需要考虑对边界的碰撞,那么它的半径为:
p[idx[i]].r=min(min(tx-left,right-tx),min(ty-down,up-ty));
  1. 对于之后放置的点,除了考虑对边界的碰撞,还需要考虑对其他圆的碰撞,对其他圆碰撞的计算方法为,需要注意的细节是如果该点在某个点的圆内,应该特判跳过:
p[idx[i]].r=dis[idx[i]][idx[j]]-p[idx[j]].r
  1. 使用cout<<floor(alls-res+0.5);进行输出会出现奇奇怪怪的输出错误(答案是对的)所以这样输出取整会更好cout<<int(alls-res+0.5)<<endl;

code:

#include <bits/stdc++.h>
#include <math.h>
using namespace std;
const int N=10;
struct node{
	double x,y,r;
}p[N];
double fx,fy,ex,ey,dis[N][N];
int idx[N];

int main(){
	int n;
	cin>>n>>fx>>fy>>ex>>ey;
	for(int i=1;i<=n;i++)	cin>>p[i].x>>p[i].y;
	for(int i=1;i<=n;i++)	idx[i]=i;
	for(int i=1;i<=n;i++)	
		for(int j=1;j<=n;j++)
			dis[i][j]=sqrt((p[i].x-p[j].x)*(p[i].x-p[j].x)+(p[i].y-p[j].y)*(p[i].y-p[j].y));
	double left=min(fx,ex),right=max(fx,ex),down=min(fy,ey),up=max(fy,ey);
	double res=0;
	do{
		double cur=0;
		for(int i=1;i<=n;i++){
			if(i==1){
				double tx=p[idx[i]].x,ty=p[idx[i]].y;
				p[idx[i]].r=min(min(tx-left,right-tx),min(ty-down,up-ty));
				cur+=p[idx[i]].r*p[idx[i]].r*acos(-1);
			}
			else{
				double tx=p[idx[i]].x,ty=p[idx[i]].y;
				p[idx[i]].r=min(min(tx-left,right-tx),min(ty-down,up-ty));
				for(int j=i-1;j>=1;j--){
					if(dis[idx[i]][idx[j]]-p[idx[j]].r<=0){
						p[idx[i]].r=0;
						break;
					}
					p[idx[i]].r=min(p[idx[i]].r,dis[idx[i]][idx[j]]-p[idx[j]].r);
				}
				cur+=p[idx[i]].r*p[idx[i]].r*acos(-1);
			}
		}
		res=max(res,cur);
	}while(next_permutation(idx+1,idx+1+n));
	double alls=(right-left)*(up-down);
	cout<<int(alls-res+0.5)<<endl;
	
	return 0;
}

3.[蓝桥杯 2012 省 ] 移动字母

在这里插入图片描述
在这里插入图片描述
思路: 一开始看这题的时候居然没一个具体的思路(提高课白学了),没看出是简化版的八数码,主要是被不知道怎么判断无解情况卡住了,以为要发现什么规律才能判断无解,看题没思路的时候都可以先从数据量出发,发现数据量十分小,把所有状态枚举完也才6!的复杂度,那么可以考虑直接用BFS把全部状态搜出来,如果没搜到目标,那么就是无解。
实现需要注意的地方
从字符串的位置到坐标的位置:x=i%3,y=i/3

code:

#include <bits/stdc++.h>
using namespace std;

int dx[]={1,-1,0,0},dy[]={0,0,1,-1};

int solve(string start){
  unordered_map<string,int>bk;
  queue<string>q;
  q.push(start);
  bk[start]=true;
  string ed="ABCDE*";

  while(q.size()){
    auto t=q.front();
    q.pop();
	if(t==ed)	return 1;
    int x,y,pos;
    for(int i=0;i<t.size();i++)
     if(t[i]=='*') x=i%3,y=i/3,pos=i;

    for(int i=0;i<4;i++){
      int tx=x+dx[i],ty=y+dy[i];
	  string tmp=t;
      int tpos=ty*3+tx;
      if(tx<0||tx>2||ty<0||ty>1)  continue;
      swap(t[tpos],t[pos]);
      if(bk[t]){
      	t=tmp;
      	continue;
	  }
      q.push(t);
      bk[t]=true;
      t=tmp;
    }
  }
  return 0;
}

int main()
{
  int t;
  cin>>t;
  while(t--){
    string s;
    cin>>s;
    cout<<solve(s)<<endl;
  }
  return 0;
}

4.[ABC144E] Gluttony

题面翻译

【题目描述】

高桥君参加大胃王比赛。比赛由 N N N人组成的团队为基本单位参赛,高桥君的队伍的队员从 1 ∼ N 1\sim N 1N编号。第 i i i名队员的消化代价为 A i A_i Ai

比赛有 N N N种不同的食物,每位队员需要负责吃掉其中一种食物,不能有两名队员吃同一种食物,也不能让一名队员吃多与一种食物。第 j j j种食物的难吃程度为 F j F_j Fj。 消化代价 x x x的队员吃完难吃程度 y y y的食物需要花费 x × y x\times y x×y秒。 整个队伍的成绩是 N N N名队员吃完食物花费时间的最大值。

比赛前,高桥君的队伍会进行修行。一次修行可以将一名消化代价大于0的队员的消化代价减少1。由于修行需要消耗庞大的食费,因此最多只能进行 K K K次修行。

通过修行和适当选择每位队员吃的食物,高桥队在比赛中能够获得的最好成绩是多少?

【输入格式】

第1行,两个正整数 N , K N,K N,K

第2行, N N N个正整数 A 1 , A 2 , ⋯   , A N A_1,A_2,\cdots,A_N A1,A2,,AN

第3行, N N N个正整数 F 1 , F 2 , ⋯   , F N F_1,F_2,\cdots,F_N F1,F2,,FN

【输出格式】
输出高桥队的最好成绩

【输入样例#1】

3 5
4 2 1
2 3 1

【输出样例#1】

2

【样例#1说明】

1号队员进行4次修行,吃2号食物,花费0秒。

2号队员进行1次修行,吃3号食物,花费1秒。

3号队员进行0次修行,吃1号食物,花费2秒。

总成绩取最大值2秒。

【数据说明】

1 ≤ N ≤ 2 × 1 0 5 1 \le N \le 2\times 10^5 1N2×105

0 ≤ K ≤ 1 0 18 0 \le K \le 10^{18} 0K1018

1 ≤ A i ≤ 1 0 6 1 \le A_i \le 10^6 1Ai106

1 ≤ F i ≤ 1 0 6 1 \le F_i \le 10^6 1Fi106

思路:求最大值最小问题,裸的二分。现在的关键在于如何进行check,可以这样贪心:对选手的消化代价和食物恶心程度进行排序,那么对于食物的分配则为消化代价小的对恶心的一一对应,如果代价不满足check,那么对其进行训练,如果训练点数<0,那么表示本次check的mid太小了,返回false。
贪心证明
在这里插入图片描述

code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+10;
ll a[N],f[N],n,k;

bool check(ll x){
	ll t=k;
	for(int j=1,i=n;i>=1;i--,j++){
		ll cur=x/f[j];
		if(a[i]>cur)	t-=a[i]-cur;
		if(t<0)	return true;
	}
	return false;
}

int main()
{
	cin.tie(0);
	cin>>n>>k;
	ll maxa=0,maxf=0;
	for(int i=1;i<=n;i++)	cin>>a[i],maxa=max(maxa,a[i]);
	for(int i=1;i<=n;i++)	cin>>f[i],maxf=max(maxf,f[i]);
	sort(f+1,f+1+n);
	sort(a+1,a+1+n);
	ll l=-1,r=maxa*maxf+1;
	ll mid;
	while(l+1!=r){
		mid=l+r>>1;
		if(check(mid))	l=mid;
		else	r=mid;
	}
	cout<<r;
	return 0;
}

5.[ABC153F] Silver Fox vs Monster

题面翻译

S i l v e r   F o x Silver\ Fox Silver Fox 在打怪。他的面前有 N N N 只怪。
N N N 只怪站在一行上。为了方便,我们把他们看作在一个数轴上。其中第 i i i 只怪物的坐标为 X i X_i Xi ,有 H i H_i Hi 点血量。
他可以用炸弹来攻击这些怪物。如果选择在位置 x x x 投放炸弹,那么坐标位于 x − D x-D xD x + D x+D x+D 之间的怪物会全部减少 A A A 点血量。
如果所有怪物的血量都 ≤ 0 \le 0 0,那么他就获胜了。
找到在获胜的情况下, S i l v e r   F o x Silver\ Fox Silver Fox 需要投放的炸弹最少有多少个。

样例 #1

样例输入 #1

3 3 2
1 2
5 4
9 2

样例输出 #1

2

样例 #2

样例输入 #2

9 4 1
1 5
2 4
3 3
4 2
5 1
6 2
7 3
8 4
9 5

样例输出 #2

5

样例 #3

样例输入 #3

3 0 1
300000000 1000000000
100000000 1000000000
200000000 1000000000

样例输出 #3

3000000000

思路: 由题目易知爆炸点应该贪心地将爆炸左端点放在一个点且需要排序从左向右做,现在的问题就来到了要怎么维护爆炸的信息。可以使用差分来更改,具体用法:对于每一个怪物点需要b[i]+=b[i-1];//b为差分数组 从前面得到爆炸的信息,这样差分的 O ( n ) O (n) O(n) 查询就体现在了遍历每一个怪物,相当于从0构建一个维护伤害的差分数组,并且层层传递下去,同时还需要维护结束爆炸的信息,那么就可以使用二分找到爆炸结束的点,并对其进行更改。

code:

#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int N=1e6+10;
pii a[N];
ll b[N],d,damage,n,res;

int main()
{
	cin>>n>>d>>damage;
	for(ll i=1;i<=n;i++)	cin>>a[i].x>>a[i].y;
	for(ll i=1;i<=n;i++)	a[i].y=(a[i].y+damage-1)/damage;
	//这是一个上取整操作
	sort(a+1,a+1+n);
	for(ll i=1;i<=n;i++){
		b[i]+=b[i-1];
		if(a[i].y-b[i]<=0)	continue;
		ll cur=a[i].y-b[i];
		b[i]+=cur;
		res+=cur;
		ll l=i-1,r=n+1,mid;
		while(l+1!=r){
			mid=l+r>>1;
			if(a[mid].x<=a[i].x+2*d)	l=mid;
			else	r=mid;
		}
		b[l+1]-=cur;
	}
	cout<<res;
	
	return 0;
}

6.[蓝桥杯 2017 国 A] 区间移位

题目描述

数轴上有 n n n 个闭区间: D 1 , ⋯   , D n D_1, \cdots ,D_n D1,,Dn

其中区间 D i D_i Di 用一对整数 [ a i , b i ] [a_i,b_i] [ai,bi] 来描述,满足 a i < b i a_i<b_i ai<bi

已知这些区间的长度之和至少有 10000 10000 10000

所以,通过适当的移动这些区间,你总可以使得他们的“并”覆盖 [ 0 , 10000 ] [0,10000] [0,10000] ——也就是说 [ 0 , 10000 ] [0,10000] [0,10000] 这个区间内的每一个点都落于至少一个区间内。

你希望找一个移动方法,使得位移差最大的那个区间的位移量最小。

具体来说,假设你将 D i D_i Di 移动到 [ a i + c i , b i + c i ] [a_i+c_i,b_i+c_i] [ai+ci,bi+ci] 这个位置。你希望使得 max ⁡ i = 1 n { ∣ c i ∣ } \max\limits_{i=1}^n\{|c_i|\} i=1maxn{ci} 最小。

输入格式

输入的第一行包含一个整数 n n n,表示区间的数量。

接下来有 n n n 行,每行 2 2 2 个整数 a i , b i a_i,b_i ai,bi,以一个空格分开,表示区间 [ a i , b i ] [a_i,b_i] [ai,bi]

保证区间的长度之和至少是 10000 10000 10000

输出格式

输出一个数字,表示答案。如果答案是整数,只输出整数部分。如果答案不是整数,输出时四舍五入保留一位小数。

样例 #1

样例输入 #1

2
10 5010
4980 9980

样例输出 #1

20

样例 #2

样例输入 #2

4
0 4000
3000 5000
5001 8000
7000 10000

样例输出 #2

0.5

提示

【样例解释】

样例 1:第一个区间往左移动 10 10 10;第二个区间往右移动 20 20 20

样例 2:第 2 2 2 个区间往右移 0.5 0.5 0.5;第 3 3 3 个区间往左移 0.5 0.5 0.5 即可。

【数据范围】

对于 30 % 30\% 30% 的评测用例, 1 ≤ n ≤ 10 1 \le n \le 10 1n10

对于 100 % 100\% 100% 的评测用例, 1 ≤ n ≤ 10000 1 \le n \le 10000 1n10000 0 ≤ a i < b i ≤ 10000 0 \le a_i<b_i \le 10000 0ai<bi10000

思路: 看到最大值最小则可以联想到二分。

  1. 本题要求对答案保留一位小数四舍五入,二分时可以对二分的值 ∗ 10 *10 10处理,最后答案再 / 10 /10 /10,这样就可以避免浮点二分。
  2. 对每个区间的右端点进行降序排序,如果相同则按左端点降序排序,处理的时候顺序处理,维护一个当前区间右端点,如果当前区间左端点能移动到当先区间右端点左边,那么则可以更新,对于每一次循环找到第一个能够加入区间且未被加入过的区间,更新右端点,如果找不到,说明本次check的值过小,更新l=mid,循环结束后,如果维护的区间右端点能够 > = 10000 >=10000 >=10000,那么表明本次check的过大,更新r=mid
  3. 更新的方法:
        if (ans - p[j].l >= x)    ans = max(ans, p[j].r + x); //右边可以延伸
        else    ans += p[j].r - p[j].l; // 右边不能延伸
  1. check传值的时候可以把传入的x声明为double,这样可以自动四舍五入
  2. 奇妙的循环方式(可以保证把全部区间都考虑到):
    for (int i = 1, j = 1; i <= n && ans < 10000; j = i){
    //略
     if (i == j)    i++;
    }

code

#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int N=1e6+10;
bool vis[N];
int n;
struct node{
	double l,r;
}p[N];

bool cmp(node a,node b){
	if(a.r!=b.r)	return a.r<b.r;
	return a.l<b.l;
}

bool check(double x){
	x*=0.1;
	double ans=0;
	memset(vis,false,sizeof vis);
	for(int i=1,j=1;i<=n&&ans<10000;j=i){
		while((vis[j]||p[j].l>x+ans)&&j<=n)	j++;
		if(j==n+1)	return true;
		vis[j]=true;
		
        if (ans - p[j].l >= x)    ans = max(ans, p[j].r + x); //右边可以延伸
        else    ans += p[j].r - p[j].l; // 右边不能延伸
		
		if(i==j)	i++;
	}
	if(ans>=10000)	return false;
	else	return true;
}

int main()
{

	cin>>n;
	for(int i=1;i<=n;i++)	cin>>p[i].l>>p[i].r;
	sort(p+1,p+1+n,cmp);
	
	int l=-1,r=100001,mid;
	while(l+1!=r){
		mid=(l+r)/2;
		if(check(mid))	l=mid;
		else	r=mid;
	}
	cout<<r*0.1;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值