DFS
L2-010 排座位(DFS/并查集)
布置宴席最微妙的事情,就是给前来参宴的各位宾客安排座位。无论如何,总不能把两个死对头排到同一张宴会桌旁!这个艰巨任务现在就交给你,对任何一对客人,请编写程序告诉主人他们是否能被安排同席。
输入格式:
输入第一行给出3个正整数:N
(≤100),即前来参宴的宾客总人数,则这些人从1到N
编号;M
为已知两两宾客之间的关系数;K
为查询的条数。随后M
行,每行给出一对宾客之间的关系,格式为:宾客1 宾客2 关系
,其中关系
为1表示是朋友,-1表示是死对头。注意两个人不可能既是朋友又是敌人。最后K
行,每行给出一对需要查询的宾客编号。
这里假设朋友的朋友也是朋友。但敌人的敌人并不一定就是朋友,朋友的敌人也不一定是敌人。只有单纯直接的敌对关系才是绝对不能同席的。
输出格式:
对每个查询输出一行结果:如果两位宾客之间是朋友,且没有敌对关系,则输出No problem
;如果他们之间并不是朋友,但也不敌对,则输出OK
;如果他们之间有敌对,然而也有共同的朋友,则输出OK but...
;如果他们之间只有敌对关系,则输出No way
。
输入样例:
7 8 4
5 6 1
2 7 -1
1 3 1
3 4 1
6 7 -1
1 2 1
1 4 1
2 3 -1
3 4
5 7
2 3
7 2
输出样例:
No problem
OK
OK but...
No way
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=110;
int g[N][N];
bool st[N];
int n,m,k;
int flag=0;
//找共同朋友
void dfs(int u)
{
st[u]=1;
for(int i=1;i<=n;i++)
{
if(g[u][i]==1&&!st[i])
{
dfs(i);
}
}
}
void dfs1(int u)
{
if(!st[u]) st[u]=1;
else
{
flag=1;
return ;
}
for(int i=1;i<=n;i++)
{
if(g[u][i]==1&&!st[i])
{
dfs(i);
}
}
}
int main()
{
cin>>n>>m>>k;
while(m--)
{
int a,b,c;cin>>a>>b>>c;
g[a][b]=g[b][a]=c;
}
while(k--)
{
flag=0;
memset(st,0,sizeof st);
int a,b;cin>>a>>b;
if(g[a][b]==1)
{
cout<<"No problem"<<endl;
}
else if(g[a][b]==0)
{
cout<<"OK"<<endl;
}
else if(g[a][b]==-1)
{
dfs(a);
dfs1(b);
if(!flag)cout<<"No way"<<endl;
else cout<<"OK but..."<<endl;
}
}
}
L2-030 冰岛人
非常秒 巧用map存储信息
2018年世界杯,冰岛队因1:1平了强大的阿根廷队而一战成名。好事者发现冰岛人的名字后面似乎都有个“松”(son),于是有网友科普如下:
冰岛人沿用的是维京人古老的父系姓制,孩子的姓等于父亲的名加后缀,如果是儿子就加 sson,女儿则加 sdottir。因为冰岛人口较少,为避免近亲繁衍,本地人交往前先用个 App 查一下两人祖宗若干代有无联系。本题就请你实现这个 App 的功能。
输入格式:
输入首先在第一行给出一个正整数 N(1<N≤105),为当地人口数。随后 N 行,每行给出一个人名,格式为:名 姓(带性别后缀)
,两个字符串均由不超过 20 个小写的英文字母组成。维京人后裔是可以通过姓的后缀判断其性别的,其他人则是在姓的后面加 m
表示男性、f
表示女性。题目保证给出的每个维京家族的起源人都是男性。
随后一行给出正整数 M,为查询数量。随后 M 行,每行给出一对人名,格式为:名1 姓1 名2 姓2
。注意:这里的姓
是不带后缀的。四个字符串均由不超过 20 个小写的英文字母组成。
题目保证不存在两个人是同名的。
输出格式:
对每一个查询,根据结果在一行内显示以下信息:
- 若两人为异性,且五代以内无公共祖先,则输出
Yes
; - 若两人为异性,但五代以内(不包括第五代)有公共祖先,则输出
No
; - 若两人为同性,则输出
Whatever
; - 若有一人不在名单内,则输出
NA
。
所谓“五代以内无公共祖先”是指两人的公共祖先(如果存在的话)必须比任何一方的曾祖父辈分高。
输入样例:
15
chris smithm
adam smithm
bob adamsson
jack chrissson
bill chrissson
mike jacksson
steve billsson
tim mikesson
april mikesdottir
eric stevesson
tracy timsdottir
james ericsson
patrick jacksson
robin patricksson
will robinsson
6
tracy tim james eric
will robin tracy tim
april mike steve bill
bob adam eric steve
tracy tim tracy tim
x man april mikes
输出样例:
Yes
No
No
Whatever
Whatever
NA
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
using namespace std;
const int N=1e5+10;
map<string,int>sex;//确定这个人的性别 0表是女 1表示男
map<string,string>fa;//相当于前驱数组 存放父亲的名
int n,m;
bool check(string a,string b)
{
map<string,int>num;
for(int i=1;a!="";i++)
{
num[a]=i;
a=fa[a];
}
for(int i=1;b!="";i++)
{
if(num[b])
{
if(num[b]>=5&&i>=5) return true;
else return false;
}
b=fa[b];
}
return true;
}
int main()
{
cin>>n;
while(n--)
{
string xing,ming;cin>>ming>>xing;
if(xing.back()=='f') sex[ming]=0;
else if(xing.back()=='m') sex[ming]=1;
else if(xing.substr(xing.size()-4)=="sson")
{
sex[ming]=1;
fa[ming]=xing.substr(0,xing.size()-4);
}
else
{
sex[ming]=0;
fa[ming]=xing.substr(0,xing.size()-7);
}
}
cin>>m;
while(m--)
{
string xing1,ming1,xing2,ming2;
cin>>ming1>>xing1>>ming2>>xing2;
if(sex.count(ming1)==0||sex.count(ming2)==0) cout<<"NA"<<endl;
else if(sex[ming1]==sex[ming2]) cout<<"Whatever"<<endl;
else if(check(ming1,ming2)) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}
L2-026 小字辈 (记忆化搜索/BFS)
本题给定一个庞大家族的家谱,要请你给出最小一辈的名单。
输入格式:
输入在第一行给出家族人口总数 N(不超过 100 000 的正整数) —— 简单起见,我们把家族成员从 1 到 N 编号。随后第二行给出 N 个编号,其中第 i 个编号对应第 i 位成员的父/母。家谱中辈分最高的老祖宗对应的父/母编号为 -1。一行中的数字间以空格分隔。
输出格式:
首先输出最小的辈分(老祖宗的辈分为 1,以下逐级递增)。然后在第二行按递增顺序输出辈分最小的成员的编号。编号间以一个空格分隔,行首尾不得有多余空格。
输入样例:
9
2 6 5 5 -1 5 6 4 7
输出样例:
4
1 9
思路:实际就是求得树的深度 以及最深层的叶子结点 数据太大 不能直接爆搜 借助记忆化搜索
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int pre[N];
int depth[N];
int n;
//记忆化搜索
int dfs(int u)
{
//if(depth[u]) return depth[u];
if(!depth[u])
{
if(pre[u]==-1)
{
depth[u]=1;
// return depth[u];//出口
}
else{
depth[u]=dfs(pre[u])+1;
}
}
return depth[u];
}
int main()
{
cin>>n;
int max1=0;
for(int i=1;i<=n;i++)
{
int num;cin>>num;
pre[i]=num;
}
for(int i=1;i<=n;i++)
{
int num=dfs(i);
max1=max(max1,num);
}
cout<<max1<<endl;
int flag=0;
for(int i=1;i<=n;i++)
{
if(depth[i]==max1)
{
if(!flag)
{
cout<<i;
flag=1;
}
else cout<<" "<<i;
}
}
return 0;
}
功夫传人(DFS)
一门武功能否传承久远并被发扬光大,是要看缘分的。一般来说,师傅传授给徒弟的武功总要打个折扣,于是越往后传,弟子们的功夫就越弱…… 直到某一支的某一代突然出现一个天分特别高的弟子(或者是吃到了灵丹、挖到了特别的秘笈),会将功夫的威力一下子放大N倍 —— 我们称这种弟子为“得道者”。
这里我们来考察某一位祖师爷门下的徒子徒孙家谱:假设家谱中的每个人只有1位师傅(除了祖师爷没有师傅);每位师傅可以带很多徒弟;并且假设辈分严格有序,即祖师爷这门武功的每个第i
代传人只能在第i-1
代传人中拜1个师傅。我们假设已知祖师爷的功力值为Z
,每向下传承一代,就会减弱r%
,除非某一代弟子得道。现给出师门谱系关系,要求你算出所有得道者的功力总值。
输入格式:
输入在第一行给出3个正整数,分别是:N(≤10^5)——整个师门的总人数(于是每个人从0到N−1编号,祖师爷的编号为0);Z——祖师爷的功力值(不一定是整数,但起码是正数);r ——每传一代功夫所打的折扣百分比值(不超过100的正数)。接下来有N行,第i行(i=0,⋯,N−1)描述编号为i的人所传的徒弟,格式为:
Ki ID[1] ID[2] ⋯ ID[Ki]
其中Ki是徒弟的个数,后面跟的是各位徒弟的编号,数字间以空格间隔。Ki为零表示这是一位得道者,这时后面跟的一个数字表示其武功被放大的倍数。
输出格式:
在一行中输出所有得道者的功力总值,只保留其整数部分。题目保证输入和正确的输出都不超过10^10。
输入样例:
10 18.0 1.00
3 2 3 5
1 9
1 4
1 7
0 7
2 6 1
1 8
0 9
0 4
0 3
输出样例:
404
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
bool st[N];
double dist[N];
map<int,int>mp;
vector<int>v[N];
int n;
double z,r;
double sum=0;
void dfs(int u)
{
for(int i=0;i<v[u].size();i++)
{
int j=v[u][i];
dist[j]=0.01*dist[u]*(100-r);
if(st[j]) dist[j]*=mp[j];
dfs(j);
}
}
signed main()
{
cin>>n>>z>>r;
for(int i=0;i<n;i++)
{
int k;cin>>k;
for(int j=0;j<k;j++)
{
int x;cin>>x;
v[i].push_back(x);
}
if(k==0)
{
int x;cin>>x;
st[i]=1;
mp[i]=x;
//若祖师爷就是得到者 z扩大x倍
if(i==0)
{
z*=x;
printf("%lld",(int)z);
return 0;
}
}
}
dist[0]=z;
dfs(0);
for(int i=0;i<n;i++)
{
if(st[i]) sum+=dist[i];
}
printf("%lld",(int)sum);
return 0;
}
BFS
L2-048 寻宝图(BFS)
给定一幅地图,其中有水域,有陆地。被水域完全环绕的陆地是岛屿。有些岛屿上埋藏有宝藏,这些有宝藏的点也被标记出来了。本题就请你统计一下,给定的地图上一共有多少岛屿,其中有多少是有宝藏的岛屿。
输入格式:
输入第一行给出 2 个正整数 N 和 M(1<N×M≤105),是地图的尺寸,表示地图由 N 行 M 列格子构成。随后 N 行,每行给出 M 位个位数,其中 0
表示水域,1
表示陆地,2
-9
表示宝藏。
注意:两个格子共享一条边时,才是“相邻”的。宝藏都埋在陆地上。默认地图外围全是水域。
输出格式:
在一行中输出 2 个整数,分别是岛屿的总数量和有宝藏的岛屿的数量。
输入样例:
10 11
01000000151
11000000111
00110000811
00110100010
00000000000
00000111000
00114111000
00110010000
00019000010
00120000001
输出样例:
7 2
Vector二维数组
#include<iostream>
#include<queue>
#include<algorithm>
#include<vector>
using namespace std;
typedef pair<int,int>PII;
const int N=1e6+10;
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
vector<char>v[N];
int n,m;
int sum,num;
bool bfs(int x,int y)
{
queue<PII>q;
q.push({x,y});
int flag=0;
if(v[x][y]!='1') flag=1;
while(q.size())
{
PII t=q.front();
q.pop();
int x1=t.first,y1=t.second;
for(int i=0;i<4;i++)
{
int xx=x1+dx[i],yy=y1+dy[i];
if(xx<0||xx>=n||yy<0||yy>=m) continue;
if(v[xx][yy]=='0') continue;
if(v[xx][yy]!='1') flag=1;
q.push({xx,yy});
v[xx][yy]='0';
}
}
if(flag) return true;
return false;
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
char a;cin>>a;
v[i].push_back(a);
}
}
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
if(v[i][j]!='0')
{
sum++;
bool flag1=bfs(i,j);
if(flag1) num++;
}
}
}
cout<<sum<<" "<<num<<endl;
return 0;
}
直接一维数组 i*m+y 弄成下标
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=1e5+100;
typedef pair<int,int>PII;
bool st[N];
char a[N];
int m,n;
int flag=0;
int get(int x,int y)
{
return x*m+y;
}
int dx[4]={0,0,-1,1},dy[4]={1,-1,0,0};
void bfs(int x1,int y1)
{
queue<PII>q;
q.push({x1,y1});
while(q.size())
{
PII t=q.front();
q.pop();
for(int i=0;i<4;i++)
{
int x=t.first+dx[i],y=t.second+dy[i];
if(x>=0&&x<n&&y>=0&&y<m&&a[get(x,y)]>='1'&&!st[get(x,y)])
{
st[get(x,y)]=true;
if(a[get(x,y)]!='1') flag=1;
a[get(x,y)]='0';
q.push({x,y});
}
}
}
}
int main()
{
cin>>n>>m;
getchar();
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
char b;
b=getchar();
a[get(i,j)]=b;
}
getchar();
}
int num=0,num1=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
flag=0;
if(a[get(i,j)]!='0'&&!st[get(i,j)])
{
num++;
bfs(i,j);
if(flag==1)num1++;
}
}
}
cout<<num<<" "<<num1<<endl;
return 0;
}
L2-026 小字辈 (记忆化搜索/BFS)
本题给定一个庞大家族的家谱,要请你给出最小一辈的名单。
输入格式:
输入在第一行给出家族人口总数 N(不超过 100 000 的正整数) —— 简单起见,我们把家族成员从 1 到 N 编号。随后第二行给出 N 个编号,其中第 i 个编号对应第 i 位成员的父/母。家谱中辈分最高的老祖宗对应的父/母编号为 -1。一行中的数字间以空格分隔。
输出格式:
首先输出最小的辈分(老祖宗的辈分为 1,以下逐级递增)。然后在第二行按递增顺序输出辈分最小的成员的编号。编号间以一个空格分隔,行首尾不得有多余空格。
输入样例:
9
2 6 5 5 -1 5 6 4 7
输出样例:
4
1 9
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int N=1e5+10;
vector<int>v[N];
int depth[N];
int pre[N];
int num,max1=1;
void bfs()
{
queue<int>q;
depth[num]=1;
q.push(num);
while(q.size())
{
int t=q.front();
q.pop();
for(int i=0;i<v[t].size();i++)
{
int j=v[t][i];
depth[j]=depth[t]+1;
max1=max(max1,depth[j]);
q.push(j);
}
}
}
int main()
{
int n;cin>>n;
for(int i=1;i<=n;i++)
{
cin>>pre[i];
if(pre[i]==-1) num=i;
}
for(int i=1;i<=n;i++)
{
v[pre[i]].push_back(i);
}
bfs();
cout<<max1<<endl;
int flag=0;
for(int i=1;i<=n;i++)
{
if(depth[i]==max1)
{
if(!flag)
{
flag=1;
cout<<i;
}
else cout<<" "<<i;
}
}
return 0;
}
并查集
L2-010 排座位(DFS/并查集)
布置宴席最微妙的事情,就是给前来参宴的各位宾客安排座位。无论如何,总不能把两个死对头排到同一张宴会桌旁!这个艰巨任务现在就交给你,对任何一对客人,请编写程序告诉主人他们是否能被安排同席。
输入格式:
输入第一行给出3个正整数:N
(≤100),即前来参宴的宾客总人数,则这些人从1到N
编号;M
为已知两两宾客之间的关系数;K
为查询的条数。随后M
行,每行给出一对宾客之间的关系,格式为:宾客1 宾客2 关系
,其中关系
为1表示是朋友,-1表示是死对头。注意两个人不可能既是朋友又是敌人。最后K
行,每行给出一对需要查询的宾客编号。
这里假设朋友的朋友也是朋友。但敌人的敌人并不一定就是朋友,朋友的敌人也不一定是敌人。只有单纯直接的敌对关系才是绝对不能同席的。
输出格式:
对每个查询输出一行结果:如果两位宾客之间是朋友,且没有敌对关系,则输出No problem
;如果他们之间并不是朋友,但也不敌对,则输出OK
;如果他们之间有敌对,然而也有共同的朋友,则输出OK but...
;如果他们之间只有敌对关系,则输出No way
。
输入样例:
7 8 4
5 6 1
2 7 -1
1 3 1
3 4 1
6 7 -1
1 2 1
1 4 1
2 3 -1
3 4
5 7
2 3
7 2
输出样例:
No problem
OK
OK but...
No way
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=110;
int g[N][N];
int p[N];
bool st[N];
int n,m,k;
//初始化
void unit()
{
for(int i=1;i<=n;i++) p[i]=i;
}
//找祖宗结点
int find(int x)
{
if(p[x]!=x) return p[x]=find(p[x]);
return p[x];
}
//合并
void add(int a,int b)
{
if(find(a)!=find(b)) p[find(a)]=find(b);
}
int main()
{
cin>>n>>m>>k;
unit();
while(m--)
{
int a,b,c;cin>>a>>b>>c;
g[a][b]=g[b][a]=c;
if(c==1) add(a,b);
}
while(k--)
{
int a,b;cin>>a>>b;
if(g[a][b]==1)cout<<"No problem"<<endl;
else if(g[a][b]==0) cout<<"OK"<<endl;
else if(g[a][b]==-1)
{
if(find(a)==find(b)) cout<<"OK but..."<<endl;
else cout<<"No way"<<endl;
}
}
return 0;
}
L2-007 家庭房产
给定每个人的家庭成员和其自己名下的房产,请你统计出每个家庭的人口数、人均房产面积及房产套数。
输入格式:
输入第一行给出一个正整数N(≤1000),随后N行,每行按下列格式给出一个人的房产:
编号 父 母 k 孩子1 ... 孩子k 房产套数 总面积
其中编号
是每个人独有的一个4位数的编号;父
和母
分别是该编号对应的这个人的父母的编号(如果已经过世,则显示-1
);k
(0≤k
≤5)是该人的子女的个数;孩子i
是其子女的编号。
输出格式:
首先在第一行输出家庭个数(所有有亲属关系的人都属于同一个家庭)。随后按下列格式输出每个家庭的信息:
家庭成员的最小编号 家庭人口数 人均房产套数 人均房产面积
其中人均值要求保留小数点后3位。家庭信息首先按人均面积降序输出,若有并列,则按成员编号的升序输出。
输入样例:
10
6666 5551 5552 1 7777 1 100
1234 5678 9012 1 0002 2 300
8888 -1 -1 0 1 1000
2468 0001 0004 1 2222 1 500
7777 6666 -1 0 2 300
3721 -1 -1 1 2333 2 150
9012 -1 -1 3 1236 1235 1234 1 100
1235 5678 9012 0 1 50
2222 1236 2468 2 6661 6662 1 300
2333 -1 3721 3 6661 6662 6663 1 100
输出样例:
3
8888 1 1.000 1000.000
0001 15 0.600 100.000
5551 4 0.750 100.000
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e4+10;
int p[N];
bool st[N];
struct people
{
int id;
int father=-1,mother=-1;
int childnum;
int child[10];
int housenum,area;
}stu[N];
struct family
{
int id;
int peoplenum;
double housenum,area;
bool flag=false;
}stu1[N];
void unit()
{
for(int i=0;i<N;i++) p[i]=i;
}
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
void add(int a,int b)
{
int x=find(a),y=find(b);
if(x<y) p[y]=x;
else if(x>y) p[x]=y;
}
double cmp(family x,family y)
{
if(x.area==y.area) return x.id<y.id;
return x.area>y.area;
}
int main()
{
int n;cin>>n;
unit();
for(int i=0;i<n;i++)
{
cin>>stu[i].id>>stu[i].father>>stu[i].mother;
st[stu[i].id]=1;
if(stu[i].father!=-1)
{
st[stu[i].father]=1;
add(stu[i].id,stu[i].father);
}
if(stu[i].mother!=-1)
{
st[stu[i].mother]=1;
add(stu[i].id,stu[i].mother);
}
cin>>stu[i].childnum;
for(int j=0;j<stu[i].childnum;j++)
{
cin>>stu[i].child[j];
st[stu[i].child[j]]=1;
add(stu[i].id,stu[i].child[j]);
}
cin>>stu[i].housenum>>stu[i].area;
}
for(int i=0;i<n;i++)
{
int id=find(stu[i].id);
stu1[id].id=id;
stu1[id].housenum+=stu[i].housenum;
stu1[id].area+=stu[i].area;
stu1[id].flag=true;
}
int sum=0;
for(int i=0;i<N;i++)
{
if(st[i])
{
int id=find(i);
stu1[id].peoplenum+=1;
}
if(stu1[i].flag) sum++;
}
for(int i=0;i<N;i++)
{
if(stu1[i].flag)
{
stu1[i].housenum=1.0*stu1[i].housenum/stu1[i].peoplenum;
stu1[i].area=1.0*stu1[i].area/stu1[i].peoplenum;
}
}
sort(stu1,stu1+N,cmp);
cout<<sum<<endl;
for(int i=0;i<sum;i++)
{
printf("%04d %d %.3lf %.3lf\n",stu1[i].id,stu1[i].peoplenum,stu1[i].housenum,stu1[i].area);
}
return 0;
}
Floyd
Dijstra
模拟
特立独行的幸福
对一个十进制数的各位数字做一次平方和,称作一次迭代。如果一个十进制数能通过若干次迭代得到 1,就称该数为幸福数。1 是一个幸福数。此外,例如 19 经过 1 次迭代得到 82,2 次迭代后得到 68,3 次迭代后得到 100,最后得到 1。则 19 就是幸福数。显然,在一个幸福数迭代到 1 的过程中经过的数字都是幸福数,它们的幸福是依附于初始数字的。例如 82、68、100 的幸福是依附于 19 的。而一个特立独行的幸福数,是在一个有限的区间内不依附于任何其它数字的;其独立性就是依附于它的的幸福数的个数。如果这个数还是个素数,则其独立性加倍。例如 19 在区间[1, 100] 内就是一个特立独行的幸福数,其独立性为 2×4=8。
另一方面,如果一个大于1的数字经过数次迭代后进入了死循环,那这个数就不幸福。例如 29 迭代得到 85、89、145、42、20、4、16、37、58、89、…… 可见 89 到 58 形成了死循环,所以 29 就不幸福。
本题就要求你编写程序,列出给定区间内的所有特立独行的幸福数和它的独立性。
输入格式:
输入在第一行给出闭区间的两个端点:1<A<B≤10^4。
输出格式:
按递增顺序列出给定闭区间 [A,B] 内的所有特立独行的幸福数和它的独立性。每对数字占一行,数字间以 1 个空格分隔。
如果区间内没有幸福数,则在一行中输出 SAD
。
输入样例 1:
10 40
输出样例 1:
19 8
23 6
28 3
31 4
32 3
注意:样例中,10、13 也都是幸福数,但它们分别依附于其他数字(如 23、31 等等),所以不输出。其它数字虽然其实也依附于其它幸福数,但因为那些数字不在给定区间 [10, 40] 内,所以它们在给定区间内是特立独行的幸福数。
输入样例 2:
110 120
输出样例 2:
SAD
题目的大致意思就是求出指定范围内的 独立 的幸福数以及依附其的幸福数的数字个数。题中的幸福数指的是经过多次迭代,每次迭代将各位数字求平方之和,得到数字1的数字。而 独立 的幸福数指的是不依附于其他任何幸福数的幸福数,可以看成是经过多次迭代能够得到1的真正的源头数字。
我们可以通过枚举指定范围内的所有数字,判断每个数字是否是 独立 的幸福数。
(1)我们需要定义一个标记数组 st 记录当前数字是否被访问过,如果某个数字 num 被访问过,我们就不再将其作为源头数字进行迭代,因为在之前的某次迭代过程中 num出现过了,那么其一定不会是 独立 的幸福数。
(2)其次就是迭代过程中出现死循环,只需要定义一个set 来判重即可,如果当前的 num 已经存在于当前的集合中,就说明有死循环,无法迭代到达1,那么说明此时的源头数字连幸福数都不是。
(3)还有就是答案数组中所存储的数字,在每次迭代过程中,都需要判断一下当前的 num 是否存在于答案数组中,如果出现了,说明之前放入答案数组的数字是依附于当前源头数字的,所以此时需要将其删除。
只有满足上述的条件,枚举的当前的源头数字才是一个不依附于其他任何幸福数的幸福数。
对于判断是否是质数,可以用试除法判断质数
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
using namespace std;
const int N=11000;
bool st[N];
set<int>s;
set<int>s1;
//记录特立独行的幸福数的独立性
map<int,int>mp;
int a,b;
//各位平方和
int get(int x)
{
int sum=0;
while(x)
{
int y=x%10;
sum+=y*y;
x/=10;
}
return sum;
}
//判断是不是素数
bool imprime(int x)
{
if(x==1) return false;
for(int i=2;i<=x/i;i++)
{
if(x%i==0) return false;
}
return true;
}
int main()
{
cin>>a>>b;
for(int i=a;i<=b;i++)
{
s1.clear();
int num=i;
int num1=0;//记录迭代次数
int flag=0;
if(!st[num])
{
st[num]=1;
while(1)
{
int x=get(num);
st[x]=1;
num=x;
num1++;
if(s.count(x))
{
s.erase(x);
}
if(x==1) break;
if(s1.count(x))
{
flag=1;
break;
}
s1.insert(x);
}
if(!flag)
{
s.insert(i);
if(imprime(i))
{
mp[i]=num1*2;
}
else mp[i]=num1;
}
}
}
if(s.size()==0)
{
cout<<"SAD"<<endl;
}
else
{
for(auto it:s)
{
cout<<it<<" "<<mp[it]<<endl;
}
}
return 0;
}
链表
L2-002 链表去重
给定一个带整数键值的链表 L,你需要把其中绝对值重复的键值结点删掉。即对每个键值 K,只有第一个绝对值等于 K 的结点被保留。同时,所有被删除的结点须被保存在另一个链表上。例如给定 L 为 21→-15→-15→-7→15,你需要输出去重后的链表 21→-15→-7,还有被删除的链表 -15→15。
输入格式:
输入在第一行给出 L 的第一个结点的地址和一个正整数 N(≤105,为结点总数)。一个结点的地址是非负的 5 位整数,空地址 NULL 用 −1 来表示。
随后 N 行,每行按以下格式描述一个结点:
地址 键值 下一个结点
其中地址
是该结点的地址,键值
是绝对值不超过104的整数,下一个结点
是下个结点的地址。
输出格式:
首先输出去重后的链表,然后输出被删除的链表。每个结点占一行,按输入的格式输出。
输入样例:
00100 5
99999 -7 87654
23854 -15 00000
87654 15 -1
00000 -15 99999
00100 21 23854
输出样例:
00100 21 23854
23854 -15 99999
99999 -7 -1
00000 -15 87654
87654 15 -1
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
const int N=1e5+10;
map<int,int>mp;
//这个题的关键是将不重复存在一个数组中 将重复的存在一个数组中
//用下标存 还要注意会把前导0 过滤掉 记得%05d
//可以用map<string,int> map<string,string> mp<int,int>去存储关系
//开两个 vector<string>v1,v2
struct node
{
int num;
int ne;
}stu[N];
int stu1[N],stu2[N];
int main()
{
int node1;cin>>node1;
int n;cin>>n;
for(int i=1;i<=n;i++)
{
int idx;cin>>idx;
cin>>stu[idx].num>>stu[idx].ne;
}
int len1=0,len2=0;
for(int i=node1;i!=-1;i=stu[i].ne)
{
int x=abs(stu[i].num);
if(!mp[x])
{
stu1[len1++]=i;
mp[x]=1;
}
else
{
stu2[len2++]=i;
}
}
for(int i=0;i<len1;i++)
{
int idx=stu1[i];
if(i!=len1-1)
printf("%05d %d %05d\n",idx,stu[idx].num,stu1[i+1]);
else printf("%05d %d -1\n",idx,stu[idx].num);
}
for(int i=0;i<len2;i++)
{
int idx=stu2[i];
if(i!=len2-1)
printf("%05d %d %05d\n",idx,stu[idx].num,stu2[i+1]);
else printf("%05d %d -1\n",idx,stu[idx].num);
}
return 0;
}
L2-022 重排链表
给定一个单链表 L1→L2→⋯→Ln−1→Ln,请编写程序将链表重新排列为 Ln→L1→Ln−1→L2→⋯。例如:给定L为1→2→3→4→5→6,则输出应该为6→1→5→2→4→3。
输入格式:
每个输入包含1个测试用例。每个测试用例第1行给出第1个结点的地址和结点总个数,即正整数N (≤105)。结点的地址是5位非负整数,NULL地址用−1表示。
接下来有N行,每行格式为:
Address Data Next
其中Address
是结点地址;Data
是该结点保存的数据,为不超过105的正整数;Next
是下一结点的地址。题目保证给出的链表上至少有两个结点。
输出格式:
对每个测试用例,顺序输出重排后的结果链表,其上每个结点占一行,格式与输入相同。
输入样例:
00100 6
00000 4 99999
00100 1 12309
68237 6 -1
33218 3 00000
99999 5 68237
12309 2 33218
输出样例:
68237 6 00100
00100 1 99999
99999 5 12309
12309 2 00000
00000 4 33218
33218 3 -1
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
const int N=1e5+10;
struct node
{
int num;
int ne;
}stu[N];
int stu1[N],stu2[N];
vector<int>v;
int main()
{
int node1;cin>>node1;
int n;cin>>n;
for(int i=0;i<n;i++)
{
int idx;cin>>idx;
cin>>stu[idx].num>>stu[idx].ne;
}
int len1=0;
stu1[len1++]=node1;
for(int i=node1;i!=-1;i=stu[i].ne)
{
int j=stu[i].ne;
stu1[len1++]=j;
}
int len2=0;
for(int i=len1-1;i>=0;i--) stu2[len2++]=stu1[i];
// cout<<endl;
int l=0,r=len1-2;
while(l<=r)
{
v.push_back(stu1[r]);
v.push_back(stu1[l]);
v.push_back(stu1[l]);
l++;
r--;
v.push_back(stu1[r]);
}
v.pop_back();
if(n%2!=0)
{
v.pop_back();
v.pop_back();
}
v.push_back(-1);
for(int i=0;i<v.size();i+=2)
{
if(v[i+1]==-1)
{
printf("%05d %d -1\n",v[i],stu[v[i]].num);
}
else printf("%05d %d %05d\n",v[i],stu[v[i]].num,v[i+1]);
}
return 0;
}
队列
列车调度(模拟+二分查找/set)
火车站的列车调度铁轨的结构如下图所示。
两端分别是一条入口(Entrance)轨道和一条出口(Exit)轨道,它们之间有N
条平行的轨道。每趟列车从入口可以选择任意一条轨道进入,最后从出口离开。在图中有9趟列车,在入口处按照{8,4,2,5,3,9,1,6,7}的顺序排队等待进入。如果要求它们必须按序号递减的顺序从出口离开,则至少需要多少条平行铁轨用于调度?
输入格式:
输入第一行给出一个整数N
(2 ≤ N
≤105),下一行给出从1到N
的整数序号的一个重排列。数字间以空格分隔。
输出格式:
在一行中输出可以将输入的列车按序号递减的顺序调离所需要的最少的铁轨条数。
输入样例:
9
8 4 2 5 3 9 1 6 7
输出样例:
4
思路:采用的是单调队列的思想 开一堆数组 找比当前这个数大的数组 盖过去(必须是数组的第一个比它大的 )
对样例进行分析:
1 2 4 8-->1
3 5-->3
6 9-->6
7-->7
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e5+10;
int a[N];
int q[N];
int main()
{
memset(q,0x3f,sizeof q);
int n;cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int l=0,r=0;
q[r++]=a[1];
for(int i=2;i<=n;i++)
{
int x=0,y=r;
//最大值最小化 找到第一个大于a[i]的 即q[x]-a[i]的差最小
while(x<y)
{
int mid=(x+y)>>1;
if(q[mid]>a[i]) y=mid;
else x=mid+1;
}
if(q[x]==0x3f3f3f3f) q[r++]=a[i];
else q[x]=a[i];
}
cout<<r<<endl;
return 0;
}
set中自带upper_bound()--二分查找
set中的函数
c.begin() 返回一个迭代器,它指向容器c的第一个元素
c.end() 返回一个迭代器,它指向容器c的最后一个元素的下一个位置
c.rbegin() 返回一个逆序迭代器,它指向容器c的最后一个元素
c.rend() 返回一个逆序迭代器,它指向容器c的第一个元素前面的位置
upper_bound():
upper_bound是找到大于t的最小地址,如果没有就指向末尾
lower_bound是找到大于等于t的最小地址
erase():
erase() 迭代器的参数必须是一个指向容器中元素的、有效的、可解引用的迭代器,因此需要确保它不是容器的结束迭代器。
#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
set<int>s;
int main()
{
int n;cin>>n;
for(int i=0;i<n;i++)
{
int num;cin>>num;
if(s.upper_bound(num)!=s.end()) s.erase(s.upper_bound(num));
s.insert(num);
}
cout<<s.size()<<endl;
return 0;
}
栈
单调栈
单调递增栈:在保持栈内元素单调递增的前提下(如果栈顶元素大于要入栈的元素,将将其弹出),将新元素入栈。
单调递减栈:在保持栈内元素单调递减的前提下(如果栈顶元素小于要入栈的元素,则将其弹出),将新元素入栈。
单调栈的时间复杂度是O(n)
题目:
给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。
输入格式
第一行包含整数 N,表示数列长度。
第二行包含 N 个整数,表示整数数列。
输出格式
共一行,包含 N 个整数,其中第 i 个数表示第 i 个数的左边第一个比它小的数,如果不存在则输出 −1。
数据范围
1≤N≤10^5
1≤数列中元素≤10^9
输入样例:
5
3 4 2 7 5
输出样例:
-1 3 -1 2 2
#include<iostream>
#include<stack>
using namespace std;
stack<int>sta;
const int N=1e5+10;
int a[N],b[N];
int main()
{
int n;cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<n;i++)
{
while(sta.size()&&sta.top()>=a[i]) sta.pop();
if(sta.size()) b[i]=sta.top();
else b[i]=-1;
sta.push(a[i]);
}
for(int i=0;i<n;i++) cout<<b[i]<<" ";
return 0;
}
二叉树
L2-006 树的遍历
给定一棵二叉树的后序遍历和中序遍历,请你输出其层序遍历的序列。这里假设键值都是互不相等的正整数。
输入格式:
输入第一行给出一个正整数N(≤30),是二叉树中结点的个数。第二行给出其后序遍历序列。第三行给出其中序遍历序列。数字间以空格分隔。
输出格式:
在一行中输出该树的层序遍历的序列。数字间以1个空格分隔,行首尾不得有多余空格。
输入样例:
7
2 3 1 5 7 6 4
1 2 3 4 5 6 7
输出样例:
4 1 6 3 5 7 2
C语言--指针写法
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef int ElementType;
typedef struct BiTNode
{
ElementType data;
struct BiTNode *lchild;
struct BiTNode *rchild;
}BiTNode,*BiTree;
int post[110],in[110];
int n;
BiTree create(int root,int start,int end)
{
if(start>end) return NULL;
BiTree T;
T=(BiTree)malloc(sizeof (struct BiTNode));
T->data=post[root];
int i;
for(i=start;in[i]!=post[root];i++);
T->lchild=create(root-(end-i)-1,start,i-1);
T->rchild=create(root-1,i+1,end);
return T;
}
int flag=0;
void Lerveorder(BiTree T)
{
BiTree q[110];
int h=0,r=0;
if(!T) return ;
q[r++]=T;
while(h<r)
{
if(q[h]->lchild) q[r++]=q[h]->lchild;
if(q[h]->rchild) q[r++]=q[h]->rchild;
if(flag==0)
{
printf("%d",q[h++]->data);
flag=1;
}
else printf(" %d",q[h++]->data);
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d ",&post[i]);
for(int i=1;i<=n;i++) scanf("%d ",&in[i]);
BiTree T=create(n,1,n);
Lerveorder(T);
return 0;
}
用vector写
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
vector<int>post,in,level(10000,-1);//开的大一点
int n;
//root为根在后序序列中的位置 start end为该树在中序序列中的范围
void create(int root,int start,int end,int index)
{
if(start>end) return ;//递归结束
level[index]=post[root];
int i;
for(i=start;in[i]!=post[root];i++);
//遍历左子树 root-(end-i)-1即 减去右子树结点个数就是左子树的根的位置
create(root-(end-i)-1,start,i-1,index*2+1);
//遍历右子树 root-1即右子树的根节点位置
create(root-1,i+1,end,index*2+2);
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
int num;cin>>num;
post.push_back(num);
}
for(int i=0;i<n;i++)
{
int num;cin>>num;
in.push_back(num);
}
create(n-1,0,n-1,0);
for(int i=0;i<level.size();i++)
{
if(level[i]!=-1)
{
if(i!=0) cout<<" ";
cout<<level[i];
}
}
return 0;
}
玩转二叉树
给定一棵二叉树的中序遍历和前序遍历,请你先将树做个镜面反转,再输出反转后的层序遍历的序列。所谓镜面反转,是指将所有非叶结点的左右孩子对换。这里假设键值都是互不相等的正整数。
输入格式:
输入第一行给出一个正整数N
(≤30),是二叉树中结点的个数。第二行给出其中序遍历序列。第三行给出其前序遍历序列。数字间以空格分隔。
输出格式:
在一行中输出该树反转后的层序遍历的序列。数字间以1个空格分隔,行首尾不得有多余空格。
输入样例:
7
1 2 3 4 5 6 7
4 1 3 2 6 5 7
输出样例:
4 6 1 7 5 3 2
指针的方法 -- 构造正常构造 层次遍历的时候先右后左即可
#include<iostream>
#include<algorithm>
using namespace std;
typedef int ElementType;
typedef struct BiTNode
{
ElementType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
int pre[33],in[33];
BiTree create(int root,int start,int end)
{
if(start>end) return NULL;
BiTree T=(BiTree)malloc(sizeof (struct BiTNode));
T->data=pre[root];
int i;
for(i=start;pre[root]!=in[i];i++);
T->lchild=create(root+1,start,i-1);
T->rchild=create(root+(i-start)+1,i+1,end);
return T;
}
int flag=0;
void print(BiTree T)
{
if(!T) return ;
BiTree q[110];
int h=0,r=0;
q[r++]=T;
while(h<r)
{
if(q[h]->rchild) q[r++]=q[h]->rchild;
if(q[h]->lchild) q[r++]=q[h]->lchild;
if(!flag)
{
printf("%d",q[h++]->data);
flag=1;
}
else{
printf(" %d",q[h++]->data);
}
}
}
int main()
{
int n;cin>>n;
for(int i=1;i<=n;i++)cin>>in[i];
for(int i=1;i<=n;i++) cin>>pre[i];
BiTree T=create(1,1,n);
print(T);
return 0;
用vector
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
vector<int>pre,in,level(100000,-1);
int n;
void create(int root,int start,int end,int index)
{
if(start>end) return ;
level[index]=pre[root];
int i;
for(i=start;in[i]!=pre[root];i++);
//把左子树元素创造在右子树的位置上
create(root+1,start,i-1,index*2+2);
//把右子树元素创造在左子树的位置上
create(root+1+(i-start),i+1,end,index*2+1);
//如果是单只 无论在左还是在右都不会影响层次遍历的结果 所以对换
//也无所谓
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
int num;cin>>num;
in.push_back(num);
}
for(int i=0;i<n;i++)
{
int num;cin>>num;
pre.push_back(num);
}
create(0,0,n-1,0);
for(int i=0;i<level.size();i++)
{
if(level[i]!=-1)
{
if(i!=0) cout<<" ";
cout<<level[i];
}
}
return 0;
}
完全二叉树的层序遍历
一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是完美二叉树。对于深度为 D 的,有 N 个结点的二叉树,若其结点对应于相同深度完美二叉树的层序遍历的前 N 个结点,这样的树就是完全二叉树。
给定一棵完全二叉树的后序遍历,请你给出这棵树的层序遍历结果。
输入格式:
输入在第一行中给出正整数 N(≤30),即树中结点个数。第二行给出后序遍历序列,为 N 个不超过 100 的正整数。同一行中所有数字都以空格分隔。
输出格式:
在一行中输出该树的层序遍历序列。所有数字都以 1 个空格分隔,行首尾不得有多余空格。
输入样例:
8
91 71 2 34 10 15 55 18
输出样例:
18 34 55 71 2 10 15 91
/* 心中有树 */
完美二叉树 从上到下 从左到右都填满
完全二叉树 最后一层可以不完全填充,其叶子节点都靠左对齐
由题意,我们知道N的大小就可以知道这棵树的形状,
那么我们可以“心中有树” 地进行后序遍历这棵树以读入数据,
然后直接输出这个数组即可。
#include<iostream>
#include<algorithm>
using namespace std;
int level[33];
int n;
/*心中有树*/
void dfs(int x)
{
if(x>=n) return ;
dfs(x*2+1);//左
dfs(x*2+2);//右
scanf("%d",&level[x]);
}
int main()
{
cin>>n;
dfs(0);
for(int i=0;i<n;i++)
{
if(i!=0) cout<<" ";
cout<<level[i];
}
return 0;
}
这是二叉搜索树吗?
一棵二叉搜索树可被递归地定义为具有下列性质的二叉树:对于任一结点,
- 其左子树中所有结点的键值小于该结点的键值;
- 其右子树中所有结点的键值大于等于该结点的键值;
- 其左右子树都是二叉搜索树。
所谓二叉搜索树的“镜像”,即将所有结点的左右子树对换位置后所得到的树。
给定一个整数键值序列,现请你编写程序,判断这是否是对一棵二叉搜索树或其镜像进行前序遍历的结果。
输入格式:
输入的第一行给出正整数 N(≤1000)。随后一行给出 N 个整数键值,其间以空格分隔。
输出格式:
如果输入序列是对一棵二叉搜索树或其镜像进行前序遍历的结果,则首先在一行中输出 YES
,然后在下一行输出该树后序遍历的结果。数字间有 1 个空格,一行的首尾不得有多余空格。若答案是否,则输出 NO
。
输入样例 1:
7
8 6 5 7 10 8 11
输出样例 1:
YES
5 7 6 8 11 10 8
输入样例 2:
7
8 10 11 8 6 7 5
输出样例 2:
YES
11 8 10 7 5 6 8
输入样例 3:
7
8 6 8 5 10 9 11
输出样例 3:
NO
#include<iostream>
#include<algorithm>
using namespace std;
typedef int ElementType;
const int N=1100;
typedef struct BiTNode
{
ElementType data;
struct BiTNode *left,*right;
}BiTNode,*BiTree;
BiTree T;
int pre[N],pre1[N],pre2[N];
int n,num1=0,num2=0;
//先构造出来二叉搜索树
BiTree insert(BiTree T,int x)
{
if(!T)
{
T=(BiTree)malloc(sizeof (struct BiTNode));
T->data=x;
T->left=NULL;
T->right=NULL;
}
else if(x<T->data)
{
T->left=insert(T->left,x);
}
else{
T->right=insert(T->right,x);
}
return T;
}
void flag1(BiTree T)
{
if(!T) return ;//递归结束
pre1[num1++]=T->data;
flag1(T->left);
flag1(T->right);
}
void flag2(BiTree T)
{
if(!T) return ;//递归结束
pre2[num2++]=T->data;
flag2(T->right);
flag2(T->left);
}
int x1=0,x2=0;//做标记 没有多余空格
void print1(BiTree T)
{
if(!T) return ;//递归结束
print1(T->left);
print1(T->right);
if(!x1)
{
printf("%d",T->data);
x1=1;
}
else printf(" %d",T->data);
}
void print2(BiTree T)
{
if(!T) return ;//递归结束
print2(T->right);
print2(T->left);
if(!x2)
{
printf("%d",T->data);
x2=1;
}
else printf(" %d",T->data);
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
cin>>pre[i];
T=insert(T,pre[i]);
}
//检验 看它是二叉搜索树的前序遍历还是其镜像的前序遍历
//还是两者都是 以上三种情况YES 两者都不是NO
//二叉搜索树的前序遍历
flag1(T);
//其镜像的前序遍历
flag2(T);
//检验看符合哪一个
int t1=0,t2=0;
for(int i=0;i<num1;i++) if(pre[i]!=pre1[i]) t1=1;
for(int i=0;i<num2;i++) if(pre[i]!=pre2[i]) t2=1;
if(!t1)
{
cout<<"YES"<<endl;
print1(T);
}
else if(!t2)
{
cout<<"YES"<<endl;
print2(T);
}
else cout<<"NO"<<endl;
return 0;
}
二叉搜索树
由二叉搜索树的概念可知,左子树结点值 < 根结点值 < 右子树结点值;所有当二叉搜索树进行中序遍历也就是左根右遍历时,可以得到升序的结果;如果是右根左遍历,就可以得到降序的结果。
分析:假设它是二叉搜索树,一开始isMirror为FALSE,根据二叉搜索树的性质将已知的前序转换为后序,转换过程中,如果发现最后输出的后序数组长度不为n,那就设isMirror为true,然后清空后序数组,重新再转换一次(根据镜面二叉搜索树的性质),如果依旧转换后数组大小不等于n,就输出no否则输出yes
怎么由前序遍历转换成后序遍历呢
我们根据前序遍历的特点,第一个点作为根节点,后面的所有点可以分为两部分,左子树和右子树,那么除去第一个根节点后
如果是二叉搜索树(左小右大)剩下的所有点中间一定可以找到一个分界线使左边的所有点比第一个点(即根节点)小,右边的大。如:前序遍历序列 4 1 3 2 6 5 7第一个为根然后两个下标向中间找分界使得左边小右边大,我们发现最终可以分成黄色和绿色两部分而下标指向 j i 一开始i从左往右走,j从右往左走,然后再将每段进行重复操作,递归,最后递归完成才返回根节点,保证相当于根节点最后被访问,这样就完成了后序遍历的转换,我们发现对于一个二叉搜索树来说,每次进行分段i,j下标都会是如图所示的情形,i-j=1,而如果不是一个二叉搜索树,下标ij一定会提前停下,并返回,最终导致后序遍历的点的个数会小于n,由此我么根据最终得到的后序遍历点的个数是否等于n来判断是否是二叉搜索树。
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
vector<int>pre,post;
bool isMirror=false;
void getpost(int root,int tail)
{
if(root>tail) return ;
int i=root+1,j=tail;//i,j为根节点的区间左右边界
if(!isMirror)
{
while(i<=tail&&pre[i]<pre[root]) i++;//从左向右查找第一个大于等于根节点的位置
while(j>root&&pre[j]>=pre[root]) j--;//从右向左查找第一个小于根节点的位置
}
else
{
//镜像 --相反
while(i<=tail&&pre[i]>=pre[root]) i++;
while(j>root&&pre[j]<pre[root]) j--;
}
if(i-j!=1) return ;
//心中有树
getpost(root+1,j);//递归判断左子树
getpost(i,tail);//递归判断右子树
post.push_back(pre[root]);
}
int main()
{
int n;cin>>n;
for(int i=0;i<n;i++)
{
int num;cin>>num;
pre.push_back(num);
}
getpost(0,n-1);
if(post.size()==n)
{
cout<<"YES"<<endl;
for(int i=0;i<post.size();i++)
{
if(i!=0)cout<<" ";
cout<<post[i];
}
}
else
{
isMirror=true;
post.clear();
getpost(0,n-1);
if(post.size()==n)
{
cout<<"YES"<<endl;
for(int i=0;i<post.size();i++)
{
if(i!=0)cout<<" ";
cout<<post[i];
}
}
else cout<<"NO"<<endl;
}
return 0;
}
排序
L2-027 名人堂与代金券
对于在中国大学MOOC(http://www.icourse163.org/ )学习“数据结构”课程的学生,想要获得一张合格证书,总评成绩必须达到 60 分及以上,并且有另加福利:总评分在 [G, 100] 区间内者,可以得到 50 元 PAT 代金券;在 [60, G) 区间内者,可以得到 20 元PAT代金券。全国考点通用,一年有效。同时任课老师还会把总评成绩前 K 名的学生列入课程“名人堂”。本题就请你编写程序,帮助老师列出名人堂的学生,并统计一共发出了面值多少元的 PAT 代金券。
输入格式:
输入在第一行给出 3 个整数,分别是 N(不超过 10 000 的正整数,为学生总数)、G(在 (60,100) 区间内的整数,为题面中描述的代金券等级分界线)、K(不超过 100 且不超过 N 的正整数,为进入名人堂的最低名次)。接下来 N 行,每行给出一位学生的账号(长度不超过15位、不带空格的字符串)和总评成绩(区间 [0, 100] 内的整数),其间以空格分隔。题目保证没有重复的账号。
输出格式:
首先在一行中输出发出的 PAT 代金券的总面值。然后按总评成绩非升序输出进入名人堂的学生的名次、账号和成绩,其间以 1 个空格分隔。需要注意的是:成绩相同的学生享有并列的排名,排名并列时,按账号的字母序升序输出。
输入样例:
10 80 5
cy@zju.edu.cn 78
cy@pat-edu.com 87
1001@qq.com 65
uh-oh@163.com 96
test@126.com 39
anyone@qq.com 87
zoe@mit.edu 80
jack@ucla.edu 88
bob@cmu.edu 80
ken@163.com 70
输出样例:
360
1 uh-oh@163.com 96
2 jack@ucla.edu 88
3 anyone@qq.com 87
3 cy@pat-edu.com 87
5 bob@cmu.edu 80
5 zoe@mit.edu 80
核心
sort(stu+1,stu+1+n,cmp);
stu[1].num1=1;
for(int i=2;i<=n;i++)
{
if(stu[i].num==stu[i-1].num) stu[i].num1=stu[i-1].num1;
else stu[i].num1=i;
}
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e4+10;
struct student
{
string s;
int num;
int num1;
}stu[N];
int cmp(student x,student y)
{
if(x.num==y.num) return x.s<y.s;
return x.num>y.num;
}
int n,g,k,sum=0;
int main()
{
cin>>n>>g>>k;
for(int i=1;i<=n;i++)
{
cin>>stu[i].s>>stu[i].num;
if(stu[i].num>=60&&stu[i].num<g) sum+=20;
if(stu[i].num>=g&&stu[i].num<=100) sum+=50;
}
cout<<sum<<endl;
sort(stu+1,stu+1+n,cmp);
stu[1].num1=1;
for(int i=2;i<=n;i++)
{
if(stu[i].num==stu[i-1].num) stu[i].num1=stu[i-1].num1;
else stu[i].num1=i;
}
for(int i=1;stu[i].num1<=k&&i<=n;i++)
{
cout<<stu[i].num1<<" "<<stu[i].s<<" "<<stu[i].num<<endl;
}
return 0;
}
L2-039 清点代码库
上图转自新浪微博:“阿里代码库有几亿行代码,但其中有很多功能重复的代码,比如单单快排就被重写了几百遍。请设计一个程序,能够将代码库中所有功能重复的代码找出。各位大佬有啥想法,我当时就懵了,然后就挂了。。。”
这里我们把问题简化一下:首先假设两个功能模块如果接受同样的输入,总是给出同样的输出,则它们就是功能重复的;其次我们把每个模块的输出都简化为一个整数(在 int 范围内)。于是我们可以设计一系列输入,检查所有功能模块的对应输出,从而查出功能重复的代码。你的任务就是设计并实现这个简化问题的解决方案。
输入格式:
输入在第一行中给出 2 个正整数,依次为 N(≤104)和 M(≤102),对应功能模块的个数和系列测试输入的个数。
随后 N 行,每行给出一个功能模块的 M 个对应输出,数字间以空格分隔。
输出格式:
首先在第一行输出不同功能的个数 K。随后 K 行,每行给出具有这个功能的模块的个数,以及这个功能的对应输出。数字间以 1 个空格分隔,行首尾不得有多余空格。输出首先按模块个数非递增顺序,如果有并列,则按输出序列的递增序给出。
注:所谓数列 { A1, ..., AM } 比 { B1, ..., BM } 大,是指存在 1≤i<M,使得 A1=B1,...,Ai=Bi 成立,且 Ai+1>Bi+1。
输入样例:
7 3
35 28 74
-1 -1 22
28 74 35
-1 -1 22
11 66 0
35 28 74
35 28 74
输出样例:
4
3 35 28 74
2 -1 -1 22
1 11 66 0
1 28 74 35
思路:本来想把数变成字符串 然后去记录个数 比较大小 但是有两个数据过不了
新的知识 map<vector<int>,int>mp
部分对
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
const int N=1e4+10;
struct s
{
string ss;
int num;
}stu[N];
map<string,int>mp;
bool cmp(s x,s y)
{
if(x.num==y.num) return x.ss<y.ss;
return x.num>y.num;
}
int n,m;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
string x;
for(int j=1;j<=m;j++)
{
string xx;cin>>xx;
x+=xx;
x+="+";
}
mp[x]++;
}
int cnt=0;
for(auto it:mp)
{
stu[cnt].ss=it.first;
stu[cnt].num=it.second;
cnt++;
}
sort(stu,stu+cnt,cmp);
cout<<mp.size()<<endl;
for(int i=0;i<cnt;i++)
{
cout<<stu[i].num<<" ";
string y=stu[i].ss;
for(int j=0;j<y.size()-1;j++)
{
if(y[j]=='+') cout<<" ";
else cout<<y[j];
}
cout<<endl;
}
return 0;
}
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
const int N=1e4+10;
struct s
{
int num;
vector<int>number;
}stu[N];
vector<int>v;
map<vector<int>,int>mp;
int n,m;
bool cmp(s x,s y)
{
if(x.num==y.num)
{
for(int i=0;i<m;i++)
{
if(x.number[i]==y.number[i]) continue;
return x.number[i]<y.number[i];
}
}
return x.num>y.num;
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
{
v.clear();
for(int j=0;j<m;j++)
{
int x;cin>>x;
v.push_back(x);
}
mp[v]++;
}
int cnt=0;
for(auto it:mp)
{
stu[cnt].num=it.second;
stu[cnt].number=it.first;
cnt++;
}
sort(stu,stu+cnt,cmp);
cout<<cnt<<endl;
for(int i=0;i<cnt;i++)
{
cout<<stu[i].num;
for(int j=0;j<m;j++)
{
cout<<" "<<stu[i].number[j];
}
cout<<endl;
}
return 0;
}
双指针
最长对称子串(双指针+模拟)
对给定的字符串,本题要求你输出最长对称子串的长度。例如,给定Is PAT&TAP symmetric?
,最长对称子串为s PAT&TAP s
,于是你应该输出11。
输入格式:
输入在一行中给出长度不超过1000的非空字符串。
输出格式:
在一行中输出最长对称子串的长度。
输入样例:
Is PAT&TAP symmetric?
输出样例:
11
数据小可以直接模拟,若长度1e5就不可直接暴力 这时用 哈希+二分
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int main()
{
string s;
getline(cin,s);
int max1=0;
int i,j,k;
for(i=0;i<s.size();i++)
{
for(j=s.size()-1;j>=i;j--)
{
int l,r;
if(s[i]==s[j])
{
l=i,r=j;
while(s[l]==s[r]) l++,r--;
if(l>=r) max1=max(max1,j-i+1);
}
}
}
cout<<max1<<endl;
return 0;
}