1. 因数分解
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int count(int n, int last_number) {
if (n == 1) return 1;
int re = 0, i;
for (i = last_number; i <= n; i++)
if (n % i == 0)
re += count(n / i, i);//为什么是i?因为小于i的因数的分解数都在之前被加到re里面去了!
return re;
}
int main() {
int testcases;
int n;
scanf("%d", &testcases);
while (testcases--) {
scanf("%d", &n);
printf("%d\n", count(n, 2));
}
}
//20 = 2 * 2 * 5
//20 = 2 * 10
//20 = 4 * 5
//20 = 20
//count(10,2)
// --count(5,2)
// --count(1,5)=1
// --count(2,5)=0
// --count(1,10)=1
//count(5,4)
// --count(1,5)=1
//count(4,5)=0
//count(2,10)=0
//count(1,20)=1
2. 抓住那头牛
描述
农夫知道一头牛的位置,想要抓住它。农夫和牛都位于数轴上,农夫起始位于点N(0<=N<=100000),牛位于点K(0<=K<=100000)。农夫有两种移动方式:
1、从X移动到X-1或X+1,每次移动花费一分钟
2、从X移动到2*X,每次移动花费一分钟
假设牛没有意识到农夫的行动,站在原地不动。农夫最少要花多少时间才能抓住牛?
输入
两个整数,N和K
输出
一个整数,农夫抓到牛所要花费的最小分钟数
样例输入
5 17
样例输出
4
//助教代码
#include<cstdio>
#include<cstring>
using namespace std;
const int MAX = 300002;
bool isTrodden[MAX];
int timer;
// queue
struct Record{
int pos;
int time;
}record[MAX] = {0};
// head and tail of queue
int head,tail;
int BFS(int target){
int cur_pos,cur_time;
while(head < tail){
//Deal with head Node
cur_pos = record[head].pos;
cur_time = record[head].time;
//1.Try Move right
//Mind: If the cow is on the left, don't bother
//没来过
if(target > cur_pos && !isTrodden[cur_pos + 1]){
if(cur_pos + 1 == target)
return cur_time + 1;
isTrodden[cur_pos + 1] = true;
// push new element at tail
record[tail].pos = cur_pos + 1;
record[tail].time = cur_time + 1;
++tail;
}
//2.Try Move left
if(cur_pos > 1 && !isTrodden[cur_pos - 1]){
if(cur_pos - 1 == target)
return cur_time + 1;
isTrodden[cur_pos - 1] = true;
// push new element at tail
record[tail].pos = cur_pos - 1;
record[tail].time = cur_time + 1;
++tail;
}
//3 Try Teleporting, mind range
//3.Try Move twice
if(target > cur_pos && !isTrodden[cur_pos * 2]){
if(cur_pos * 2 == target)
return cur_time + 1;
isTrodden[cur_pos * 2 ] = true;
// push new element at tail
record[tail].pos = cur_pos * 2 ;
record[tail].time = cur_time + 1;
++tail;
}
++head;
}
return -1;
}
int main(){
int n,k;
scanf("%d%d",&n,&k);
//the cow is at the left,no need to move rightwards
if(k <= n){
printf("%d\n",n - k);
return 0;
}
//Init
memset(isTrodden,false,MAX);
isTrodden[n] = true; //this place has been occupied
record[0].pos = n,record[0].time = 0;
head = 0,tail = 1;
printf("%d\n",BFS(k));
return 0;
}
3. 实现KMP
总时间限制: 1000ms 内存限制: 65535kB
描述
给两个字符串A、B, 从A中找出第一次出现B的位置。
输入
第一行输入一个整数t,表示测试数据的个数
对于每组测试数据,输入两个字符串S T,S和T中间用一个空格隔开,每组数据占一行。
S,T的长度均不超过20000
输出
对于每组测试数据,输出A中找出第一次出现B的位置,如果A不包含B,输出-1
样例输入
3
AAAAbAA Ab
AAABBB BB
XXXXXX YY
样例输出
3
3
-1
提示
使用KMP快速匹配算法能解决本题。
#include <bits/stdc++.h>
using namespace std;
int* Next(string P) {
int m = P.length();
int* N = new int[m];//N[i]代表的是第i个字符的特征值,代表的是P[0]~P[i]这个串的最长前缀子串的长度
N[0] = 0;
for (int i = 1; i < m; i++) {
int k = N[i - 1];//default: k=0
while (k > 0 && P[i] != P[k])
k = N[k - 1];
if (P[i] == P[k])
N[i] = k + 1;
else N[i] = 0;
}
return N;
}
int KMPFind(string S, string P, int* N, int startIndex = 0) {//default: startIndex=0
int lastIndex = S.length() - P.length();
if (startIndex > lastIndex)return -1;
int j = 0;
for (int i = startIndex; i < S.length(); i++) {
while (S[i] != P[j] && j > 0)
j = N[j - 1];
if (S[i] == P[j])j++;
if (j == P.length())return i - j + 1;
}
return -1;
}
int main() {
int t;
cin >> t;
while (t--) {
string S, P;
cin >> S >> P;
cout << KMPFind(S, P, Next(P), 0) << endl;
}
return 0;
}
4. 前缀和后缀
-
总时间限制:
1000ms
-
内存限制:
65536kB
-
描述
编写一个程序,当给定字符串s1和s2时,求最长的s1前缀字符串,使得它同时是s2的后缀。
-
输入
(多组数据) 输入包含两行。第一行包含s1,第二行包含s2。 字符串均为小写字母构成
-
输出
输出由一行组成,首先是一个字符串,该字符串是s1的前缀和s2的后缀,空格后跟该字符串的长度。如果最长的字符串是空字符串,则输出应直接为0. s1和s2的长度最多为50000。
-
样例输入
clinton homer riemann marjorie
-
样例输出
0 rie 3
#include <bits/stdc++.h> using namespace std; const int N = 5e4 + 10; int lens1, lens2, Next[N]; char s1[N], s2[N]; void GetNext(){ int i, j; Next[0] = -1; i = 0; j = -1; while (i < lens1) { if (j == -1 || s1[i] == s1[j]) Next[++i] = ++j; else j = Next[j]; } } void Kmp() { int i, j; i = 0; j = 0; while (i < lens2) { if (j == -1 || s1[j] == s2[i]) { //s2是主串 j++; i++; } else j = Next[j]; } if (!j) cout << 0 << endl; else { for (int i = 0; i < j; i++)cout << s1[i]; cout << ' ' << j << endl; } } int main() { while (~scanf("%s%s", s1, s2)) { lens1 = strlen(s1); lens2 = strlen(s2); GetNext(); Kmp(); } return 0; }
5. 字符乘方
-
总时间限制:
3000ms
-
内存限制:
65536kB
-
描述
给定两个字符串a和b,我们定义ab为他们的连接。例如,如果a=”abc” 而b=”def”, 则ab=”abcdef”。 如果我们将连接考虑成乘法,一个非负整数的乘方将用一种通常的方式定义:a0=””(空字符串),a(n+1)=a*(a^n)。
-
输入
每一个测试样例是一行可打印的字符作为输入,用s表示。s的长度至少为1,且不会超过一百万。最后的测试样例后面将是一个点号作为一行。
-
输出
对于每一个s,你应该打印最大的n,使得存在一个a,让s=a^n
-
样例输入
abcd
aaaa
ababab
.
-
样例输出
1 4 3
-
提示
本问题输入量很大,请用scanf代替cin,从而避免超时。
-
来源
Waterloo local 2002.07.01
#include <bits/stdc++.h> using namespace std; //最大前缀子串,包含了最多的最小循环节 //那么总串减去最长前缀子串,得到的后缀就是最小循环节,如果长度是最小循环节的长度的整数倍 char s[1000010]; int Next[1000010]; void GetNext() { int j = -1; int i = 0; Next[0] = -1; while (i < strlen(s)) { if (j == -1 || s[i] == s[j]) { Next[++i] = ++j; } else j = Next[j]; } } int main() { while (1) { memset(s, NULL, strlen(s)); scanf("%s", s); if (!strcmp(s,".\0"))return 0;//这里有一点更改;两个字符串完全相等就会返回0 GetNext(); int len = strlen(s); if (len % (len - Next[len]) == 0)cout << len / (len - Next[len]) << endl; else cout << 1 << endl; } }
tips:C 库函数 - memset()
描述
C 库函数 void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。
声明
下面是 memset() 函数的声明。
void *memset(void *str, int c, size_t n)
参数
- str – 指向要填充的内存块。
- c – 要被设置的值。该值以 int 形式传递,但是函数在填充内存块时是使用该值的无符号字符形式。
- n – 要被设置为该值的字节数。
返回值
该值返回一个指向存储区 str 的指针。
实例
下面的实例演示了 memset() 函数的用法。
#include <stdio.h>
#include <string.h>
int main ()
{
char str[50];
strcpy(str,"This is string.h library function");
puts(str);
memset(str,'$',7);
puts(str);
return(0);
}
让我们编译并运行上面的程序,这将产生以下结果:
This is string.h library function
$$$$$$$ string.h library function
注意其他未涉及到的部分不会改变!也不会变成0或者null啥的。
6. 剪花布条
-
总时间限制:
1000ms
-
内存限制:
65536kB
-
描述
一块花布条,里面有些图案,另有一块直接可用的小饰条,里面也有一些图案。对于给定的花布条和小饰条,计算一下能从花布条中尽可能剪出几块小饰条来呢?
-
输入
输入中含有一些数据,分别是成对出现的花布条和小饰条,其布条都是用可见ASCII字符表示的,可见的ASCII字符有多少个,布条的花纹也有多少种花样。花纹条和小饰条不会超过1000个字符长。如果遇见#字符,则不再进行工作。
-
输出
输出能从花纹布中剪出的最多小饰条个数,如果一块都没有,那就输出0,每个结果之间应换行。
-
样例输入
abcde a3
aaaaaa aa
#
-
样例输出
0
3
-
来源
HDU
#include <bits/stdc++.h>
#define _CRT_SECURE_NO_WARNINGS
using namespace std;
int nexta[1006];
char t[1006], s[1006];
void getnexta(char s[])
{
memset(nexta, 0, sizeof(nexta));//清零
int k = -1, j = 0;
nexta[0] = -1;
while (j < strlen(s))
{
if (k == -1 || s[j] == s[k])
nexta[++j] = ++k;
else k = nexta[k];
}
}
int kmp(char s[], char t[]){//t模式串,s母串
getnexta(t);
int ans = 0;
int n = strlen(s), m = strlen(t);
int i = 0, j = 0;
while (i < n && j < m) {
if (j == -1 || s[i] == t[j]) {
i++;
j++;
}
else
j = nexta[j];
if (j == m){//根据题目要求改变
ans++;
j = 0;
}
}
return ans;
}
int main()
{
while (1)
{
scanf("%s", s);
if (strcmp(s, "#") == 0)
return 0;
scanf("%s", t);
printf("%d\n", kmp(s, t));
}
}
7. 中缀表达式的值
-
总时间限制:
200ms
-
内存限制:
1024kB
-
描述
人们熟悉的四则运算表达式称为中缀表达式,例如(23+34*45/(5+6+7))。在程序设计语言中,可以利用堆栈的方法把中缀表达式转换成保值的后缀表达式(又称逆波兰表示法),并最终变为计算机可以直接执行的指令,得到表达式的值。 给定一个中缀表达式,编写程序,利用堆栈的方法,计算表达式的值。
-
输入
第一行为测试数据的组数N 接下来的N行,每行是一个中缀表达式。表达式中只含数字、四则运算符和圆括号,操作数都是正整数,数和运算符、括号之间没有空格。中缀表达式的字符串长度不超过600。
-
输出
对每一组测试数据输出一行,为表达式的值
-
样例输入
3 3+5*8 (3+5)*8 (23+34*45/(5+6+7))
-
样例输出
43 64 108
-
提示
注意:运算过程均为整数运算(除法运算’/'即按照C++定义的int除以int的结果,测试数据不会出现除数为0的情况),输出结果也为整数(可能为负)。 中间计算结果可能为负。
#include<bits/stdc++.h>
using namespace std;
//定义优先级
const int END = -1;
const int VALUE = 0;
const int PLUS_MINUS = 1;
const int MULTI_DIV = 2;
const int OPEN_PAREN = 3;
const int CLOSE_PAREN = 4;
//定义符号对象的优先级
int priority(const char c) {
switch (c) {
case '\0':return END;
case '+':
case '-':return PLUS_MINUS;
case '*':
case '/':return MULTI_DIV;
case '(':return OPEN_PAREN;
case ')':return CLOSE_PAREN;
default: return VALUE;
}
}
struct NODE {
int type = 0;// 栈里的元素类型, 0代表运算符, 1代表浮点数,默认为运算符,因为需要处理的大部分都是运算符
char op = '\0';
int number = 0;
};
NODE calc(int a, int b, char op) {
int re = 0;
switch (op) {
case '+': re = a + b; break;
case '-': re = a - b; break;
case '*': re = a * b; break;
case '/': re = a / b; break;
}
NODE result;
result.number = re;
result.type = 1;
return result;
}
stack<NODE>opStack;//存储运算符的临时栈,在生成后缀表达式的过程中会全部压入postFixStack中;只有运算符
stack<NODE>postFixStack;//后缀表达式;运算符和数值都有
stack<NODE>reverseStack;//后缀表达式反过来,这样不断弹出就相当于是从左往右扫描后缀表达式;运算符和数值都有
stack<NODE>answerStack;//用于计算和存储后缀表达式中间结果的栈;只有数值
//因为在每个循环里面每个栈都会排空,所以可以放在外面,不用每次循环都重新开辟储存空间
int main() {
int N;
cin >> N;
while (N--) {
char s[605] = "\0";//注意是双引号
cin >> s;
char* p, * q;
p = q = s;
int curPriority;
/**********生成后缀表达式**********/
while (1) {
curPriority = priority(*p);
NODE tmp;
if (curPriority == END) {
while (!opStack.empty()) {//符号栈剩下的符号全部弹出
postFixStack.push(opStack.top());
opStack.pop();
}
break;
}
if (curPriority == VALUE) {
char numStr[605] = {};
int i = 0;
for (q = p; priority(*q) == VALUE; q++) {
//使用指针q进行搜索,直到q指向下一个符号或字符串末尾为止,同时将这一段数字储存在字符串中,这是为了处理两位及以上的数
numStr[i++] = *q;
numStr[i] = '\0';
}
tmp.number = atoi(numStr);//转换为整数
tmp.type = 1;
postFixStack.push(tmp);
p = q;//转到下一个符号(或者末尾)
}
else if (curPriority == OPEN_PAREN) {//左括号,压入符号栈
tmp.op = '(';
opStack.push(tmp);
p++;
}
else if (curPriority == CLOSE_PAREN) {//右括号,不断弹出直到遇到左括号
while (!opStack.empty()) {//其实也可以换成while(1),因为必然会遇到左括号(如果输入符合规范),但是万一不规范while(1)就有死循环的风险
if (opStack.top().op == '(') {
opStack.pop();
break;
}
postFixStack.push(opStack.top());
opStack.pop();
}
p++;
}
else {
//如果栈顶的四则运算符的优先级比当前运算符优先级高、或者相等(3 - 2 + 1应该转换为3 2 - 1 +),则将栈顶的弹出
//但是满足栈顶优先级大于当前优先级的还有栈顶为左括号的情况,这个时候不能将左括号弹出(左括号需要等到遇到右括号的时候再处理)
//不可能遇到右括号,因为右括号根本不会入栈,一遇到右括号就全部弹出直至遇见左括号了
//当然,前提是非空
while (!opStack.empty() && priority(opStack.top().op) >= curPriority && priority(opStack.top().op)!= OPEN_PAREN) {
postFixStack.push(opStack.top());
opStack.pop();
}
/*
原来的语句:
while (!opStack.empty()&& priority(opStack.top()) >= curPriority && priority(opStack.top()!= OPEN_PAREN))
知道哪里错了吗?
是...priority(opStack.top()【)】!= OPEN_PAREN【)】而不是...priority(opStack.top()!= OPEN_PAREN【))】!!!
这样会导致这一句永远是priority(1)/逻辑0而不执行循环了
*/
tmp.op = *p;
opStack.push(tmp);
p++;
}
}
/**********反转后缀表达式**********/
while (!postFixStack.empty()) {
reverseStack.push(postFixStack.top());
postFixStack.pop();
}
/**********计算后缀表达式的值**********/
while (!reverseStack.empty()) {
NODE tmp = reverseStack.top();
reverseStack.pop();
if (!tmp.type) {//如果tmp是字符
NODE topNum = answerStack.top();//答案栈栈顶的数字
answerStack.pop();
NODE nextTopNum = answerStack.top();//次顶的数字;这要求次顶的数字必须存在,即后缀表达式扫描到运算符时,前面至少有两个数字
answerStack.pop();
char ope = tmp.op;
NODE result = calc(nextTopNum.number, topNum.number, ope);//注意次序,是【次顶 ope 栈顶】(4 5 -应为4-5)
answerStack.push(result);
}
else
answerStack.push(tmp);
}
cout <<answerStack.top().number<< endl;
while(!answerStack.empty())//其实只有一个数了,但是以防万一还是尽量排空比较好,防止影响到下一次循环
answerStack.pop();
}
return 0;
}
8. 走迷宫
总时间限制: 1000ms 内存限制: 65536kB
-
描述
一个迷宫由R行C列格子组成,有的格子里有障碍物,不能走;有的格子是空地,可以走。
给定一个迷宫,求从左上角走到右下角最少需要走多少步(数据保证一定能走到)。只能在水平方向或垂直方向走,不能斜着走。 -
输入
第一行是两个整数,R和C,代表迷宫的长和宽。( 1<= R,C <= 40)
接下来是R行,每行C个字符,代表整个迷宫。
空地格子用’.‘表示,有障碍物的格子用’#‘表示。
迷宫左上角和右下角都是’.’。 -
输出
输出从左上角走到右下角至少要经过多少步(即至少要经过多少个空地格子)。计算步数要包括起点和终点。 -
样例输入
5 5
…###
#…
#.#.#
#.#.#
#.#… -
样例输出
9
//增加了一点注释
#include<bits/stdc++.h>
using namespace std;
char maze[50][50] = {};
int row, col;
struct NODE {
int mRow = 1;
int mCol = 1;
int steps = 1;
};
//与节点有关的函数
void markNode(NODE currPoint) {
maze[currPoint.mRow][currPoint.mCol] = 'x';//. not visited, # walls, x visited.虽说x和#也不需要区分
}
NODE asideNode(NODE currPoint,int i) {
NODE tmp;
switch (i)
{
case 1://left
tmp.mRow = currPoint.mRow;
tmp.mCol = currPoint.mCol - 1;
return tmp;
case 2://right
tmp.mRow = currPoint.mRow;
tmp.mCol = currPoint.mCol + 1;
return tmp;
case 3://down
tmp.mRow = currPoint.mRow + 1;
tmp.mCol = currPoint.mCol;
return tmp;
case 4://up
tmp.mRow = currPoint.mRow - 1;
tmp.mCol = currPoint.mCol;
return tmp;
default:
cout << "SOMETHING WRONG" << endl;
return currPoint;
}
}
int isUnvisited(NODE currPoint) {
if (maze[currPoint.mRow][currPoint.mCol] == '.')return 1;
return 0;
}
//可以走的节点,在循环过程中会不断抛弃老节点(pop),更新“路径进展”(push),即使有多条路径可以到达终点,也会是更短的那条先遇到/出队列
queue<NODE>validNodes;
int main() {
//输入,构建迷宫
cin >> row >> col;
for (int i = 0; i <= row + 1; i++) {
for (int j = 0; j <= col + 1; j++) {
if (i == 0 || i == row + 1)
maze[i][j] = '#';//把边界外的一圈都标记为#,省略判断是否越界
else if (j == 0 || j == col + 1)
maze[i][j] = '#';
else
cin >> maze[i][j];
}
}
//使用队列判断
NODE startPoint;
markNode(startPoint);
validNodes.push(startPoint);
while (!validNodes.empty()) {//比while(1)要好一些
NODE currPoint = validNodes.front();
validNodes.pop();
for (int i = 1; i <= 4; i++) {//左右下上
NODE tmp = asideNode(currPoint, i);
if (tmp.mCol == col && tmp.mRow == row) {//出口
cout << currPoint.steps + 1 << endl;
return 0;
}
if (isUnvisited(tmp)) {
markNode(tmp);
tmp.steps = currPoint.steps + 1;
validNodes.push(tmp);
}
}
}
cout << "NO RIGHT PATH!" << endl;//如果没有在循环里面结束程序,那么说明没有能够到达终点的路径
return 0;
}
9. 由中根序列和后根序列重建二叉树
-
总时间限制:
500ms
-
内存限制:
65535kB
-
描述
我们知道如何按照三种深度优先次序来周游一棵二叉树,来得到中根序列、前根序列和后根序列。反过来,如果给定二叉树的中根序列和后根序列,或者给定中根序列和前根序列,可以重建一二叉树。本题输入一棵二叉树的中根序列和后根序列,要求在内存中重建二叉树,最后输出这棵二叉树的前根序列。用不同的整数来唯一标识二叉树的每一个结点,下面的二叉树 中根序列是9 5 32 67后根序列9 32 67 5前根序列5 9 67 32
-
输入
两行。第一行是二叉树的中根序列,第二行是后根序列。每个数字表示的结点之间用空格隔开。结点数字范围0~65535。暂不必考虑不合理的输入数据。
-
输出
一行。由输入中的中根序列和后根序列重建的二叉树的前根序列。每个数字表示的结点之间用空格隔开。
-
样例输入
9 5 32 67
9 32 67 5
-
样例输出
5 9 67 32
#include <bits/stdc++.h>
using namespace std;
vector<int> a;
void find(int* ldr, int* lrd,int len) {//找到根节点,前序输出
if (len >= 1)
cout << lrd[len - 1] << " ";//输出后根序列的末端,即根节点
if (len > 1)
for (int i = 0; i < len; i++) {
if (ldr[i] == lrd[len - 1]) {//中根序列中,根节点左边的是左节点,右边是右节点
find(ldr, lrd, i);
find(ldr + i + 1, lrd + i, len - i - 1);
return;
}
}
}
int main() {
int len;
int* lrd, * ldr;
int t;
while (cin>>t) {
a.push_back(t);
}
len = a.size() / 2;
ldr = a.data();
lrd = a.data() + len;
find(ldr, lrd, len);
return 0;
}