一、第二周学习总结表

  • 本周学习完成情况

    Contest完成情况
    PTA函数与递归练习12/13
    函数与递归练习6/10
    STL(vector,stack,queue)练习6/6
    STL经典练习12/12
    回溯应用练习赛5/5
    其他练习5/5
    比赛补题2/2
  • 个人感悟

​ 本周为ACM集训的第二周,回顾了函数递归,学习了新的回溯算法。这一周里,几场练习赛下来,深刻感受到了学习新算法和数据结构的重要性。比如牛客的大部分题目,题面看起来并不算难,但是数据范围总是会让传统的办法无效,这时候新的算法和数据结构的重要性就体现的很重要。比如前一天的Atcoder,前三十分钟我便写完了前三题,而后一个小时因为知识点的受限,虽然后面的题能看出来大概方向但是依旧写不出实际的代码。比如D题能直接看出来是动态规划,但是没有系统的学习和大量的练习我最终也没有推出最优子结构。再比如C题,熟悉了map以后,我仅仅花了五分多钟就写了出来。如果没有接触到STL容器,可能要做很久。再比如周日的河南大学萌新赛的J题,自学了同余定理之后,我一下就有了思路,虽然最后没有优化好还是有部分超时,没有完整的把题目通过,但是对知识点有了深刻的理解,对解题思路有了更新的感悟。

​ 所以接下来一周,我对自己的计划是完成日常练习题的基础上,学习更多新的知识点,和做题的技巧。不但要刷日常的练习还要刷不同平台的题目,感受出题的不同方向和考察知识点的角度。

二、本周学习新内容

1.四舍五入简便写法

floor(x+0.5)

2.使用assert来调试程序

int x=2;
assert(x>=3);
cout<<x<<endl;

/*报错
Assertion failed!

Program: E:\Code\VSCode\Test.exe
File: Test.cpp, Line 15\``

Expression: x>=3
*/

3.auto register

auto可以自动根据后面的表达式,来给变量赋予类型。

register可以将常用的变量放到寄存器中,提高运行的效率。

4._gcd

c++自带的求最大公约数的函数,底层原理是欧几里得算法。

5.深度优先搜索(DFS)

  1. 核心

​ 沿着树的深度遍历解答树的结点,尽可能的深的搜索。前进中遭遇失败,则回溯到前进前的结点,另寻别的同路继续搜索,直到满足条件

  1. 思想

​ 在寻找终点的过程中将当前状态压入栈,若遇到死路则栈顶出栈,直到某个状态可以继续发散

  1. 注意

​ 注意时间复杂度满不满足使用DFS,容易爆栈

  1. 核心思想代码:

void dfs(当前状态){
如果到终点就返回;
标记当前状态已经访问过;
for (当前状态能走的所有步){
新状态=当前状态转移之后的状态;
if(新状态不符合要求){
continue;//直接跳过,寻找当前状态的下一种新状态
}
if(新状态没有访问过 && 符合要求){
dfs(新状态);
}
}
}

6.998244353

一个神奇的数,常用被取模。

7.同余定理

  1. 同余定理即

  • 证明:

​ 其实就是余数在相减的过程中被减去了

  1. 其他性质

    可以在题目中对大数取余的时候可以用到

​ 两个数同余,那么他们的平方也同余,他们加上同样的数也同余

8.容器的一些不熟悉的使用方法

  1. priority_queue

  • 每次push和pop都会动态调整容器内的序列
  • 其实就是一个堆,默认为最大堆,从小到大排,所以q.top()为最大值,q.pop()就是把最大值移除

  • erase的返回值为删除元素后的迭代器下一个,所以删除完了有时候需要iter—

  • 定义应该这样priority_queue<Type, [Container],Funtional>

    而如果使用了第三个参数,第二个参数也必须加上

    priority_queue<int, greater<>> pq;//这是错误的
    priority_queue<int,vector<int> , greater<>> pq;//这是对的
  1. set

  • 访问速度是O(log~n~)
  • 底层是二叉搜索树
  • erase(item)把item元素删除
  • lower_bound(item):返回大于等于目标item的第一个值
  • lower_bound(item):返回大于目标item的第一个值
  1. transform

  • transform(s.begin(),s.end(),s.begin(),::tolower);

  • 字符串全部转换大小写

  1. pair

  • 一个二元组
  • 用法pair<string,int> p
  • p.first()表示第一个元素,p.second()表示第二个元素

