第四周集训

牛客补题

游游的排列构造

游游定义一个排列中,满足以下条件的为“好元素”:
对于第i个元素 ai 而言,ai为前i个元素的最大值。例如,[3,1,5,2,4]中,第一个和第三个元素是好元素。

游游希望你构造一个长度为n的排列,其中有k个好元素,且任意两个好元素都不相邻。你能帮帮她吗?

排列的定义:由 1 到 n所有正整数组成的长度为n的数组,每个正整数出现恰好一次。

分析

两个好元素不相邻 那插空。且第一个数不管是几都是好元素

代码

#include<bits/stdc++.h>
using namespace std;
int a[100010];
int main(){
    int n,k;
    cin >> n >> k;
    for(int i = 1; i <= n; i ++)
       a[i] = i;
    
    for(int i = 1; i <= n - k; i ++)
    {
        if(i <= k)
        cout << a[n - k + i] << " ";
     cout << a[i] << " ";//插空
     
    }
    if(k > n / 2) cout << a[n];
}

贪心算法练习

# [NOIP2002 提高组] 均分纸牌

题目描述

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

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

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

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

移动 3 3 3 次可达到目的:

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

输入格式

第一行共一个整数 N N N,表示纸牌堆数。
第二行共 N N N 个整数 A 1 , A 2 , … , A N A_1,A_2,\ldots,A_N A1,A2,,AN,表示每堆纸牌初始时的纸牌数。

输出格式

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

样例 #1

样例输入 #1

4
9 8 17 6

样例输出 #1

3

提示

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 100 1 \le N \le 100 1N100 1 ≤ A i ≤ 10000 1 \le A_i \le 10000 1Ai10000

分析

1.算平均数。

2.求每堆纸牌与平均数的关系(多1记为1,少1记为-1)。

3.当q[y](第y堆纸牌与平均数的关系)不等于0时,q[y+1]=q[y+1]+q[y],移动次数加1。

代码

#include<bits/stdc++.h>//均分纸牌 
using namespace std;
const int N=105;
int a[N],n,sum=0,temp[N]; 
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		sum += a[i];
	}
	int ave = sum / n;//每堆排的数量 
	int ans=0;//移动次数 
	for(int i=1;i<n;i++){
	 if (a[i] == ave) continue;
	 else {
	 	a[i+1] += a[i] - ave;//向右移动a【i】 - ave;
		 ans++; 
	 }
	}
	cout<< ans;	
    return 0;
} 

# 凌乱的yyy / 线段覆盖

题目背景

快 noip 了,yyy 很紧张!

题目描述

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

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

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

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

输入格式

第一行是一个整数 n n n,接下来 n n n 行每行是 2 2 2 个整数 a i , b i   ( a i < b i ) a_{i},b_{i}\ (a_{i}<b_{i}) ai,bi (ai<bi),表示比赛开始、结束的时间。

输出格式

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

样例 #1

样例输入 #1

3
0 2
2 4
1 3

样例输出 #1

2

提示

  • 对于 20 % 20\% 20% 的数据, n ≤ 10 n \le 10 n10
  • 对于 50 % 50\% 50% 的数据, n ≤ 1 0 3 n \le 10^3 n103
  • 对于 70 % 70\% 70% 的数据, n ≤ 1 0 5 n \le 10^{5} n105
  • 对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 6 1\le n \le 10^{6} 1n106 0 ≤ a i < b i ≤ 1 0 6 0 \le a_{i} < b_{i} \le 10^6 0ai<bi106

分析

只需要先将每一场比赛以结束时间排一次序,得到一个顺序的结构体数组,存储最前面一节课的下课时间,然后以结束时间为准,在这相同结束时间比赛中下,如果有比赛的开始时间小于等于记录的结束时间,直接参加比赛,然后计数,最后输出总次数就可以了。

代码

#include<bits/stdc++.h>//(万能库)
struct px{//(定义一个结构体数组,分别储存开始时间和结束时间)
    int a;//(开始时间)
    int b;//(结束时间)
}x[2000000];

bool cmp(px x,px y){//(不管开始时间,直接按照结束时间排序)
    return x.b<y.b;
}
using namespace std;
int main()
{
    int n,sum=1,mi;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) cin>>x[i].a>>x[i].b;//(读入数据)
    sort(x+1,x+n+1,cmp);//(排序)
    mi=x[1].b;//(无脑记录第一个值)
    int j=1;
    while(j<=n){//(未优化的超长循环)
        j++;
        if(x[j].a>=mi){//(找到符合要求的比赛,记录,参加)
            sum++;//(计数)
            mi=x[j].b;
	    }
    }
    cout<<sum;//(输出)
    return 0;
}

# 删数问题

题目描述

键盘输入一个高精度的正整数 N N N(不超过 250 250 250 位),去掉其中任意 k k k 个数字后剩下的数字按原左右次序将组成一个新的非负整数。编程对给定的 N N N k k k,寻找一种方案使得剩下的数字组成的新数最小。

输入格式

输入两行正整数。

第一行输入一个高精度的正整数 n n n

第二行输入一个正整数 k k k,表示需要删除的数字个数。

输出格式

输出一个整数,最后剩下的最小数。

样例 #1

样例输入 #1

175438 
4

样例输出 #1

13

分析

每一步总是选择一个使剩下的数最小的数字删去;
即按高位到低位的顺序搜索,若各位数字递增,则删除最后一个数字,否则删除第一个递减区间的首字符,这样删一位便形成了一个新数字串。然后回到数字串首,按上述规则再删下一个数字。重复以上过程s次为之,剩下的数字串便是问题的解

代码

#include<bits/stdc++.h>
using namespace std;
string n;
int s,len,k;
int main(){
	cin>>n>>s;
	len=n.size();
	while(s--){
		k = len;
		for(int i=0;i<len;i++){
			if(n[i]>n[i+1]){
				k=i;
				break;
			}
		}
		n.erase(k,1);//删除函数,就是从第i个位置连续删1个。
		len--; 
	}
	while(n[0]=='0') n.erase(0,1);// 删去前导 0 ,l<len-1防止10 和 1 的数据啥也不输出,减去一个‘1 ’输出 0
	if(!n.empty()) cout<<n<<endl;
	else cout<<"0"<<endl;
	return 0;
}

注意

就是删去前导0,不然的话,输入10和1 ,就会啥也不输出


树和二叉树练习–利用递归解决问题

# [NOIP2001 普及组] 求先序排列

题目描述

给出一棵二叉树的中序与后序排列。求出它的先序排列。(约定树结点用不同的大写字母表示,且二叉树的节点个数 $ \le 8$)。

输入格式

共两行,均为大写字母组成的字符串,表示一棵二叉树的中序与后序排列。

输出格式

共一行一个字符串,表示一棵二叉树的先序。

样例 #1

样例输入 #1

BADC
BDCA

样例输出 #1

ABCD

分析

首先,一点基本常识,给你一个后序遍历,那么最后一个就是根(如ABCD,则根为D)。

因为题目求先序,意味着要不断找根。

那么我们来看这道题方法:(示例)

中序ACGDBHZKX,后序CDGAHXKZB,首先可找到主根B;

那么我们找到中序遍历中的B,由这种遍历的性质,可将中序遍历分为ACGD和HZKX两棵子树,

