今日学习时间共11小时左右。
整天几乎都在刷题吧,现在把题目发上来。
题目描述
One cold winter evening Alice and her older brother Bob was sitting at home near the fireplace and giving each other interesting problems to solve. When it was Alice's turn, she told the number nn to Bob and said:
—Shuffle the digits in this number in order to obtain the smallest possible number without leading zeroes.
—No problem! — said Bob and immediately gave her an answer.
Alice said a random number, so she doesn't know whether Bob's answer is correct. Help her to find this out, because impatient brother is waiting for the verdict.
输入格式
The first line contains one integer nn ( 0<=n<=10^9 ) without leading zeroes. The second lines contains one integer mm ( 0<=m<=10^9 ) — Bob's answer, possibly with leading zeroes.
输出格式
Print OK if Bob's answer is correct and WRONG_ANSWER otherwise.
题意翻译
题目描述(舍弃各种乱七八糟的故事)
给定一个数N,要求把N的各个数位打乱,要求组成一个可能的,最小的数(最小数有可能含有前导0)。现在已经有一个“最小数”,请你判断这个“最小数”是不是最小数。
第一行输入n不含前导0。
第二行输入的假定的最小数可能含有前导0。 题目要求排序后的最小数不含前导0。
输入格式
两行。 第一行是给定的数N。 第二行是假定的N重组的“最小数”。
输出格式
一行。 如果输入的最小数就是NN重组的最小数,输出“OK”。 否则输出“WRONG_ANSWER”。
Translated by LiM_817
输入输出样例
输入 #1复制
3310 1033
输出 #1复制
OK
输入 #2复制
4 5
输出 #2复制
WRONG_ANSWER
这是昨天测试的题目,定睛一看,哦豁好像也不是那么难嘛。 输入->a,b->a排序小的在前->a最前方的0与最先出现的非0交换->各个位数比较是否相同以及长度是否相同->输出答案。很简单的思维对吧?于是写好了交上去,发现WA了。
???
那么为什么呢?冥思苦想3小时,发现长度为1时,如果输入是:
0
0
那么答案应该是OK的,但是却是WRONG_ANSWER,因为第二行给去掉前导0了。变成了:
0
NULL
所以我们要加一个特判,加了就过了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void fast(char a[], int left, int right)
{
char t, m = a[left];
int L = left, R = right;
if(L > R)
return;
while(L < R)
{
while(L < R && a[R] >= m) R --;
while(L < R && a[L] <= m) L ++;
if(L < R)
{
t = a[L], a[L] = a[R], a[R] = t;
}
}
a[left] = a[L];
a[L] = m;
fast(a, left, L - 1);
fast(a, L + 1, right);
}
char a[1000001], b[1000001];
int main()
{
gets(a);
gets(b);
if(strlen(a) == 1 && strlen(b) == 1) //特判
{
if(a[0] == b[0])
{
cout << "OK";
return 0;
}
else
{
cout << "WRONG_ANSWER";
return 0;
}
}
fast(a, 0, strlen(a) - 1);
if(a[0] == '0')
{
int pos = 0;
while(a[pos] == '0')
{
pos ++;
}
swap(a[0], a[pos]);
}
while(b[0] == '0')
{
for(int i = 0;i < strlen(b) - 1;i ++)
{
b[i] = b[i + 1];
}
b[strlen(b) - 1] = '\0';
}
bool can = true;
if(strlen(a) != strlen(b)) can = false;
else for(int i = 0;i < strlen(a);i ++)
{
if(a[i] != b[i])
{
can = false;
break;
}
}
// cout << a << endl;
// cout << b << endl;
if(can)
{
cout << "OK";
}
else
{
cout << "WRONG_ANSWER";
}
}
题目描述
利用快速排序算法将读入的 N 个数从小到大排序后输出。
快速排序是信息学竞赛的必备算法之一。对于快速排序不是很了解的同学可以自行上网查询相关资料,掌握后独立完成。(C++ 选手请不要试图使用 STL
,虽然你可以使用 sort
一遍过,但是你并没有掌握快速排序算法的精髓。)
输入格式
第 1 行为一个正整数 N,第 2 行包含 N 个空格隔开的正整数 ai,为你需要进行排序的数,数据保证了 Ai 不超过 10^9。
输出格式
将给定的 N 个数从小到大输出,数之间空格隔开,行末换行且无空格。
输入输出样例
输入 #1复制
5 4 2 4 5 1
输出 #1复制
1 2 4 4 5
说明/提示
对于 20% 的数据,有 N≤10^3;
对于 100% 的数据,有 N≤10^5。
这道题我尝试用了下传统的快排模版,但最后三个测试点超时了。于是我就去网上看了看快速排序的讲解和资料,发现优化的方法可以有很多,我选择了一个目前最简单、最容易理解的,就是修改基准值。原本基准值的选择在左侧,然后最后到中间交换。那么我们一开始就选择中间的值为基准值不加以交换,可以省去一点算力。最后再判断一下是否"到头"了,即递归左侧排序和右侧排序时,加上判断操作,可以减少一些排序工作。
如排序:
1 8 5 3 8 0 3
第一轮: (L, R, 重叠, 基准值)
1 8 5 3 8 0 3
1 8 5 3 8 0 3 交换 1 3 5 3 8 0 8
1 3 5 3 8 0 8 交换 1 3 0 3 8 5 8
1 3 0 3 8 5 8 结束
第二轮(从左到右)
1 3 0 3
1 3 0 3 交换 1 3 0 3
1 3 0 3 结束 (注意,这个时候l到最右侧了,说明这个部分已经右侧排完了)
8 5 8
8 5 8 交换 5 8 8 结束 (注意,这个时候R到最左侧了,说明这个部分已经左侧排完了)
剩余的部分,就是 1 3 0, 8 8. (程序会再执行直到两边排完)
第三轮(从左到右)
1 3 0
1 3 0 交换 1 0 3 结束 (右侧结束)
8 8 交换 8 8 结束 (两侧结束)
剩余部分: 1 0
第四轮
1 0 交换 0 1 结束 (两侧结束)
至此,我们的排序已经完成了。
把各个部分连起来,就是: 0 1 3 3 5 8 8
好了,排序完成。
下面上代码。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void fast(ll a[], ll left, ll right);
int main()
{
ios::sync_with_stdio(false);
ll n;
cin >> n;
ll a[n + 1] = {0};
for(ll i = 1;i <= n;i ++)
{
cin >> a[i];
}
fast(a, 1, n);
for(ll i = 1;i <= n;i ++)
{
cout << a[i] << " ";
}
return 0;
}
void fast(ll a[], ll left, ll right)
{
ll L = left, R = right, m;
m = a[(L + R) / 2]; //选择中间值为基准值
if(L > R)
return;
while(L <= R) //这里使用do while结构也可以过
{
while(a[R] > m) R --;
while(a[L] < m) L ++;
if(L <= R)
{
swap(a[L], a[R]);
L ++;
R --;
}
}
// while(L <= R);
if(left < R) fast(a, left, R); //左侧排序
if(L < right)fast(a, L, right); //右侧排序
}
题目描述
输入一串二叉树,输出其前序遍历。
输入格式
第一行为二叉树的节点数 n。(1≤n≤26)
后面 n 行,每一个字母为节点,后两个字母分别为其左右儿子。
空节点用 *
表示
输出格式
二叉树的前序遍历。
输入输出样例
输入 #1复制
6 abc bdi cj* d** i** j**
输出 #1复制
abdicj
前序列输出,即 根->左子树->右子树。
其实刚开始做这道题的时候发现可以用"链表"的方式解决,它们的结构很像,但是我改了半天也没改出来树结构的写法。但后来发现,二叉树的前序列输出就可以用字符串解决,字符串处理输出后就是相当于二叉树的前序列输出。步骤是:获取一行字符串, 找到第一个字符,在答案字符串中找到该字符,然后在该字符的后面先插入第三个字符和第二个字符(非'*'字符),最后遍历完成后得到的就是答案字符串。
如:
6
abc
bdi
cj*
d**
i**
j**
那么它形成的树就是:
前序列输出就是 abdicj
字符串处理步骤就是:
abc
abdic
abdicj
abdicj
abdicj
abdicj
发现,这个跟前序列输出一模一样!
但是也要注意,*字符是需要去掉的。
下面,上代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
getchar();
string str; //第一行字符串
cin >> str;
while(str[0] == '*')
{
str.erase(0, 1);
}
while(str[1] == '*')
{
str.erase(1, 1);
}
if(str[2] == '*')
{
str.erase(2, 1);
}
// cout << str << endl;
for(int i = 0;i < n - 1;i ++)
{
string tmp;
cin >> tmp;
int pos = str.find(tmp[0]);
// cout << "pos: " << pos << endl;
ostringstream oss;
if(tmp[2] != '*')
{
oss << tmp[2];
str.insert(pos + 1, oss.str()); //插入
oss.str("");
}
if(tmp[1] != '*')
{
oss << tmp[1];
str.insert(pos + 1, oss.str()); //插入
oss.str("");
}
// cout << str << endl;
}
cout << str;
return 0;
}
不过我好像忽略了根的问题,不过貌似这道题所有数据的第一行第一个字符的都是主根,给我AC了... ...
题目描述
给出一棵二叉树的中序与后序排列。求出它的先序排列。(约定树结点用不同的大写字母表示,长度≤8)。
输入格式
2行,均为大写字母组成的字符串,表示一棵二叉树的中序与后序排列。
输出格式
1行,表示一棵二叉树的先序。
输入输出样例
输入 #1复制
BADC BDCA
输出 #1复制
ABCD
说明/提示
【题目来源】
NOIP 2001 普及组第三题
这道题,首先我们要知道,怎么从后序遍历和中序遍历得到先序遍历。
后序遍历的最后一个就是这棵树的根,再从中序遍历中找到这个根的位置,可以左右分开成两颗子树。那么怎 么求先序遍历呢?先序遍历是根->左树->右树,那么我们只要不停地找根输出,然后再先左后右就行了。同样可以用字符串解决。
比如:
BADC
BDCA
那么处理步骤就是: (根,左部分,右部分)
第一轮
BDCA
BADC BDCA 输出A
第二轮
B 输出B
DC
DC DC 输出C
第三轮
D 输出D
结束
那么我们的得到的答案就是ABCD
下面上代码
#include <bits/stdc++.h>
using namespace std;
string amid, aaft;
void findroot(string amd, string aat)
{
if(amd.size() == 0) return;
string root = aat.substr(aat.size() - 1, 1);
cout << root;
int pos = amd.find(root);
findroot(amd.substr(0, pos), aat.substr(0, pos)); //遍历左子树
findroot(amd.substr(pos + 1), aat.substr(pos, aat.size() - pos - 1)); //遍历右子树
//注意先序遍历先根、左树、右树,所以遍历顺序不要错
}
int main()
{
cin >> amid >> aaft;
findroot(amid, aaft);
return 0;
}
题目描述
农夫约翰非常认真地对待他的奶牛们的血统。然而他不是一个真正优秀的记帐员。他把他的奶牛 们的家谱作成二叉树,并且把二叉树以更线性的“树的中序遍历”和“树的前序遍历”的符号加以记录而 不是用图形的方法。
你的任务是在被给予奶牛家谱的“树中序遍历”和“树前序遍历”的符号后,创建奶牛家谱的“树的 后序遍历”的符号。每一头奶牛的姓名被译为一个唯一的字母。(你可能已经知道你可以在知道树的两 种遍历以后可以经常地重建这棵树。)显然,这里的树不会有多于 26 个的顶点。 这是在样例输入和 样例输出中的树的图形表达方式:
C
/ \
/ \
B G
/ \ /
A D H
/ \
E F
树的中序遍历是按照左子树,根,右子树的顺序访问节点。
树的前序遍历是按照根,左子树,右子树的顺序访问节点。
树的后序遍历是按照左子树,右子树,根的顺序访问节点。
输入格式
第一行: 树的中序遍历
第二行: 同样的树的前序遍历
输出格式
单独的一行表示该树的后序遍历。
输入输出样例
输入 #1复制
ABEDFCHG CBADEFGH
输出 #1复制
AEFDBHGC
说明/提示
题目翻译来自NOCOW。
USACO Training Section 3.4
本题就是类似上面的,但是是已知前序和中序求后序。后序就是:左右根,即先左子树再右子树再根。已知前序,那么前序的第一个字符就是该树的根,在中序中找到该根,就可以把左子树和右子树分别出来,如果中序为ABEDFCHG,前序为CBADEFGH,那么第一次的树(最大的)根就是C,左子树为ABEDF,右子树为HG,因为前序是先根再左再右的,那么第二次的树(次大)的根分别为B、G,可以发现,这就是一个递归的过程,跟上一题很像(但是我写了好久)。
如: (根,左子树,右子树)
顶 (三行分别为: 前序、中序、前序)
CBADEFGH
ABEDFCHG
CBADEFGH
左
BADEF
ABEDF
BADEF
左左
A
左右
DEF
EDF
DEF
左右左
E
左右右
F
好了,至此左子树完毕
右
GH
HG
GH
右左
H
好了,这样子我们所有的节点都遍历过了,根也都找到了。最后按照后序遍历的方式将它们输出就行了。可以形成这样的树:
详见代码。
#include <bits/stdc++.h>
using namespace std;
void findroot(string pre, string in)
{
if(pre.size() == 0) return;
char root = pre[0];
int pos = in.find(root);
pre.erase(0, 1); //这里去掉根,不然要做额外处理,这样处理最"无脑",不用太多加考虑左子树右子树的边界
findroot(pre.substr(0, pos), in.substr(0, pos)); //左侧
findroot(pre.substr(pos), in.substr(pos + 1)); //右侧
cout << root;
}
int main()
{
string pre;
string in;
ios::sync_with_stdio(false);
cin >> in >> pre;
findroot(pre, in);
return 0;
}
题目描述
如题,现在有一个并查集,你需要完成合并和查询操作。
输入格式
第一行包含两个整数 N,M ,表示共有 N 个元素和 M 个操作。
接下来 MM 行,每行包含三个整数 Zi,Xi,Yi 。
当 Zi=1 时,将 Xi 与 Yi 所在的集合合并。
当 Zi=2 时,输出 Xi 与 Yi 是否在同一集合内,是的输出 Y
;否则输出 N
。
输出格式
对于每一个 Z_i=2Zi=2 的操作,都有一行输出,每行包含一个大写字母,为 Y
或者 N
。
输入输出样例
输入 #1复制
4 7 2 1 2 1 1 2 2 1 2 1 3 4 2 1 4 1 2 3 2 1 4
输出 #1复制
N Y N Y
说明/提示
对于 30% 的数据,N≤10,M≤20。
对于 70% 的数据,N≤100,M≤10^3。
对于 100% 的数据,1≤N≤10^4,1≤M≤2×10^5,1≤Xi,Yi≤N,Zi∈{1,2}。
这道题,就是树其实,集合合并就是将一个数字的父节点设置为另一个数字的父节点,相当于"标记",即"是否在一个集合里"。
举个简单的例子:
我们有5个元素,如下,它们刚开始什么关系都没有。
然后我合并1、2号元素所在的集合,不妨设置2号元素所在的集合为父节点,只要把1号元素的父节点设置为2号就行了,然后我们同样合并3、5, 4、5号元素所在的集合,它们的关系就变成下图了。
这个时候我再合并3、1号元素所在的集合。就像下图这样。
那该怎么合并呢?我们只要找到3号元素所在的集合,因为它已经和5号元素合并过了,所以它的父节点,可以理解为"集合标记"就是5号元素,1号元素同理。那么合并3、1号元素所在的集合相当于合并5、2号元素所在的集合。于是上面的关系图就等价于下面的关系图了。
整理一下,它们最后的关系就是这样的:
就是妥妥的树结构。这样子如果检查3、1元素是否在一个集合里,最后查到的它们的父节点都是2号元素,那么它们就在一个集合里。在其他情况下,如果检查的节点父节点不同,那么它们就不在一个集合里。题目的AC代码如下:
#include <bits/stdc++.h>
using namespace std;
const int SUMNODES = 500000 + 1;
int N, M;
int ParentNode[SUMNODES];
void initialize(int N) //初始化集合,各个数字独立
{
for(int i = 1;i <= N;i ++)
{
ParentNode[i] = i; //设置它们的父节点为自己
}
}
int findparent(int num) //找到num号元素的父节点
{
return num == ParentNode[num] ? num : (ParentNode[num] = findparent(ParentNode[num]));
//如果父节点为自己,那么就返回自己
//如果父节点不是自己,那么就找到它的上层父节点,直到找到最终父节点。
}
void merge(int a, int b) //合并
{
int x = findparent(a); //获得a号元素的父节点
int y = findparent(b); //获得b号元素的父节点
ParentNode[x] = y;
}
void check(int a, int b) //检查是否再一个集合里
{
int x = findparent(a);
int y = findparent(b);
if(x == y)
{
cout << "Y" << endl;
}
else
{
cout << "N" << endl;
}
}
int main()
{
cin >> N >> M;
initialize(N);
int A, B, C;
for(int i = 0;i < M;i ++)
{
cin >> A >> B >> C;
if(A == 1)
{
merge(B, C);
}
else
{
check(B, C);
}
}
return 0;
}
题目背景
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
题目描述
规定:x 和 y 是亲戚,y 和 z 是亲戚,那么 x 和 z 也是亲戚。如果 x,y 是亲戚,那么 x 的亲戚都是 y 的亲戚,y 的亲戚也都是 x 的亲戚。
输入格式
第一行:三个整数 n,m,p,(n,m,p≤5000),分别表示有 n 个人,m 个亲戚关系,询问 p 对亲戚关系。
以下 m 行:每行两个数 Mi,Mj,1≤Mi, Mj≤N,表示 Mi 和 Mj 具有亲戚关系。
接下来 p 行:每行两个数 Pi,Pj,询问 Pi 和 Pj 是否具有亲戚关系。
输出格式
p 行,每行一个 Yes
或 No
。表示第 i 个询问的答案为“具有”或“不具有”亲戚关系。
输入输出样例
输入 #1复制
6 5 3 1 2 1 5 3 4 5 2 1 3 1 4 2 3 5 6
输出 #1复制
Yes Yes No
这个题目,和上面那个题目很像,几乎一模一样,上面的代码AC后看到这题核心代码部分复制下来,改下输入输出方式,就直接AC了。怎么完成的操作和理解方式上面讲的很明白了,这里就不再赘述了。
#include <bits/stdc++.h>
using namespace std;
const int SUMNODES = 500000 + 1;
int N, M, P;
int ParentNode[SUMNODES];
void initialize(int N) //初始化集合,各个人关系独立
{
for(int i = 1;i <= N;i ++)
{
ParentNode[i] = i; //设置他们的父节点为自己
}
}
int findparent(int num) //找到num号元素的父节点
{
return num == ParentNode[num] ? num : (ParentNode[num] = findparent(ParentNode[num]));
//如果父节点为自己,那么就返回自己
//如果父节点不是自己,那么就找到它的上层父节点,直到找到最终父节点。
}
void merge(int a, int b) //合并
{
int x = findparent(a); //获得a号元素的父节点
int y = findparent(b); //获得b号元素的父节点
ParentNode[x] = y;
}
void check(int a, int b) //检查是否再一个集合里
{
int x = findparent(a);
int y = findparent(b);
if(x == y)
{
cout << "Yes" << endl;
}
else
{
cout << "No" << endl;
}
}
int main()
{
cin >> N >> M >> P;
initialize(N);
int A, B;
for(int i = 0;i < M;i ++)
{
cin >> A >> B;
merge(A, B);
}
for(int i = 0;i < P;i ++)
{
cin >> A >> B;
check(A, B);
}
return 0;
}
题目描述
明天就是母亲节了,电脑组的小朋友们在忙碌的课业之余挖空心思想着该送什么礼物来表达自己的心意呢?听说在某个网站上有卖云朵的,小朋友们决定一同前往去看看这种神奇的商品,这个店里有 n 朵云,云朵已经被老板编号为 1,2,3,...,n,并且每朵云都有一个价值,但是商店的老板是个很奇怪的人,他会告诉你一些云朵要搭配起来买才卖,也就是说买一朵云则与这朵云有搭配的云都要买,电脑组的你觉得这礼物实在是太新奇了,但是你的钱是有限的,所以你肯定是想用现有的钱买到尽量多价值的云。
输入格式
第一行输入三个整数,n,m,w,表示有 n 朵云,m 个搭配和你现有的钱的数目。
第二行至 n+1 行,每行有两个整数, ci,di,表示第 i 朵云的价钱和价值。
第 n+2 至 n+1+m 行 ,每行有两个整数 ui,vi。表示买第 ui 朵云就必须买第 vi 朵云,同理,如果买第 vi 朵就必须买第 ui 朵。
输出格式
一行,表示可以获得的最大价值。
输入输出样例
输入 #1复制
5 3 10 3 10 3 10 3 10 5 100 10 1 1 3 3 2 4 2
输出 #1复制
1
说明/提示
- 对于 30% 的数据,满足 1≤n≤100;
- 对于 50% 的数据,满足 1≤n,w≤10^3,1≤m≤100;
- 对于 100% 的数据,满足 1≤n≤10^4,0≤m≤5×10^3。
也是一道并查集的题目,最后再用背包处理一下就可以了。因为一个节点的元素较多,我使用了结构体去定义,再构建结构体数组,每个里面包含三个元素:价格、价值、父节点,用于输入处理。搭配购买从本质上就是合并元素集合,不过多了价格、价值而已,只要在父节点结构体中累加就行了。至于动态规划,只要事先把父节点都加入到一个数组里就可以了,相当于"几堆"商品的"总价格"和“总价值"的dp处理。下面放上我的AC代码:
#include <bits/stdc++.h>
using namespace std;
const int SUMNODES = 50000 + 1;
int n, m, w;
struct Clouds {
int pri;
int val;
int parent;
} CD[SUMNODES];
void initialize(int N) //初始化集合,各个关系独立
{
for(int i = 0;i <= N;i ++)
{
CD[i].parent = i; //设置它们的父节点为自己
}
}
int findparent(int num) //找到num号元素的父节点
{
return num == CD[num].parent ? num : (CD[num].parent = findparent(CD[num].parent));
//如果父节点为自己,那么就返回自己
//如果父节点不是自己,那么就找到它的上层父节点,直到找到最终父节点。
}
void merge(int a, int b) //合并
{
int x = findparent(CD[a].parent); //获得a号元素的父节点
int y = findparent(CD[b].parent); //获得b号元素的父节点
CD[x].parent = CD[y].parent;
CD[y].val += CD[x].val; //把价值和价格全部叠加上父节点以便于访问
CD[y].pri += CD[x].pri;
}
int main()
{
cin >> n >> m >> w;
initialize(n);
for(int i = 0;i < n;i ++)
{
cin >> CD[i].pri >> CD[i].val;
}
for(int i = 0;i < m;i ++)
{
int a, b;
cin >> a >> b;
merge(a - 1, b - 1);
}
int sum = 0;
int roots[10001] = {0};
for(int i = 0;i < n;i ++)
{
if(CD[i].parent == i) //把所有父节点加入根数组中
{
roots[sum ++] = i;
// cout << "第" << i + 1 << "朵云是根节点,价格和价值分别为: " << CD[roots[sum - 1]].pri << " 、" << CD[roots[sum - 1]].val << endl;
}
}
long long dp[w + 1] = {0}; //动态规划处理
for(int i = 0;i < sum;i ++)
{
for(int j = w;j >= CD[roots[i]].pri;j --)
{
dp[j] = max(dp[j], dp[j - CD[roots[i]].pri] + CD[roots[i]].val);
}
}
cout << dp[w];
return 0;
}
明天的目标:题目!题目!题目!4题及以上!
当然题目也不是唯一目标,《大话数据结构》看了似懂非懂,实际应用还是十分困难,还得再多做一些实例和练习呀!