三、题解一览

1.河南理工大学萌新赛 - B - 宝石

题目链接

题目标签

打表;哈希

题目描述

小Y得到了一堆宝石,但是其中有一些宝石是假的没有价值,显然小Y并不想要这些假的宝石,小Y被告诉了一个鉴别真假宝石的方法。将这些宝石排成一行,按照从 1∼n标上标号并得出其真实度,如果第 i 个宝石的真实度等于标号大于 i 的三个宝石的真实度乘积(可以使用标号相同的三个宝石),这个宝石被认为是真的,你能帮小Y找出所有真的宝石的数量吗。

第一行一个整数 n (4≤n≤1500),表示所有宝石数量。

第二行 n个整数 ai(−10^6^≤ai≤10^6^),表示每个宝石的真实度。

题目解析

直接暴力毫无疑问,数据量过大会TLE。

那么我就想到了打表,将两个宝石的乘积乘起来,再用map存起来。遍历宝石,如果除的通,且在map里面,即宝石为真的,ans++。

但是新的问题又出现了:如果直接每个宝石都正序打表,从头开始数据量过大。如果储存起来,可以采用记忆化思想减少重复运算,但又不能在针对某个宝石进行遍历时,知道表中的数据是不是宝石后面的乘积。

由此可以尝试一下倒序打表,这样打表的好处在于倒序打表,针对每一个新遍历的宝石i,进行打表。这样一来不会重复打表,可以保证表中的数据一定时某个宝石i后面的数据的乘积

带着这种思想便可以大致写出程序的大概了,但是仍需注意一些问题:

  1. 下标问题:针对第i个宝石判断真假时,对下标为n-1的宝石到i+1的宝石进行求积,并存入map中,再用宝石i ÷ [n-1,i+1]的宝石求商,如果商被标记过,ans++
  2. 求商:必须判断num[i]!=0,不然会报错
  3. 特判:对a[i] == 0 && a[j] == 0 的情况需要特判
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define maxnum 500005
#define inf 1000000007
using namespace std;
const double PI = acos(-1.0);

int main()
{
int n;
cin >> n;
int num[n + 100];
memset(num, 0, sizeof(num));
unordered_map<ll, bool> mp;
for (int i = 0; i < n; i++)
{
cin >> num[i];
}
//需要倒序,并且一边打表一边判断
//正序打表,储存下来不能判断是不是该宝石后的,所以必须倒序
int ans = 0;
for (int i = n - 1; i >= 0; i--) //倒序,i作为判断的宝石
{
for (int j = n - 1; j > i; j--) //倒序更新,并且随着i的不断减小,只更新没有出现过的乘积
{
mp[num[i + 1] * num[j]] = 1;
}
for (int j = n - 1; j > i; j--) //关于第i个宝石,针对其后面的所有宝石,进行试除,如果可除通,并且被标记过,说明成功的
{
if (num[i] == 0 && mp[0] == 1)//对于1 1 0 0 这种情况,需要特判,因为除数和被除数都为0
{
ans++;
break;
}
if (num[j] != 0 && num[i] % num[j] == 0 && mp[num[i] / num[j]] == 1)//常规判断
{
ans++;
break;
}
}
}
cout << ans << endl;
return 0;
}

2.计算24点,并输出

题目标签

递归;搜索

题目描述

给出4个数,计算能否组成24点。