那么对应可找到后序遍历CDGA和HXKZ(从头找即可)

从而问题就变成求1.中序遍历ACGD,后序遍历CDGA的树 2.中序遍历HZKX,后序遍历HXKZ的树;

接着递归,按照原先方法,找到1.子根A,再分为两棵子树2.子根Z,再分为两棵子树。

就按这样一直做下去(先输出根,再递归);

模板概括为:

  1. step1:找到根并输出
  2. step2:将中序,后序各分为左右两棵子树;
  3. step3:递归,重复step1,2;

代码

#include<bits/stdc++.h>
using namespace std;
//const int N = 1e6+10;
char a[105]/*存中序*/,b[105];/*存后序*/;
void tree(int d,int e){//递归函数
	int i,f,c,j;
	c = 0; //c归零
	if(d > e ) return;//查找范围为d-e(一颗树),范围中没东西了,结束 
	for(j=strlen(a) - 1;j>=0;j--){//倒着找,以便找出最后一个
		for(i = d;i<=e;i++){//!!!整棵树都要找!!
			if(a[i] == b[j]){//!!!整棵树都要找!!
				c = i;
				break;//c有变动,退出循环(没变动说明没找到或者最后才找到) 
			}
		}
		if(c) break;
	}
	cout<<a[c];//输出根
	tree(d,c-1);//左子树先
	tree(c+1,e);
} 

int main()
{
	int c;
	scanf("%s%s",a,b);
	tree(0,strlen(a) - 1);
	
}

# 新二叉树

题目描述

输入一串二叉树,输出其前序遍历。

输入格式

第一行为二叉树的节点数 n n n。( 1 ≤ n ≤ 26 1 \leq n \leq 26 1n26)

后面 n n n 行,每一个字母为节点,后两个字母分别为其左右儿子。特别地,数据保证第一行读入的节点必为根节点。

空节点用 * 表示

输出格式

二叉树的前序遍历。

样例 #1

样例输入 #1

6
abc
bdi
cj*
d**
i**
j**

样例输出 #1

abdicj

分析

给出前序,组成新的二叉树。画图理解会更加直观。

代码

#include<bits/stdc++.h>
using namespace std;
//const int N = 1e6+10;
//char a[30]/*存中序*/,b[105];/*存后序*/;
struct node{
	char l,r;
}t[130];
char a,a1;

void tree(char x)
{
	if(x=='*') return;//如果是 * 说明此乃空节点,那就不用再往下探了 
	cout << x;//先把它给输出出来,碰着一个就踢出去一个,输出的顺序是可以保障的
	tree(t[x].l);//找到他的左孩子,继续往下探(如果左孩子是*的话,会返回的,可以看上一句的上一句) 
	tree(t[x].r);//找到他的右孩子,继续向下探索
	
/*这里我举个例子: 例如输入abc和bcd,a的ASC码是73,所以t[73].l是b(ASC码74),接着再从b开始探,t[74].l之前有过输入,是个c。再从序号为'c'(75)的t数组继续往下探索,一探索到*,就会往回跑。回到c数组的r,往回探,所以顺序问题可以保证,要是还是理解不了,可以画画试试,亲测有效。这个函数和输入其实就是在数组的各个部分之间不断穿梭,用字符的ASC码值作为连接节点的线,数组的左右孩子就是下一个要寻找的数组的代号*/ 
}

int main()
{
	int n;
	cin>>n;
	cin>>a1;
	cin>>t[a1].l;
	cin>>t[a1].r;//输入第一个字母,第一个字母比较特殊,所以单独输入,左孩子,a1所代表的字符再次会转换为ASC码 , 
	for(int i=2;i<=n;i++){
		
		cin>>a;
		cin>>t[a].l;
		cin>>t[a].r;
		
	}
	tree(a1);//进入函数,用的是递归
	return 0;
	
}

# [NOIP2004 普及组] FBI 树

题目描述

我们可以把由 0 和 1 组成的字符串分为三类:全 0 串称为 B 串,全 1 串称为 I 串,既含 0 又含 1 的串则称为 F 串。

FBI 树是一种二叉树,它的结点类型也包括 F 结点,B 结点和 I 结点三种。由一个长度为 2 N 2^N 2N 的 01 串 S S S 可以构造出一棵 FBI 树 T T T,递归的构造方法如下:

  1. T T T 的根结点为 R R R,其类型与串 S S S 的类型相同;
  2. 若串 S S S 的长度大于 1 1 1,将串 S S S 从中间分开,分为等长的左右子串 S 1 S_1 S1 S 2 S_2 S2;由左子串 S 1 S_1 S1 构造 R R R 的左子树 T 1 T_1 T1,由右子串 S 2 S_2 S2 构造 R R R 的右子树 T 2 T_2 T2

现在给定一个长度为 2 N 2^N 2N 的 01 串,请用上述构造方法构造出一棵 FBI 树,并输出它的后序遍历序列。

输入格式

第一行是一个整数 N ( 0 ≤ N ≤ 10 ) N(0 \le N \le 10) N(0N10)

第二行是一个长度为 2 N 2^N 2N 的 01 串。

输出格式

一个字符串,即 FBI 树的后序遍历序列。

样例 #1

样例输入 #1

3
10001011

样例输出 #1

IBFBBBFIBFIIIFF

提示

对于 40 % 40\% 40% 的数据, N ≤ 2 N \le 2 N2

对于全部的数据, N ≤ 10 N \le 10 N10

分析

1.建树。按照题意是在递归过程中建立树,建树的方法实际上就是树的先序遍历(先根节点,再左右子树)。当本节点长度大于1时递归建立子树。

2.输出。而输出过程是对树的后序遍历(先左右子树,再根节点),这里有个技巧就是可以和建树过程集成在一起。只需将代码放在递归调用之后就可以了。

3.判断。最后是判断当前节点的FBI树类型,可以用B(初始值为1)保存全是‘0’的情况,如果遇到‘1’就将B置为0,用I(初始值为1)保存全是‘1’的情况,如果遇到‘0’就将I置为0。最后判断B和I中的值,如果两个都为0则输出F(不全为‘0’,不全为‘1’)。

代码

#include<bits/stdc++.h>
#include<string>
using namespace std;
string s;
int n;
void tree(int x,int y){
	if(y>x){
		tree(x,(x+y)/2); //先遍历左树
		tree((x+y+1)/2,y);//遍历右树
	}
	int b = 1,i = 1;
	for(int j=0;j<= y-x;j++){
		if(s[x+j] == '1') b=0;//数组中开始出现1
		else if(s[x+j] == '0') i=0;//数组中开始出现0
		
	} 
	if(b) cout<<"B";//b不为0
	else if(i){
		cout<<"I";
	} 
	else cout<<"F"; //b 和 i 都为0
}
int main()
{
	cin>>n>>s;
	tree(0,(1<<n)-1);//2的n次方为总节点 而减1是因为从0开始
	return 0;	
}

这道题给出了一个新的思路,想求一个都为 0 的数组,可以先让b = 1,当出现别的数字时 b = 0;这样更方便判断。

# 【模板】堆

题目描述