计算为+ - × ÷

可以加小括号

题目解析

递归函数的参数为

dfs(num,len,str);
//num为数组
//len为
//str为保存的输出字符串的数组

对于数组num,每次任取两个不一样的数,相互操作,分别dfs

len为数组中的数的个数。如果len为1,说明已经完成全部操作

str以字符串的形式记录了对应操作的字符串

不过每次dfs时,需要新开一个数组

先记录不被选中的两个数,再把新合并的数存入数组中,再进行下一步操作

如果用原数组,或者全局变量,数据会被记录,不同的操作还需要回溯,不方便dfs

通过代码

#include <cstring>
#include <iomanip>
#include <cmath>
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
#include <set>
#include <numeric>
#include <ctime>
#define ll long long
#define true 1
#define false 0
#define eps 1e-6
#define bool int
using namespace std;
int vis[4];
int sum=0;
bool flag=false;
bool fflag;
void dfs(double a[],int len,string aa[4]){
if (len==1)//剩下一个数,说明操作完了
{
if (fabs(24-a[0])<eps)//因为有小数,存在误差
{
// printf("Yes");//不能直接输出,因为有多种情况,就会输出多次
flag=true;
}

}
if (flag==true)//如果已经找到了,就不再进行下面的计算了
{
if (fflag==false)
{
cout<<aa[0]<<endl;
fflag=true;
}
return;
}

for (int i = 0; i < len ; i++)//挑选两个数操作
{
for (int j = 0; j < len ; j++)
{
if (i==j)//不能选相同的
{
continue;
}
double t[4]={0};
string tt[4];
memset(tt,0,sizeof(tt));
int l=0;
for (int k = 0; k < len; k++)//先把除了选中的数以外的数存入新数组,不然等会不同的操作每次都要存,重复操作
{
if (k!=i && k!=j)
{
t[l]=a[k];//除了选中的数以外的数存入新数组
tt[l]=aa[k];//除了选中以外的数
l++;
}

}
//控制括号的输出
string ykh=")";
string zkh="(";
//如果最后一步不用加括号
if (len==2)
{
ykh="";
zkh="";
}

t[l]=a[i]+a[j];//把合并的两个数存入新数组
tt[l]=zkh+aa[i]+"+"+aa[j]+ykh;//表达式存入新数组
dfs(t,len-1,tt);//下一步搜索


t[l]=a[i]-a[j];//把合并的两个数存入新数组
tt[l]=zkh+aa[i]+"-"+aa[j]+ykh;//表达式存入新数组
dfs(t,len-1,tt);//下一步搜索

t[l]=a[j]-a[i];//把合并的两个数存入新数组
tt[l]=zkh+aa[j]+"-"+aa[i]+ykh;//表达式存入新数组
dfs(t,len-1,tt);//下一步搜索

t[l]=a[i]*a[j];//把合并的两个数存入新数组
tt[l]=aa[i]+"*"+aa[j];//表达式存入新数组
dfs(t,len-1,tt);//下一步搜索

//对除法进行搜索
if (a[i]!=0)
{
t[l]=a[j]/a[i];
tt[l]="("+aa[j]+"/"+aa[i]+")";
dfs(t,len-1,tt);
}
if (a[j]!=0)
{
t[l]=a[i]/a[j];
tt[l]="("+aa[i]+"/"+aa[j]+")";
dfs(t,len-1,tt);
}
}

}


}
int main(){
double num[4];
while (scanf("%lf %lf %lf %lf",&num[0],&num[1],&num[2],&num[3]) && num[0]+num[1]+num[2]+num[3]!=0)
{
flag=false;
fflag=false;
string str[4]={to_string(int(num[0])),to_string(int(num[1])),to_string(int(num[2])),to_string(int(num[3]))};

dfs(num,4,str);
if (flag==true)
{
}else{
printf("-1\n");
}

}

}

3.PTA - 汉诺塔问题

题目标签

递归

题目描述

将n个盘子从原始柱子a借助过渡柱b移动到目标柱c

题目解析

将n个盘子从a借助b移动到c需经过以下三步操作

  1. 将n-1个盘子从a借助c移动到b
  2. 将第n个盘子从a移动到c
  3. 将n-1个盘子从b借助a移动到c

而每一步操作又可以递归一次

递归的出口就在于

当只有一个盘子时可以直接将a移动到c

通过代码

#include <stdio.h>

void MoveTower(int num, char src, char dst, char trs){
if (1 == num)
{
printf("%d: %c -> %c\n",num, src, dst);;
}
else
{
MoveTower(num - 1, src, trs, dst);
printf("%d: %c -> %c\n",num, src, dst);
MoveTower(num - 1, trs, dst, src);
}
}
int main()
{
int n;
char s, d, t;
scanf("%d %c %c %c", &n, &s, &d, &t);
MoveTower(n, s, d, t);
return 0;
}

4.函数递归 - E - 正方形

题目链接

题目标签

模拟;矩阵

题目描述

设有一个n×n的矩阵,给出点与点之间连接的关系,求出一共有多少个正方形

题目解析

由于数据量少,采用纯暴力法;

遍历每一个点,对该点遍历每一个正方形的边长,判断是否有连线,有的话则cnt[边长]++

那么对于每一个边长的正方形,该如何去遍历,判断有无连线呢

因为连线是离散的,还没有学图论还不知道怎么优化,去松弛点与点的关系

这里直接先用一个四维数组num[i~1~][j~1~][i~2~][j~2~]表示i~1~j~1~到i~2~j~2~之间有连线

那么如何判断边长为2及以上的点有连线呢

这里直接用一个循环判断

边长为k时

for(int l;l<=k;l++){

对点之间的判断加上一个l,表示当前从原始的点出发了几步,来判断当前点到目标点之间的点之间有没有连线
num[i][j+l][i][j+l+1]==1;
num[i+k][j+l][i+k][j+l+1]==1;
num[i+l][j][i+l+1][j]==1;
num[i+l][j+k][i+l+1][j+k]==1
上述四个关系式来表示边长为l的长方形走步长为k时是否点与点之间连着线
}
如下图黑线表示l的长度

image-20220719205736448

几个细节要注意:

  1. k的步长部分要循环到k<=min(n-i,n-j)这样可以减少无用的循环
  2. 输入部分要注意,Vertical的输入是先输入列再输入行
  3. uva的输入输出很严格,换行要多注意细节,与标准输出进行对比