给定一个数列,初始为空,请支持下面三种操作:

  1. 给定一个整数 x x x,请将 x x x 加入到数列中。
  2. 输出数列中最小的数。
  3. 删除数列中最小的数(如果有多个数最小,只删除 1 1 1 个)。

输入格式

第一行是一个整数,表示操作的次数 n n n
接下来 n n n 行,每行表示一次操作。每行首先有一个整数 o p op op 表示操作类型。

  • o p = 1 op = 1 op=1,则后面有一个整数 x x x,表示要将 x x x 加入数列。
  • o p = 2 op = 2 op=2,则表示要求输出数列中的最小数。
  • o p = 3 op = 3 op=3,则表示删除数列中的最小数。如果有多个数最小,只删除 1 1 1 个。

输出格式

对于每个操作 2 2 2,输出一行一个整数表示答案。

样例 #1

样例输入 #1

5
1 2
1 5
2
3
2

样例输出 #1

2
5

提示

【数据规模与约定】

  • 对于 30 % 30\% 30% 的数据,保证 n ≤ 15 n \leq 15 n15
  • 对于 70 % 70\% 70% 的数据,保证 n ≤ 1 0 4 n \leq 10^4 n104
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 1 0 6 1 \leq n \leq 10^6 1n106 1 ≤ x < 2 31 1 \leq x \lt 2^{31} 1x<231 o p ∈ { 1 , 2 , 3 } op \in \{1, 2, 3\} op{1,2,3}

分析

1.堆是一颗完全二叉树
2.堆的顶端一定是“最大”,最小”的,但是要注意一个点,这里的大和小并不是传统意义下的大和小,它是相对于优先级而言的,当然你也可以把优先级定为传统意义下的大小,但一定要牢记这一点,初学者容易把堆的“大小”直接定义为传统意义下的大小,某些题就不是按数字的大小为优先级来进行堆的操作的
3.堆一般有两种样子,小根堆和大根堆,分别对应第二个性质中的“堆顶最大”“堆顶最小”,对于大根堆而言,任何一个非根节点,它的优先级都小于堆顶,对于小根堆而言,任何一个非根节点,它的优先级都大于堆顶(这里的根就是堆顶啦)

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
int h[N];
int len = 0;//堆的大小 
void push(int x){//要插入的数,上浮
	h[++len]=x;
	int i=len;//插入到堆底 
	while(i > 1 && h[i] < h[i/2]){//找到它的父亲且父亲比它大,那就交换 
		swap(h[i],h[i/2]);
		i/=2;//找到它的父亲 
	}
}
void pop(){//下沉,删除队头,调整堆
	h[1] = h[len]; //交换堆顶和堆底,然后直接弹掉堆底 
	len --;
	int i = 1;
	while(2 * i <=len){  //对该节点进行向下交换的操作 
		int s = 2 * i;
		if(s < len && h[s + 1] < h[s]){
			s ++;
		}
		if(h[s] < h[i]){
			swap(h[s],h[i]);
			i = s;
		}
		else{
			break;
		}
	}
}
int main(){
	int n;
	cin >> n;
	int op ,x;
	while(n--){
		cin >> op;
		if(op==1){
			cin >> x;
			push(x);//将x存到数组
		}
		else if(op ==2){
			cout << h[1] << endl;//输出堆顶
		}
		else{
			pop();
		}
	} 
}

事实上堆的插入就是把新的元素放到堆底,然后检查它是否符合堆的性质,如果符合就丢在那里了,如果不符合,那就和它的父亲交换一下,一直交换交换交换,直到符合堆的性质,那么就插入完成了

# [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G

题目描述

在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。

每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n − 1 n-1 n1 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 1 1 1 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

例如有 3 3 3 种果子,数目依次为 1 1 1 2 2 2 9 9 9 。可以先将 1 1 1 2 2 2 堆合并,新堆数目为 3 3 3 ,耗费体力为 3 3 3 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 12 12 12 ,耗费体力为 12 12 12 。所以多多总共耗费体力 = 3 + 12 = 15 =3+12=15 =3+12=15 。可以证明 15 15 15 为最小的体力耗费值。

输入格式

共两行。
第一行是一个整数 n ( 1 ≤ n ≤ 10000 ) n(1\leq n\leq 10000) n(1n10000) ,表示果子的种类数。

第二行包含 n n n 个整数,用空格分隔,第 i i i 个整数 a i ( 1 ≤ a i ≤ 20000 ) a_i(1\leq a_i\leq 20000) ai(1ai20000) 是第 i i i 种果子的数目。

输出格式

一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 2 31 2^{31} 231

样例 #1

样例输入 #1

3 
1 2 9

样例输出 #1

15

提示

对于 30 % 30\% 30% 的数据,保证有 n ≤ 1000 n \le 1000 n1000

对于 50 % 50\% 50% 的数据,保证有 n ≤ 5000 n \le 5000 n5000

对于全部的数据,保证有 n ≤ 10000 n \le 10000 n10000

分析

一开始看到这个题,就想到跟以前做的贪心题有点类似,大致思路也就是,先找到两个最小的,取出来,相加再放进去,不过我犯了一个致命性的错误,我每一次取之前都对剩下的进行排序,导致时间超时,后来不排序直接找到最小的和次小的,把它们相加再放进去,这样就省下来排序的时间了,直接过了。看到洛谷里面有人用优先队列做的,其实更简单,定义一个以质量从小到大的队列,每次出队两个,入队一个(入队的就是出队两个数的和),这样也不用排序,同样也可以过。

代码

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

int n;
int x,sum = 0;
priority_queue<int,vector<int>,greater<int>> q; //从小到大:
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++) {
		cin>>x;
		q.push(x); //x进入队列
	}
	for(int i=1;i<n;i++){
		int a = q.top();//最小的
		q.pop();
		int b = q.top();//次小的
		q.pop();
		sum += a+b;
		q.push(a+b);//将合并后的堆加入优先队列
	}
	cout<< sum <<endl;
	return 0;
}

# 中位数

题目描述

给定一个长度为 N N N 的非负整数序列 A A A,对于前奇数项求中位数。

输入格式

第一行一个正整数 N N N

第二行 N N N 个正整数 A 1 … N A_{1\dots N} A1N

输出格式

⌊ N + 1 2 ⌋ \lfloor \frac{N + 1}2\rfloor 2N+1 行,第 i i i 行为 A 1 … 2 i − 1 A_{1\dots 2i - 1} A12i1 的中位数。

样例 #1

样例输入 #1

7
1 3 5 7 9 11 6

样例输出 #1

1
3
5
6

提示

对于 20 % 20\% 20% 的数据, N ≤ 100 N \le 100 N100

对于 40 % 40\% 40% 的数据, N ≤ 3000 N \le 3000 N3000

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 100000 1 \le N ≤ 100000 1N100000 0 ≤ A i ≤ 1 0 9 0 \le A_i \le 10^9 0Ai109

分析

首先记录一个变量mid,记录答案(中位数)。建立两个堆,一个大根堆一个小根堆,大根堆≤mid的数,小根堆存 > mid的数。但我们在输出答案前需要对mid进行调整,如果小根堆和大根堆内元素相同,就无需处理,此时mid仍然是当前的中位数。如果两个堆中元素个数不同,那我们就需要进行调整。把元素个数较多的堆的堆顶作为mid,mid加入元素较少的堆。