通过代码

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define maxnum 500005
#define inf 1000000007
using namespace std;
const double PI = acos(-1.0);
int main()
{
int t=0;
int n,m;
while(cin>>n>>m){
if (t!=0)
{
cout<<"\n**********************************\n"<<endl;
}
++t;

int num[11][11][11][11];//四维数组,存储
memset(num,0,sizeof(num));
for (int k = 0; k < m; k++)
{
char opt;
int i,j;
cin>>opt>>i>>j;
if (opt=='H')
{
num[i][j][i][j+1]=1;//两双向链接
num[i][j+1][i][j]=1;
}else{
num[j][i][j+1][i]=1;
num[j+1][i][j][i]=1;
}
}

//枚举i j
int cnt[10]={0};
for (int i = 1; i <= n; i++)
{
for(int j = 1;j<= n;j++){
//对于点i,j根据步长k枚举


for(int k=1;k<=min(n-i,n-j);k++){//k为步长
bool flag=true;

for (int l = 0; l < k; l++)//l为到i,j+k之间的点,一步一步判断
{
if( num[i][j+l][i][j+l+1]==1 &&
num[i+k][j+l][i+k][j+l+1]==1 &&
num[i+l][j][i+l+1][j]==1 &&
num[i+l][j+k][i+l+1][j+k]==1)
{//判断四个点的步长
continue;//步长内再走一步
}
}

//走完k步以后判断走不走得通
if (flag==false)
{
continue;//走不通,下一个步长
}
if (flag==true)
{
cnt[k]++;//走的通,正方形大小为k的++
}

}


}
}

cout<<"Problem #"<<t<<endl<<endl;
bool f=false;
for (int i = 1; i < 10 ; i++)
{
if(cnt[i]!=0){
cout<<cnt[i]<<" "<<"square (s) of size "<<i<<endl;
f=true;
}
}

if(f==false){
cout<<"No completed squares can be found."<<endl;
}
}
// printf("Time used = %.2lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}

5.火车站出栈合法性问题

两种方法

1.枚举每一种出栈顺序。用dfs做

2.模拟出栈

6.The Blocks Problem

题目标签

vector

题目描述

有n个小方块,编号从0-n-1

给出一序列指令操作

求出最终小方块的摆放方式

move a onto b

  • a和b都是方块的编号,先将a和b上面所有的方块都放回原处,再将a放在b上。

move a over b

  • a和b都是方块的编号,先将a上面所有的方块放回原处,再将a放在b上。(b上原有方块不动)

pile a onto b

  • a和b都是方块的编号,将a和其上面所有的积极组成的一摞整体移动到b上。在移动前要先将b上面所有的积极都放回原处。移动的一摞方块要保持原来的顺序不变。

pile a over b

  • a和b都是方块的编号,将a和其上面所有的方块组成的一摞整体移动到b所在一摞方块的最上面一个方块上。移动的一摞方块要保持原来的顺序不变。

quit

  • 结束方块的操纵。

题目解析

一开始的想法:

搞十个栈,然后存入一个结构体,结构体记录了当前结点存在的栈的位置

一番尝试后发现过于复杂

容易把自己绕晕,而且push进栈里的结构体,也只能push值进去,而不是原来的,所以不能直接从读入的a,b进行查找,反而不如遍历查找来的效率高

正确解法:

使用一个vector数组;

使用resizie操作可以轻松的把上面不需要的元素清楚。比栈方便很多

而读入进来的结点在哪个位置只需要手写一个find函数,参数是调用了传入形参的引用,这样就可以在外部函数里操作。也不用把查找完的值放回主函数内。

关于指令操作:

其实只需要2种指令;一种清理上方元素归为,一种为把所有元素全部放进去

因为一个元素的时候,某一元素上面的所有元素就是他自己,所以不用做单独的放一个元素的操作

通过代码

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define maxnum 500005
#define inf 1000000007
using namespace std;
const double PI = acos(-1.0);
const int N=25;
int n;
vector<int> v[N];
//用vector来模拟
//每次要先找到
//定理:
// onto清空b上的
// move清空a上的
// 两种情况都需要把x上的放到y上
//注意:
// 都设置为全局变量,方便操作
void print()
{
for (int i = 0; i < n; i++)
{
cout << i << ":";
for (int j = 0; j < v[i].size(); j++)
cout << " " << v[i][j];
cout << endl;
}
}
void find(int key,int &x,int &y){
for(int i=0;i<n;i++){
for (int j = 0; j < v[i].size(); j++){
if(v[i][j]==key){
return;
}
}
}
}
//clear:把v[ax][ay]-v[ax][max]的清空,归位
void clear(int ax,int ay){
for (int j = ay+1; j < v[ax].size(); j++)
{
int b = v[ax][j];
v[b].push_back(b);
}
v[ax].resize(ay + 1);
}
//把v[ax]上从ay到最大元素全部搬到v[bx]上,然后再重新resize v[ax]的大小,不需要做过多的删除操作
void renew(int ax,int ay,int bx){
for (int i = ay; i < v[ax].size(); i++)
{
v[bx].push_back(v[ax][i]);
}
v[ax].resize(ay);
}

int main()
{
cin>>n;
for (int i = 0; i < n; i++)
{
v[i].push_back(i);
}
//初始化
string op;
while (cin>>op && op!="quit")
{
string way;
int a,b;
cin>>a>>way>>b;
//先找到点的位置,再进行下一步操作
//定义两个坐标来储存变量,让外部函数调用他们的引用
int ax,bx,ay,by;
find(a,ax,ay);//操作,让ax,ay变成a所在的位置
find(b,bx,by);//操作,让bx,by变成b所在的位置
if (ax==bx)
{
continue;
}
if (op=="move")
{
clear(ax,ay);
}
if (way=="onto")
{
clear(bx,by);
}
renew(ax,ay,bx);
}
print();
return 0;
}

7.组合数问题

题目标签

搜索

题目描述

给定n个数,从中取r个数进行组合,求解法和个数

题目解析

n个数,取r个进行组合。也就是dfs r层,每层都要取数,数的范围是前一层取的数+1 ~ n。

为了保证组合没有重复的,所以每层取得数都是前一层数的+1.同时还能保证是按照升序进行输出。

通过代码

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define maxnum 500005
#define inf 1000000007
using namespace std;
const double PI = acos(-1.0);
int n;int r;
int all;
int a[22]={0};
void dfs(int begin){
if (begin==r+1)//终点
{
for (int i = 1; i <= r; i++)
{
printf("%3d",a[i]);
}
cout<<endl;
return;
}
for (int i = a[begin-1]+1; i <= n; i++)//i是必须是递增的
{
a[begin]=i;
dfs(begin+1);//递增后的下一步搜索
}
}
int main()
{
cin>>n>>r;
dfs(1);
return 0;
}

8.数的全排列

题目标签

搜索

题目描述

给定n个数1-n,求全排列

题目解析

除了next_permutation以外,还有几种使用深度优先搜素的算法来进行求解

首先将1-n全部存入数组,存进去之后,用dfs来进行数的交换,交换的顺序是从最后交换到第一个。

通过代码

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define maxnum 500005
#define inf 1000000007
using namespace std;
const double PI = acos(-1.0);
int a[10] = {0};
int num=0;
//定义一个数组,让搜索时可以访问到那些元素
//每次让他们交换一个位置,然后该交换位置之后的继续向下搜索交换位置的
//每次交换位置的是begin和i交换,i++,也就是从begin开始,到begin以后的要进行交换
void Perm(int begin, int end)
{
//先写出出口
if (begin == end)
{
for (int i = 0; i < end; i++)//必须是<=end
{
printf("%5d", a[i]);
}
num++;
printf("\n");
}
for (int i = begin; i < end; i++)//必须是<=end
{
swap(a[i], a[begin]);
Perm(begin + 1, end);
swap(a[i], a[begin]);
}
}

int main()
{
int n;
scanf("%d",&n);
for (int i = 0; i < n; i++)
{
a[i] = i + 1;
}
//初始化完成
Perm(0, n);
return 0;
}

9.N皇后问题

题目标签

深度优先搜索

题目描述

一个N*N的棋盘上有N个棋子,棋子的攻击范围是同一行,同一列,同一对角线(正副两个对角线),要求求出一种摆放的顺序,使得摆放八个棋子不会相互攻击到。

题目解析

采用dfs的方式求解。

因为棋子不可能会被放在同一行,所以从上往下的行数进行dfs,注意每次放置一个棋子,都需要为他的攻击范围进行标记,即该点不能被放置新的棋子。

即一行一行的往下搜索,每一层的搜索,都将选择1-n的位置判断是否能摆放,能摆放则进行摆放,每种摆放位置都进入棋盘的下一行进行下一步dfs搜索。

  • 需要注意的点:

1. 如果采用二维数组进行标记:

那么需要在走不通的路之后进行回溯时,需要对标记过的进行撤销。而标记和撤销标记的关键步骤在于,不要使用a[i][j]=1 或这a[i][j]=0的方式进行标记撤销,而要使用a[i][j]=++或这a[i][j]—的方式。原因在于,如果一个点被他上面的棋子标记了,同时也被另一个棋子标记了,如果另一个棋子走不通,撤销标记时,直接把该点置为0的话,那么第一个棋子标记他的效果则会被另一个棋子的撤销而失效。所以采用++ — 的方式,即一个棋子不再是被标记而是被标记x次。这样撤销的时候不会直接撤销全部效果。而是—,如果>0,说明还有标记他的。只有当他变为0,也就是所有标记他的棋子都回溯了以后,才能说明这一点被真正的重新可以使用

2. 优化后的简便方法:

只需要用ans数组记录摆放的列即可,因为ans[i]=x.就已经说明了是i行x列的被放上了棋子。

由于采用二维数组做的复杂性,和回溯撤销非常麻烦。这里引入一个刘汝佳《算法竞赛入门经典》中的一个快速判断是否能放置棋子的办法。

同一列其实非常好判断,只需在下一个棋子的时候,枚举之前全部下过的棋子的列数(用ans数组保存的哪个),当前列和之前下过的列是否相等,如果相等说明在同一列。

而同一对角线则需要一些数学性质:

  1. 同一主对角线上的点,y-x的值都是相等的。如(1,1) (2,2) (3,3)都处于同一对角线上,而他们的y-x值都相等。再如(1,2) (2,3) (3,4) 也是存在于同一主对角线上。y-x的值也相等。
  2. 副对角线上的点则是x+y的值都相等,详见下图

image-20220723120939376

通过代码

  • 二维数组标记版本

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define maxnum 500005
#define inf 1000000007
using namespace std;
int tree = 0;
const double PI = acos(-1.0);
int n;
int a[13][13] = {0};
int ans[13] = {0};
int num = 0;
void dfs(int k)
{
if (k == n) //终点
{
if (num < 3)
{
for (int i = 0; i < n; i++)
{
printf("%d%s", ans[i], i == n - 1 ? "\n" : " ");
}
}
num++;
return;
}

//每个深度(行),枚举列放棋子,i为列,k为行
for (int i = 0; i < n; i++)
{
if (a[k][i] < 1) //没被标记可以放
{
tree++;
ans[k] = i + 1;
a[k][i]++;
//标记不能放置的棋子,同行同列,斜边
//同行可以其实不标记,因为是一行一行下去的
for (int j = k; j < n; j++) //同一列
{
a[j][i]++;
}
for (int j = k + 1, xie = 1; j < n; j++) //向下的斜边
{
if (i + xie < n)
a[j][i + xie]++;
if (i - xie >= 0)
a[j][i - xie]++;
xie++;
}
dfs(k + 1);

//如果用新数组递归下去,回溯不用删除
for (int j = k; j < n; j++) //同一列
{
a[j][i]--;
}
for (int j = k + 1, xie = 1; j < n; j++) //向下的斜边
{
if (i + xie < n)
a[j][i + xie]--;
if (i - xie >= 0)
a[j][i - xie]--;
xie++;
}
a[k][i]--;
}
}
}
int main()
{
cin >> n;
dfs(0);
cout << num << endl;
return 0;
}
  • 一维数组优化版本

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define maxnum 500005
#define inf 1000000007
using namespace std;
int tree = 0;
const double PI = acos(-1.0);
int n;
int a[13][13] = {0};
int ans[13] = {0};
int num = 0;
//优化方法:
//主要是对判断是否被标记做优化
//引理:处于同一主对角线上的,纵坐标值-横坐标值相等。处于同一副对角线上的,纵坐标值+横坐标值值相等。
//我们也不需要使用二维数组来储存,因为a[i]的i就是行号,a[i]的值就是列号。
void dfs(int k)
{
if (k == n) //终点
{
if (num < 3)
{
for (int i = 0; i < n; i++)
{
printf("%d%s", ans[i], i == n - 1 ? "\n" : " ");
}
}
num++;
return;
}

for (int i = 0; i < n; i++)//针对每一行有n种不同的列可以摆放棋子
{
bool ok = true;
ans[k] = i + 1;//下标从0开始,但输出的是从1开始,i+1。不会影响引理的判断,因为所有的列号都被+1了
for (int j = 0; j < k; j++)
{
if (ans[k] == ans[j] || k - ans[k] == j - ans[j] || k + ans[k] == j + ans[j]) //引理
{
ok = false;//如果位于同一列/斜边---不能进行下一步dfs
break;
}
}
if (ok == true)
dfs(k + 1);
}
}
int main()
{
cin >> n;
dfs(0);
cout << num << endl;
return 0;
}

10.信息编码

题目标签

哈希

题目描述

给定一串字符串,为解码的信息。

每个字符分别对应着 0 01 10 000 001 010 011 100 101 110 0000 0001 ······。没有全部为1的解码串

当读取到这样的字符是,可以解码。

给出一串01组成的密钥,前三个代表着字符的长度,后面相应的字符串代表着字符的解码串,读到全1时表面当前长度的解码串结束,读到000时表示当前case结束

题目解析

用一个二维数组来表示解码的信息。code[len][value],前一个代表长度,后一个储存信息。

value将2进制信息转换成10进制来储存

注意结尾要把换行符读取掉

通过代码

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define maxnum 500005
#define inf 1000000007
using namespace std;
const double PI = acos(-1.0);
int change(string s){
int sum=0;
for(int i=0;i<s.length();i++){
sum=sum*2+(s[i]-'0');
}
return sum;
}
int main()
{
bool flag = true;
// code[len][value];len为长度;value为值;value有2^len - 1个
char code[100][1000] = {""};
string head;
while (getline(cin, head))
{
int t = 0;
//编码
for (int i = 1; t < head.length(); i++)
{
int len = pow(2, i) - 1;
for (int j = 0; j < len; j++)
{
code[i][j] = head[t];
t++;
}
}

while (1)//解码
{
//读取长度
string slen = "";
char c;
for (int i = 0; i < 3; i++)
{

scanf("%c", &c);
if(c=='\n'){
scanf("%c",&c);
}
slen += c;
}
int l = change(slen);
if(l==0) break;
//读取对应码值
while (1)
{
string s;
char cc;
for (int i = 0; i < l; i++) //读取l个字符
{
scanf("%c", &cc);
if(cc=='\n'){
scanf("%c",&cc);
}
s += cc;
}
int num = change(s);
if (num == pow(2, l) - 1)
{ // 111跳出当前循环
break;
}
cout << code[l][num];
}
}
cout << endl;
getchar();
}
return 0;
}

11.产生冠军

题目标签

拓扑排序/Set

题目描述

给定一组数据,代表xx输给了xx。

请求判定这局有没有冠军

题目解析

解法一:

仅当有人一人没输过时才能产生冠军,所以只需用两个set,分别记录所有人和输的人,比较他们的size的差是否为1即可。

解法二:

拓扑排序解法,给所有人记录入度和出度,仅当有一人的入度为0时,即可产生冠军

通过代码

解法二:

#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
while(cin >> n && n != 0)
{
map<string, int> mp;  // 分别为名字,入度
string a, b;
for(int i = 0; i < n; ++i)
{
cin >> a >> b;
mp[b] = 1;
if(mp[a] != 1)
mp[a] = 0;
}
int cnt = 0; // 入度为0的节点个数
for(map<string, int>::iterator it = mp.begin(); it != mp.end(); ++it)
if(it->second == 0)
cnt++;

if(cnt == 1)
cout << "Yes" << endl;
else
cout << "No" << endl;
}

return 0;
}