代码

#include<bits/stdc++.h>
#include<queue>
using namespace std;
//typedef long long ll;
const int N = 1e5+5;
int a[N],n,mid;

priority_queue<int , vector<int> ,less<int> > q1;//大顶堆
priority_queue<int , vector<int> ,greater<int> > q2;//小顶堆

int main()
{
	cin>>n;
	cin >> a[1];
	mid = a[1];//mid初值是a[1]
      cout << mid << endl; 
	for(int i=2;i<=n;i++){
		cin>>a[i];
		if(a[i] > mid ) q2.push(a[i]);
		else q1.push(a[i]);
		if(i%2 == 1){//第奇数次加入
			while(q1.size() != q2.size()){
				if(q1.size() > q2.size()){
					q2.push(mid);
					mid = q1.top();
					q1.pop();
					
				}
				else{
					q1.push(mid);
					mid = q2.top();
					q2.pop();
				}
			}
			cout << mid <<endl;
		}
	} 
	return 0;
}

用两个堆,一个大根堆,一个小根堆,大根堆维护当前输入中较大的数,小根堆维护当前数组中较小的数。这样就保证了大根堆的堆顶是较小数的最大值,小根堆的堆顶是较大数中的最小值,然后当大根堆的元素个数为小根堆的元素个数+1的时候,大根堆的堆顶就是中位数。

# 最小函数值

题目描述

n n n 个函数,分别为 F 1 , F 2 , … , F n F_1,F_2,\dots,F_n F1,F2,,Fn。定义 F i ( x ) = A i x 2 + B i x + C i ( x ∈ N ∗ ) F_i(x)=A_ix^2+B_ix+C_i(x\in\mathbb N*) Fi(x)=Aix2+Bix+Ci(xN)。给定这些 A i A_i Ai B i B_i Bi C i C_i Ci,请求出所有函数的所有函数值中最小的 m m m 个(如有重复的要输出多个)。

输入格式

第一行输入两个正整数 n n n m m m

以下 n n n 行每行三个正整数,其中第 i i i 行的三个数分别为 A i A_i Ai B i B_i Bi C i C_i Ci

输出格式

输出将这 n n n 个函数所有可以生成的函数值排序后的前 m m m 个元素。这 m m m 个数应该输出到一行,用空格隔开。

样例 #1

样例输入 #1

3 10
4 5 3
3 4 5
1 7 1

样例输出 #1

9 12 12 19 25 29 31 44 45 54

提示

数据规模与约定

对于全部的测试点,保证 1 ≤ n , m ≤ 10000 1 \leq n,m\le10000 1n,m10000 1 ≤ A i ≤ 10 , B i ≤ 100 , C i ≤ 1 0 4 1 \leq A_i\le10,B_i\le100,C_i\le10^4 1Ai10,Bi100,Ci104

分析

建一个大根堆,存最小的数到第m小的数,第m小的数就理所当然的是堆顶了。 每次我们只需要比较新加进来的数比堆顶大还是比堆顶小,如果比堆顶小,将原来的堆顶丢掉,将新的数塞进去; 如若比堆顶大,根据该题题意,a>0&&b>0,函数对称轴x=−b/2∗a恒小于0,可以得出,y在x>0时是单调递增的,所以接下来的函数值y只会大不会小,可以直接break掉了

由于我们存储的时候用的是大根堆,所以记得要逆序输出,将m个数从小到大输出

代码

#include<bits/stdc++.h>
#include<queue>
using namespace std;
//typedef long long ll;
const int N = 1e4+5;
int s[N],n,m;

priority_queue<int> q;

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		int a,b,c;
		cin>>a>>b>>c;
		for(int j=1;j<=m;j++){
			int k = a*j*j+b*j+c;
			if(i == 1) q.push(k);
			else{
				if(k < q.top()){
					q.push(k);
					q.pop();
				}
				else break; //如果k已经大于第m小的数了,接下来k仍旧单调递增
                           //所以可以直接break掉,一个重要的优化
			}
		}
	}
	for(int i=1;i<=m;i++){
		s[i] = q.top();
		q.pop();
		
	}//记得要逆着输出!
	for(int i=m;i>=1;i--){
		cout<<s[i]<<" ";
	}
	return 0;
}

前m小的数,很容易想到 对顶堆的经典问题第k大数 并且这题非常良心,m的值不会改变

那么它的思想和对顶堆就非常类似了

对顶堆:以求第k大数为例,具体操作需要一个大根堆,一个小根堆。大根堆中存第k+1大到最小的数字,小跟堆中存第一大到第k大数字。每次加入新数字,与小跟堆的top比较,如若比top大,将小根堆的根加入大根堆中,再将小根堆的根pop出来,将要加入的新数字放入小跟堆;如若比小根堆top小,直接加入大根堆。

对顶堆中需要大根堆的原因是它的k根据不同题意可能会改变(比如每次k++之类的),而这题的m不会变,所以就不需要存第m+1小到最大的数啦,直接把它们丢掉好了


深度优先搜索练习

# 全排列问题

题目描述

按照字典序输出自然数 1 1 1 n n n 所有不重复的排列,即 n n n 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。

输入格式

一个整数 n n n

输出格式

1 ∼ n 1 \sim n 1n 组成的所有不重复的数字序列,每行一个序列。

每个数字保留 5 5 5 个场宽。

样例 #1

样例输入 #1

3

样例输出 #1

    1    2    3
    1    3    2
    2    1    3
    2    3    1
    3    1    2
    3    2    1

提示

1 ≤ n ≤ 9 1 \leq n \leq 9 1n9

分析

我们以N=3为例,构造一棵搜索树(或说是状态树)来进行搜索。

同时构造出三个格子,用来存放搜索树中的结果。

现在,我们从第一格开始搜索。第一格填1的搜索树如下:

所以N=3的情况下,第一格填1的排列情况共有两种123,132.

第一格填2的搜索树如下:
请添加图片描述

所以N=3的情况下,第一格填2的排列情况共有两种 213,231.
请添加图片描述

代码

#include<iostream>
#include<cstdio>
using namespace std;
int a[500];
int b[500];
int n;
void dfs(int x){//x代表第几层
	if(x == n){
		for(int i=0;i<x;i++){
			printf("%5d",a[i]);
		}
		printf("\n");
		return;
	}
	for(int i=1;i<=n;i++){
		if(!b[i]){
			b[i] = 1;//标记
			a[x] = i;
			dfs(x+1);//递归
			b[i] = 0;//回溯
		}
	}
}
int main()
{
	cin>>n;
	dfs(0);
	
	return 0; 
}

# 组合的输出

题目描述

排列与组合是常用的数学方法,其中组合就是从 n n n 个元素中抽出 r r r 个元素(不分顺序且 r ≤ n r \le n rn),我们可以简单地将 n n n 个元素理解为自然数 1 , 2 , … , n 1,2,\dots,n 1,2,,n,从中任取 r r r 个数。

现要求你输出所有组合。

例如 n = 5 , r = 3 n=5,r=3 n=5,r=3,所有组合为:

123 , 124 , 125 , 134 , 135 , 145 , 234 , 235 , 245 , 345 123,124,125,134,135,145,234,235,245,345 123,124,125,134,135,145,234,235,245,345

输入格式

一行两个自然数 n , r ( 1 < n < 21 , 0 ≤ r ≤ n ) n,r(1<n<21,0 \le r \le n) n,r(1<n<21,0rn)

输出格式

所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,每个元素占三个字符的位置,所有的组合也按字典顺序。

注意哦!输出时,每个数字需要 3 3 3 个场宽。以 C++ 为例,你可以使用下列代码:

cout << setw(3) << x;

输出占 3 3 3 个场宽的数 x x x。注意你需要头文件 iomanip

样例 #1

样例输入 #1

5 3

样例输出 #1

1  2  3
  1  2  4
  1  2  5
  1  3  4
  1  3  5
  1  4  5
  2  3  4
  2  3  5
  2  4  5
  3  4  5

分析

组合与排列的区别在于组合里每一个数都要大于前一个数。

代码

#include<iostream>
#include<cstdio>
using namespace std;
int a[500];
int b[500];
int n,r;
void dfs(int x){
    if(x > r){
        for(int i=1;i<x;i++){
            printf("%3d",a[i]);
        }
        printf("\n");
        return;
    }
    for(int i=a[x-1]+1;i<=n;i++){ //因为后一个数要比前面的大,所以就不能一直从1开始
        a[x] = i;
        dfs(x+1);
    }
}
int main()
{
    cin>>n>>r;
    dfs(1);
     
    return 0; 
}

注意

①组合的数字不能有重复!!!

②后面的数字要比前面大!!!

③输出时一定要带上场宽!!!

④输完后一定要记得换行!!!

⑤递归后一定要状态回溯!!!

有重复元素的排列问题

问题描述

设集合R={r1,r2,…,rn}是要进行排列的n个元素,其中r1,r2,…,rn可能相同。 试着设计一个算法,列出R的所有不同排列。 即,给定n以及待排的n个可能重复的元素。计算输出n个元素的所有不同排列。

算法设计

给定n及待排列的n个元素,计算出这n个元素的所有不同排列。

数据输入

第1行是元素个数n,1<=n<=500。接下来的1行是待排列的n个元素

结果输出

程序运行结束时,将计算输出n个元素的所有不同排列。最后1行中的数是排列总数。

输入样例

输入:

4
aacc

输出:

aacc
acac
acca
caac
caca
ccaa
6

分析

拆分字符串,进行递归回溯

代码

#include<bits/stdc++.h>
using namespace std;
char a[251];
int n[27],s,sum=0;
void f(int k) {
	if(k==s+1) {
		for(int i=1;i<=s;i++)
			printf("%c",a[i]);
		printf("\n");
		sum++;
		return ;
	}
	for(int i=1;i<=26;i++)//从字母a找到字母z 
	{
		if(n[i]!=0)
		{
			n[i]--;
			a[k]=char(i+96);
			f(k+1);//下一个字符 
			n[i]++;
		}
		else
			continue;
	}
}
int main() {
	scanf("%d",&s);
	for(int i=1; i<=s; i++) {
		cin>>a[i];
		n[a[i]-96]++;//每个字符的数量
	}
	f(1);
	printf("%d",sum);
	return 0;
}

排列棋子

将M个白棋子与N个黑棋子排成一行,可以排成多种不同的图案。例如:2个白棋子和2个黑棋子,一共可以排成如下图所示的6种图案(根据组合数计算公式:)

说明: http://10.60.64.213:8080/JudgeOnline/images/4736_2.bmp

请你编写一段程序,输出M个白棋子与N个黑棋子能够组成的所有图案。

为了避免程序输出结果过多导致严重超时,特别限制:1≤M,N≤6
输入
两个正整数M,N表示白棋子与黑棋子的数量,并且满足1≤M,N≤6
输出
M个白棋子与N个黑棋子可以排列的所有图案。
要求:每行输出一种图案,白棋子用0表示,黑棋子用1表示,按升序输出
样例输入
2 1
2 2
2 3
样例输出
001
010
100

0011
0101
0110

1001
1010
1100

00111
01011
01101
01110
10011
10101
10110
11001
11010
11100

代码

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

int a[100];
bool b[100];
int n,m,t;
void dfs(int x){
	if(n+m== 0){
		for(int i=1;i<=t;i++) cout<<a[i];
		cout<<endl;
	}
	if(b[x] == false){
		b[x]=true;
		if(m){
			a[x] = 0;
			m--;
			dfs(x+1);
			m++;
			b[x]=false;
		}
		if(n){
			a[x] = 1;
			n--;
			dfs(x+1);
			n++;
			b[x]=false;
			
		}
	}
}
int main()
{
	while(cin >> m >> n ){
		t = m+n;
		memset(a,-1,sizeof(a));
		memset(b,false,sizeof(b));
		dfs(1);
		cout<<endl;		
	}
	return 0;

}

# 自然数的拆分问题

题目描述

任何一个大于 1 1 1 的自然数 n n n,总可以拆分成若干个小于 n n n 的自然数之和。现在给你一个自然数 n n n,要求你求出 n n n 的拆分成一些数字的和。每个拆分后的序列中的数字从小到大排序。然后你需要输出这些序列,其中字典序小的序列需要优先输出。

输入格式

输入:待拆分的自然数 n n n

输出格式

输出:若干数的加法式子。

样例 #1

样例输入 #1

7

样例输出 #1

1+1+1+1+1+1+1
1+1+1+1+1+2
1+1+1+1+3
1+1+1+2+2
1+1+1+4
1+1+2+3
1+1+5
1+2+2+2
1+2+4
1+3+3
1+6
2+2+3
2+5
3+4

提示

数据保证, 2 ≤ n ≤ 8 2\leq n\le 8 2n8

分析

拆分5=2+2+15=2+1+1+1为例。首先在完成前一组数值拆分恢复现场,回溯穷举至2(第一层),得第一个拆分数值2,余下的数值5-2,即为3,剩下数值3与已拆分前一个所得数值比较取较小值2(避免5再次拆分成2和3),获得拆分数值穷举范围即2-1,进入第二层搜索,首先是穷举2,获得第二个拆分数值2,计算剩余数值得1,然后递归调拆分余下的数值1,首先判断剩下的数值是否为0(完成拆分),不是,该数值与前一个拆分所得的数值2比较取小数值得1,获得该数的数值拆分范围为1-1,进入第三层搜索,取第三个拆分数值1,同时剩下的数值减去该数值得0,递归调用,拆分余下的数值0,在递归调用过程中,发现剩余数值为0,递归调用结束,即打印该拆分数即4=2+2+1,恢复现场至上一层,得剩余数值为1,再次恢复现场至上一层(第二层),得剩余数值为3,穷举下一个拆分数值1,据悉完成5=1+1+1+1+1的拆分。

代码

#include<iostream>
using namespace std;
const int N = 22;
int path[N],len = 0,n;

void dfs(int x){//x代表剩余 
	//结束条件
	if(x ==0 && len>1){
		for(int i=1;i<len;i++){
			cout<<path[i]<<"+";
		}
		cout<<path[len]<<endl;
		return ;
	}  
	for(int i=path[len];i<=x;i++){  
		if(x-i>=0){
			path[++len] = i;
			//cout<<"i="<<i<<" dfs("<<x<<"-"<<i<<")"<<" "<<len<<endl;
			dfs(x-i);
			len--;
		}
	}
}
int main()
{
	cin>>n;
	path[0]=1;
	dfs(n);
	
	return 0;
}


BFS经典例题练习

# 奇怪的电梯

题目描述

呵呵,有一天我做了一个梦,梦见了一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第 i i i 层楼( 1 ≤ i ≤ N 1 \le i \le N 1iN)上有一个数字 K i K_i Ki 0 ≤ K i ≤ N 0 \le K_i \le N 0KiN)。电梯只有四个按钮:开,关,上,下。上下的层数等于当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如: 3 , 3 , 1 , 2 , 5 3, 3, 1, 2, 5 3,3,1,2,5 代表了 K i K_i Ki K 1 = 3 K_1=3 K1=3 K 2 = 3 K_2=3 K2=3,……),从 1 1 1 楼开始。在 1 1 1 楼,按“上”可以到 4 4 4 楼,按“下”是不起作用的,因为没有 − 2 -2 2 楼。那么,从 A A A 楼到 B B B 楼至少要按几次按钮呢?

输入格式

共二行。

第一行为三个用空格隔开的正整数,表示 N , A , B N, A, B N,A,B 1 ≤ N ≤ 200 1 \le N \le 200 1N200 1 ≤ A , B ≤ N 1 \le A, B \le N 1A,BN)。

第二行为 N N N 个用空格隔开的非负整数,表示 K i K_i Ki

输出格式

一行,即最少按键次数,若无法到达,则输出 -1

样例 #1

样例输入 #1

5 1 5
3 3 1 2 5

样例输出 #1

3

提示

对于 100 % 100 \% 100% 的数据, 1 ≤ N ≤ 200 1 \le N \le 200 1N200 1 ≤ A , B ≤ N 1 \le A, B \le N 1A,BN 0 ≤ K i ≤ N 0 \le K_i \le N 0KiN

分析

这道题方向只有两个,要么上要么下,所以只需要用广搜,将每个楼层向上和向下的情况都遍历一
遍,用step来记录按钮次数,直到找到相应的楼层,如果所有的节点都遍历完了还没找到相应的楼层,说明到不了,输出-1。

代码

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

int n,a,b;
int k[210]/* 当前楼层的数字*/,s[210]/*判断是否到达过*/;
void bfs(int a,int b);//广搜 
int main(){
   cin>>n>>a>>b;
   
   for(int i=1;i<=n;i++) cin>>k[i];
   bfs(a,b);
   cout<< s[b];//输出到达当前楼层所用次数 
    return 0;
}
queue <int> q;
void bfs(int a,int b){
	memset(s,-1,sizeof s);//初始化s为-1 方便不能到达时输出 
	q.push(a);
	s[a] = 0;
	while(!q.empty()){
		int x = q.front();
		q.pop();
		int  f = x + k[x];//向上走 
		if( f<=n && s[f]==-1){
			q.push(f);
			s[f] = s[x] + 1;
			
		}
		f = x - k[x];//向下走 
		if(f>=1 && s[f]==-1){
			q.push(f);//下一次的起始位置 
			s[f] = s[x] + 1;//到达该楼层的次数加1 
		}
		 
	}
}

# 迷宫

题目描述

给定一个 N × M N \times M N×M 方格的迷宫,迷宫里有 T T T 处障碍,障碍处不可通过。

在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。

给定起点坐标和终点坐标,每个方格最多经过一次,问有多少种从起点坐标到终点坐标的方案。

输入格式

第一行为三个正整数 N , M , T N,M,T N,M,T,分别表示迷宫的长宽和障碍总数。

第二行为四个正整数 S X , S Y , F X , F Y SX,SY,FX,FY SX,SY,FX,FY S X , S Y SX,SY SX,SY 代表起点坐标, F X , F Y FX,FY FX,FY 代表终点坐标。

接下来 T T T 行,每行两个正整数,表示障碍点的坐标。

输出格式

输出从起点坐标到终点坐标的方案总数。

样例 #1

样例输入 #1

2 2 1
1 1 2 2
1 2

样例输出 #1

1

提示

对于 100 % 100\% 100% 的数据, 1 ≤ N , M ≤ 5 1 \le N,M \le 5 1N,M5 1 ≤ T ≤ 10 1 \le T \le 10 1T10 1 ≤ S X , F X ≤ n 1 \le SX,FX \le n 1SX,FXn 1 ≤ S Y , F Y ≤ m 1 \le SY,FY \le m 1SY,FYm

分析

这题用深搜更好写,用广搜需要在每一个走到的点中,开一个数组来标记之前走过的位置,来找到不同的路径。

广搜代码

#include <bits/stdc++.h>
#include <queue>
using namespace std;
queue<int>q;
int dir[][2] = {
	{0,-1},{1,0},{0,1},{-1,0}
}; 
int mp[10][10],a[10][10];
int n,m,cnt=0,sx,sy,fx,fy,tx,ty,t;
void bfs(int x,int y);
int main()
{
	cin>>n>>m>>t;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			mp[i][j] = 1; //先排除障碍初始化迷宫都能走 
		}
	}
	cin>>sx>>sy>>fx>>fy;
	for(int i=1;i<=t;i++){
		cin>>tx>>ty;
		mp[tx][ty] = 0;  //将障碍处设为0方便后面判断 
	}
	bfs(sx,sy);
	cout<<cnt;//输出 
  
    return 0;
}
void bfs(int x,int y){
	if(x==fx&&y==fy){
		cnt++;
		return;//返回,继续搜索;
	}
	for(int i=0;i<4;i++){0——3是左,右,下,上四个方向;
		int nx = x + dir[i][0];//新行
		int ny = y + dir[i][1];//新列 
		if(a[nx][ny] ==0 && mp[nx][ny] ==1 ){
			a[x][y] = 1;//标记走过 
			bfs(nx,ny);//从新的位置开始走 
			a[x][y]=0;//还原状态 
		}
	}
}

深搜代码

#include<bits/stdc++.h>
using namespace std;
const int N = 10;
int a[N][N]={0};
int v[N][N]={0};
//方向数组(上下左右)
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};
int n, m, t;
int sx, sy, fx, fy;
int cnt = 0;
//判断是否在迷宫范围内
int check(int x,int y){
	if (x <= n && x >= 1 && y <= m && y >= 1){
	    return 1;
	}
	return 0;
}
void DFS(int x,int y){
	if(x == fx && y == fy){
		cnt++;
		return;
	}
	for (int i = 0; i < 4;i++){
	//下一个位置的坐标
		int tx = x + dx[i];
		int ty = y + dy[i];
		if (check(tx, ty)==1 && v[tx][ty] != 1 && a[tx][ty] != 1){
			v[tx][ty] = 1;
			DFS(tx, ty);
			v[tx][ty] = 0;//回溯,上一个位置恢复成没走过
		}
    }
}
int main()
{
	//输入迷宫长宽和障碍总数
	cin >> n >> m >> t;
	//起点和终点坐标
	cin >> sx >> sy >> fx >> fy;
	v[sx][sy] = 1;
	//输入t个障碍
	int x, y;
	while(t--){
		cin >> x >> y;
		a[x][y] = 1;//将障碍直接标记为走过,这样就不会走到障碍上去了
	}
	DFS(sx,sy);
	cout << cnt;
	return 0;
}

# 填涂颜色

题目描述

由数字 0 0 0 组成的方阵中,有一任意形状闭合圈,闭合圈由数字 1 1 1 构成,围圈时只走上下左右 4 4 4 个方向。现要求把闭合圈内的所有空间都填写成 2 2 2。例如: 6 × 6 6\times 6 6×6 的方阵( n = 6 n=6 n=6),涂色前和涂色后的方阵如下:

0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1
0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 2 2 1
1 1 2 2 2 1
1 2 2 2 2 1
1 1 1 1 1 1

输入格式

每组测试数据第一行一个整数 n ( 1 ≤ n ≤ 30 ) n(1 \le n \le 30) n(1n30)

接下来 n n n 行,由 0 0 0 1 1 1 组成的 n × n n \times n n×n 的方阵。

方阵内只有一个闭合圈,圈内至少有一个 0 0 0

输出格式

已经填好数字 2 2 2 的完整方阵。

样例 #1

样例输入 #1

6
0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1

样例输出 #1

0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 2 2 1
1 1 2 2 2 1
1 2 2 2 2 1
1 1 1 1 1 1

提示

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 30 1 \le n \le 30 1n30

分析

因为我们很难判断那些为0的点在圆内还是在圆外,所以我们可以反向操作,先将所有值为0的点染色成2,然后在以最外圈每一个点为起点进行搜索,将圆外面的点颜色染回来就行。

代码

#include<iostream>
using namespace std;
int a[35][35],b[35][35];
int dir[][2] = {
	{-1,0},{0,1},{1,0},{0,-1}
	
};

int n;

void bfs(int x,int y){
	if(x<0 || x>n+1 || y<0 || y>n+1 || b[x][y] != 0 ){//如果搜过头或者已经被搜过了或者本来就是墙的就往回
		return;
	}
	b[x][y] = 1;
	for(int i=0;i<4;i++){
		int nx = x+dir[i][0];
		int ny = y+dir[i][1];
		bfs(nx,ny);
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>a[i][j];
		    if(a[i][j] == 0) b[i][j] =0;
		    else b[i][j] =2;//
		    
		}
	}
	bfs(0,0);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			
		    if(b[i][j] == 0) cout<<2<<" ";//如果染过色以后i,j那个地方还是0,说明没有搜到,就是周围有墙,当然就是被围住了,然后输出2
		    else cout<<a[i][j]<<" ";//因为被染色了,本来没有被围住的水和墙都染成了1,所以就输出a[i][j]

		   
		}
		 cout<<endl;
    }
	
	return 0;
}

I - Red and Black

题目

There is a rectangular room, covered with square tiles. Each tile is colored either red or black. A man is standing on a black tile. From a tile, he can move to one of four adjacent tiles. But he can’t move on red tiles, he can move only on black tiles.

Write a program to count the number of black tiles which he can reach by repeating the moves described above.
题意概括 :红与黑,#代表红色格子,.代表黑色格子,@代表人,人只能走黑色格子而不能走红色格子,问人一共能走多少个格子.

Input

The input consists of multiple data sets. A data set starts with a line containing two positive integers W and H; W and H are the numbers of tiles in the x- and y- directions, respectively. W and H are not more than 20.

There are H more lines in the data set, each of which includes W characters. Each character represents the color of a tile as follows.

‘.’ - a black tile
‘#’ - a red tile
‘@’ - a man on a black tile(appears exactly once in a data set)
The end of the input is indicated by a line consisting of two zeros.

Output

For each data set, your program should output a line which contains the number of tiles he can reach from the initial tile (including itself).

Sample Input

6 9

....#.

.....#

......

......

......

......

......

#@...#

.#..#.

11 9

.#.........

.#.#######.

.#.#.....#.

.#.#.###.#.

.#.#..@#.#.

.#.#####.#.

.#.......#.

.#########.

...........

11 6

..#..#..#..

..#..#..#..

..#..#..###

..#..#..#@.

..#..#..#..

..#..#..#..

7 7

..#.#..

..#.#..

###.###

...@...

###.###

..#.#..

..#.#..

0 0

Sample Output

45

59

6

13

分析

解题思路 :首先找到人所在位置的坐标,然后向四周扩散,记录所能走的点的个数,知道不能走为止.

代码

#include<iostream>
#include<queue>
using namespace std;

char a[25][25];//地图 
//搜索方向数组
int dir[][2] = {{0,-1},{-1,0},{0,1},{1,0}};//左上右下 
int n,m;
struct pos{
	int r,c;
	//构造方法
	
}p;
//从起点p开始搜索,返回黑砖的数量
int bfs(pos p); 
int main()
{
	pos st;//起点 
	while(cin>>m>>n){//输入地图信息 
	if(!m && !n) break;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
			if(a[i][j] == '@'){//找到起点 
				st.r = i;
				st.c = j;
			}
		}
	}
	//广搜
	 cout<<bfs(st)<<endl;
    }  
   
	return 0;
}
int bfs(pos p){
	queue<pos> q;
	q.push(p);//起点入队并标记红砖 
	a[p.r][p.c] = '#';
	int cnt = 1;//搜到的黑砖数量 
	while(!q.empty()){
		//取队首并出队
		pos p1 = q.front();
		q.pop();
		//搜索(左上右下) 
		for(int i=0;i<4;i++){
			int nr = p1.r + dir[i][0];//新行 
			int nc = p1.c + dir[i][1];//新列 
			//(nr,nc)在地图范围内,并且是黑砖 
			if(nr >= 1 && nr <= n && nc >= 1 && nc <= m && a[nr][nc] == '.'){
				cnt++;
				p.c = nc;
				p.r = nr;
				q.push(p);
				a[nr][nc] = '#';
			}
		}
	}
	return cnt;
} 

搜索综合练习

# 最短路计数

题目描述

给出一个 N N N 个顶点 M M M 条边的无向无权图,顶点编号为 1 ∼ N 1\sim N 1N。问从顶点 1 1 1 开始,到其他每个点的最短路有几条。

输入格式

第一行包含 2 2 2 个正整数 N , M N,M N,M,为图的顶点数与边数。

接下来 M M M 行,每行 2 2 2 个正整数 x , y x,y x,y,表示有一条由顶点 x x x 连向顶点 y y y 的边,请注意可能有自环与重边。

输出格式

N N N 行,每行一个非负整数,第 i i i 行输出从顶点 1 1 1 到顶点 i i i 有多少条不同的最短路,由于答案有可能会很大,你只需要输出 $ ans \bmod 100003$ 后的结果即可。如果无法到达顶点 i i i 则输出 0 0 0

样例 #1

样例输入 #1

5 7
1 2
1 3
2 4
3 4
2 3
4 5
4 5

样例输出 #1

1
1
1
2
4

提示

1 1 1 5 5 5 的最短路有 4 4 4 条,分别为 2 2 2 1 → 2 → 4 → 5 1\to 2\to 4\to 5 1245 2 2 2 1 → 3 → 4 → 5 1\to 3\to 4\to 5 1345(由于 4 → 5 4\to 5 45 的边有 2 2 2 条)。

对于 20 % 20\% 20% 的数据, 1 ≤ N ≤ 100 1\le N \le 100 1N100
对于 60 % 60\% 60% 的数据, 1 ≤ N ≤ 1 0 3 1\le N \le 10^3 1N103
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 1 0 6 1\le N\le10^6 1N106 1 ≤ M ≤ 2 × 1 0 6 1\le M\le 2\times 10^6 1M2×106

分析

因为所有的边权都为1,所以一个点的最短路就相当于是它在BFS搜索树中的深度。一个点最短路一定经过了一个层数比它少一的结点(否则不是最短路)。所以用每个相邻且层数比当前结点层数少一的点更新当前点的路径跳数即可。

代码

#include <bits/stdc++.h>
#include <queue>
#include<vector>

#define Mod 100003
using namespace std;
const int N =  1000005;

int dep[N],cnt[N];//dep是每个点对于源点的深度,cnt储存答案,即到点i的最短路个数
vector<int> mp[N];//注意这是2维数组,用来存每一个点所连的边,而题目中有10^6个点,所以是10^6+5
bool vis[N];//判断点是否使用
queue< int > q;
void bfs(int x);
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++) {
		int x,y;
		cin>>x>>y;
		mp[x].push_back(y);//存边,无向图存有向边两次
		mp[y].push_back(x);
		
	}
	
	bfs(1);
	for(int i=1;i<=n;i++){
		
		cout<<cnt[i]<<endl;
	}
	return 0;
}
void bfs(int x){
	dep[x] = 0;
    vis[x] = 1;
	q.push(x);
	cnt[x] = 1;
	while(!q.empty()){
		int a = q.front();
		q.pop();
		for(int i=0;i<mp[a].size();i++){//遍历点所连的每一条边
			int t=mp[a][i];
			
			if(!vis[t]){
				vis[t] = 1;
				dep[t] = dep[a] + 1;//深度+1
				q.push(t);
			}
			
			if(dep[t] == dep[a] + 1)//这个点直接从旁边深度-1的结点添加最短路数量
			    cnt[t] = (cnt[t] + cnt[a])%Mod;
		}
	
	}
}

# 八数码难题

题目描述

3 × 3 3\times 3 3×3 的棋盘上,摆有八个棋子,每个棋子上标有 1 1 1 8 8 8 的某一数字。棋盘中留有一个空格,空格用 0 0 0 来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为 123804765 123804765 123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

输入格式

输入初始状态,一行九个数字,空格用 0 0 0 表示。

输出格式

只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数。保证测试数据中无特殊无法到达目标状态数据。

样例 #1

样例输入 #1

283104765

样例输出 #1

4

提示

样例解释

图中标有 0 0 0 的是空格。绿色格子是空格所在位置,橙色格子是下一步可以移动到空格的位置。如图所示,用四步可以达到目标状态。

并且可以证明,不存在更优的策略。

分析

map去重,在这里只需要一个队列,因为需要较少步数达到的状态一定在步数较多的状态前入队列

代码

#include<iostream>
#include<map>
#include<queue>
#include<algorithm>
#define ll long long
using namespace std;
const ll dx[]={-1,0,0,1},dy[]={0,-1,1,0};//转移数组;
ll n;
int main()
{
    cin>>n;
    queue<ll> q;
    q.push(n);
    map<ll,ll> m;
    m[n]=0;
    while(!q.empty())
    {
        int u=q.front(); //初始状态入队列
        int c[3][3],f=0,g=0,n=u;
		q.pop();
        if(u==123804765) break;
        for(ll i=2;i>=0;i--)
            for(ll j=2;j>=0;j--){
                c[i][j]=n%10;
				n/=10;
                if(!c[i][j]){
                	f=i;
			    	g=j;
				}
				
            }
        for(ll i=0;i<4;i++)
        {
            ll nx=f+dx[i];
			ll ny=g+dy[i];
			ll ns=0;
            if(nx<0||ny<0||nx>2||ny>2) continue; //越界就不执行
            swap(c[nx][ny],c[f][g]);
            for(int i=0;i<3;i++)
                for(int j=0;j<3;j++)ns=ns*10+c[i][j];//矩阵转数列 
            if(!m.count(ns)) //ns之前在map中没出现过 
            {
                m[ns]=m[u]+1;//map去重的同时顺便统计到达这个状态所需的步数
                q.push(ns);
            }
            swap(c[nx][ny],c[f][g]);//状态复原
        }
    }
    cout<<m[123804765]<<endl; // map的下标直接用数列表示
    return 0;
}

# [NOIP2002 普及组] 选数

题目描述

已知 n n n 个整数 x 1 , x 2 , ⋯   , x n x_1,x_2,\cdots,x_n x1,x2,,xn,以及 1 1 1 个整数 k k k k < n k<n k<n)。从 n n n 个整数中任选 k k k 个整数相加,可分别得到一系列的和。例如当 n = 4 n=4 n=4 k = 3 k=3 k=3 4 4 4 个整数分别为 3 , 7 , 12 , 19 3,7,12,19 3,7,12,19 时,可得全部的组合与它们的和为:

3 + 7 + 12 = 22 3+7+12=22 3+7+12=22

3 + 7 + 19 = 29 3+7+19=29 3+7+19=29

7 + 12 + 19 = 38 7+12+19=38 7+12+19=38

3 + 12 + 19 = 34 3+12+19=34 3+12+19=34

现在,要求你计算出和为素数共有多少种。

例如上例,只有一种的和为素数: 3 + 7 + 19 = 29 3+7+19=29 3+7+19=29

输入格式

第一行两个空格隔开的整数 n , k n,k n,k 1 ≤ n ≤ 20 1 \le n \le 20 1n20 k < n k<n k<n)。

第二行 n n n 个整数,分别为 x 1 , x 2 , ⋯   , x n x_1,x_2,\cdots,x_n x1,x2,,xn 1 ≤ x i ≤ 5 × 1 0 6 1 \le x_i \le 5\times 10^6 1xi5×106)。

输出格式

输出一个整数,表示种类数。

样例 #1

样例输入 #1

4 3
3 7 12 19

样例输出 #1

1

分析

数据规模小所以直接深搜即可。

代码

#include<bits/stdc++.h>

using namespace std;
int n,k;
const int N = 5e6+5;
int a[N]/*存数组*/,tag[N],vis[N]/*标记是否被访问过*/,sum/*记录种类个数*/;
int prime(int x){//判断是否为素数 
	int t = sqrt(x),i;
	for( i=2;i<=t;i++){
		if(x % i == 0) return 0;	
	}
	
	return 1; 
}
void dfs(int step,int last){
	if(step == k){ //循环到了k个数字 
		int ans = 0;
		for(int i=0;i<k;i++){
			ans += a[tag[i]];
		}
		if(prime(ans)){
			
			sum ++;
			
		}
		return ;
	}
	for(int i = last;i<=n;i++){
		if( !vis[i] ){
			vis[i] = 1;
			tag[step] = i;
		//	cout<<"dfs("<<step<<"+"<<1<<" )"<<i;
		//	cout<<endl; 
			dfs(step + 1, i);
			vis[i] = 0;
			
		}
	}
}
int main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	dfs(0,1);
	cout<<sum<<endl;
  
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值