7-1 列出连通集
分数 20
全屏浏览题目
切换布局
作者 陈越
单位 浙江大学
给定一个有N个顶点和E条边的无向图,请用DFS和BFS分别列出其所有的连通集。假设顶点从0到N−1编号。进行搜索时,假设我们总是从编号最小的顶点出发,按编号递增的顺序访问邻接点。
输入格式:
输入第1行给出2个整数N(0<N≤10)和E,分别是图的顶点数和边数。随后E行,每行给出一条边的两个端点。每行中的数字之间用1空格分隔。
输出格式:
按照"{ v1 v2 ... vk }"的格式,每行输出一个连通集。先输出DFS的结果,再输出BFS的结果。
输入样例:
8 6
0 7
0 1
2 0
4 1
2 4
3 5
输出样例:
{ 0 1 4 2 7 }
{ 3 5 }
{ 6 }
{ 0 1 2 7 4 }
{ 3 5 }
{ 6 }
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
#define MNum 11
int visited[MNum];
int N,E;
int mp[MNum][MNum];
queue<int> q;
void DFS(int v)
{
printf("%d ", v); //输出路径
visited[v] = 1;
for (int w = 0; w < N; w++)
if (mp[v][w] && !visited[w]) //递归调用
DFS(w);
}
void BFS(int v)
{
printf("%d ", v); //输出路径
visited[v] = 1;
q.push(v);
while(!q.empty())
{
int f = q.front();
q.pop();
for (int i = 0; i < N; i++)
if (mp[f][i] && !visited[i]) //递归调用
{
visited[i] = 1;
printf("%d ", i);
q.push(i);
}
}
}
int main()
{
int v1,v2;
cin>>N>>E;
memset(mp,0,sizeof(mp));
memset(visited,0,sizeof(visited));
for(int i = 0;i < E;i++)
{
cin>>v1>>v2;
mp[v1][v2] = 1;
mp[v2][v1] = 1;
}
for(int i = 0;i < N;i++)
{
if(!visited[i])
{
cout<<"{ ";
DFS(i);
cout<<"}"<<endl;
}
}
memset(visited,0,sizeof(visited));
for(int i = 0;i < N;i++)
{
if(!visited[i])
{
cout<<"{ ";
BFS(i);
cout<<"}"<<endl;
}
}
return 0;
}
7-2 最小生成树的唯一性
分数 20
全屏浏览题目
切换布局
作者 陈越
单位 浙江大学
给定一个带权无向图,如果是连通图,则至少存在一棵最小生成树,有时最小生成树并不唯一。本题就要求你计算最小生成树的总权重,并且判断其是否唯一。
输入格式:
首先第一行给出两个整数:无向图中顶点数 N(≤500)和边数 M。随后 M 行,每行给出一条边的两个端点和权重,格式为“顶点1 顶点2 权重”,其中顶点从 1 到N 编号,权重为正整数。题目保证最小生成树的总权重不会超过 230。
输出格式:
如果存在最小生成树,首先在第一行输出其总权重,第二行输出“Yes”,如果此树唯一,否则输出“No”。如果树不存在,则首先在第一行输出“No MST”,第二行输出图的连通集个数。
输入样例 1:
5 7
1 2 6
5 1 1
2 3 4
3 4 3
4 1 7
2 4 2
4 5 5
输出样例 1:
11
Yes
输入样例 2:
4 5
1 2 1
2 3 1
3 4 2
4 1 2
3 1 3
输出样例 2:
4
No
#include<bits/stdc++.h>
using namespace std;
int f[501];
typedef struct node{
int u,v,w;
}Edges;
void init(){
for(int i=0;i<501;i++) f[i]=i;
}
int find(int x){
if(f[x]==x) return x;
else return f[x]=find(f[x]);
}
int fun(int a,int b){
int x=find(a),y=find(b);
if(x!=y){
f[x]=y;return 1;
}
return 0;
}
bool cmp(Edges a,Edges b){
return a.w<b.w;
}
int main(){
vector<Edges> Edge;
int n,m,sum=0,cnt=0,flag=0;
cin>>n>>m;
for(int i=0;i<m;i++){
int u,v,w;
cin>>u>>v>>w;
Edge.push_back({u,v,w});
}
init();
sort(Edge.begin(),Edge.end(),cmp);
for(int i=0;i<m;i++){
int u=Edge[i].u,v=Edge[i].v;
u=find(u);v=find(v);
if(u!=v){
int j=i+1;
if(Edge[j].w==Edge[i].w){
int uu=find(Edge[j].u),vv=find(Edge[j].v);
if((uu==u&&vv==v)||(uu==v&&vv==u))
flag=1;
}
fun(u,v);
cnt++;
sum+=Edge[i].w;
}
if(cnt==n-1) break;
}
if(cnt==n-1){
cout<<sum<<endl;
if(flag) cout<<"No";
else cout<<"Yes";
}else{
cout<<"No MST"<<endl;
int a=0;
for(int i=0;i<n;i++){
if(f[i]==i) a++;
}
cout<<a;
}
}
7-3 哲哲打游戏
分数 20
全屏浏览题目
切换布局
作者 DAI, Longao
单位 杭州百腾教育科技有限公司
哲哲是一位硬核游戏玩家。最近一款名叫《达诺达诺》的新游戏刚刚上市,哲哲自然要快速攻略游戏,守护硬核游戏玩家的一切!
为简化模型,我们不妨假设游戏有 N 个剧情点,通过游戏里不同的操作或选择可以从某个剧情点去往另外一个剧情点。此外,游戏还设置了一些存档,在某个剧情点可以将玩家的游戏进度保存在一个档位上,读取存档后可以回到剧情点,重新进行操作或者选择,到达不同的剧情点。
为了追踪硬核游戏玩家哲哲的攻略进度,你打算写一个程序来完成这个工作。假设你已经知道了游戏的全部剧情点和流程,以及哲哲的游戏操作,请你输出哲哲的游戏进度。
输入格式:
输入第一行是两个正整数 N 和 M (1≤N,M≤105),表示总共有 N 个剧情点,哲哲有 M 个游戏操作。
接下来的 N 行,每行对应一个剧情点的发展设定。第 i 行的第一个数字是 Ki,表示剧情点 i 通过一些操作或选择能去往下面 Ki 个剧情点;接下来有 Ki 个数字,第 k 个数字表示做第 k 个操作或选择可以去往的剧情点编号。
最后有 M 行,每行第一个数字是 0、1 或 2,分别表示:
- 0 表示哲哲做出了某个操作或选择,后面紧接着一个数字 j,表示哲哲在当前剧情点做出了第 j 个选择。我们保证哲哲的选择永远是合法的。
- 1 表示哲哲进行了一次存档,后面紧接着是一个数字 j,表示存档放在了第 j 个档位上。
- 2 表示哲哲进行了一次读取存档的操作,后面紧接着是一个数字 j,表示读取了放在第 j 个位置的存档。
约定:所有操作或选择以及剧情点编号都从 1 号开始。存档的档位不超过 100 个,编号也从 1 开始。游戏默认从 1 号剧情点开始。总的选项数(即 ∑Ki)不超过 106。
输出格式:
对于每个 1(即存档)操作,在一行中输出存档的剧情点编号。
最后一行输出哲哲最后到达的剧情点编号。
输入样例:
10 11
3 2 3 4
1 6
3 4 7 5
1 3
1 9
2 3 5
3 1 8 5
1 9
2 8 10
0
1 1
0 3
0 1
1 2
0 2
0 2
2 2
0 3
0 1
1 1
0 2
输出样例:
1
3
9
10
#include <bits/stdc++.h>
using namespace std;
struct node {
int pos;
vector<int> Path;
};
int n, m, now = 1;
vector<int> temp, Edge[100005];
vector<node> Save(105);
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
int k;
scanf("%d", &k);
for (int j = 0; j < k; j++) {
int t2;
scanf("%d", &t2);
Edge[i].push_back(t2);
}
}
temp.push_back(1);
for (int i = 1; i <= m; i++) {
int c, J;
scanf("%d%d", &c, &J);
if (c == 0) {
temp.push_back(Edge[now][J-1]);
now = Edge[now][J - 1];
} else if (c == 1) {
Save[J].pos = now;
Save[J].Path = temp;
printf("%d\n", now);
} else {
now = Save[J].pos;
temp = Save[J].Path;
}
}
printf("%d", temp.back());
return 0;
}
7-4 大众情人
分数 20
全屏浏览题目
切换布局
作者 陈越
单位 浙江大学
人与人之间总有一点距离感。我们假定两个人之间的亲密程度跟他们之间的距离感成反比,并且距离感是单向的。例如小蓝对小红患了单相思,从小蓝的眼中看去,他和小红之间的距离为 1,只差一层窗户纸;但在小红的眼里,她和小蓝之间的距离为 108000,差了十万八千里…… 另外,我们进一步假定,距离感在认识的人之间是可传递的。例如小绿觉得自己跟小蓝之间的距离为 2,则即使小绿并不直接认识小红,我们也默认小绿早晚会认识小红,并且因为跟小蓝很亲近的关系,小绿会觉得自己跟小红之间的距离为 1+2=3。当然这带来一个问题,如果小绿本来也认识小红,或者他通过其他人也能认识小红,但通过不同渠道推导出来的距离感不一样,该怎么算呢?我们在这里做个简单定义,就将小绿对小红的距离感定义为所有推导出来的距离感的最小值。
一个人的异性缘不是由最喜欢他/她的那个异性决定的,而是由对他/她最无感的那个异性决定的。我们记一个人 i 在一个异性 j 眼中的距离感为 Dij;将 i 的“异性缘”定义为 1/maxj∈S(i){Dij},其中 S(i) 是相对于 i 的所有异性的集合。那么“大众情人”就是异性缘最好(值最大)的那个人。
本题就请你从给定的一批人与人之间的距离感中分别找出两个性别中的“大众情人”。
输入格式:
输入在第一行中给出一个正整数 N(≤500),为总人数。于是我们默认所有人从 1 到 N 编号。
随后 N 行,第 i 行描述了编号为 i 的人与其他人的关系,格式为:
性别 K 朋友1:距离1 朋友2:距离2 …… 朋友K:距离K
其中 性别
是这个人的性别,F
表示女性,M
表示男性;K
(<N 的非负整数)为这个人直接认识的朋友数;随后给出的是这 K
个朋友的编号、以及这个人对该朋友的距离感。距离感是不超过 106 的正整数。
题目保证给出的关系中一定两种性别的人都有,不会出现重复给出的关系,并且每个人的朋友中都不包含自己。
输出格式:
第一行给出自身为女性的“大众情人”的编号,第二行给出自身为男性的“大众情人”的编号。如果存在并列,则按编号递增的顺序输出所有。数字间以一个空格分隔,行首尾不得有多余空格。
输入样例:
6
F 1 4:1
F 2 1:3 4:10
F 2 4:2 2:2
M 2 5:1 3:2
M 2 2:2 6:2
M 2 3:1 2:5
输出样例:
2 3
4
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, double>PII; //first存编号,second存该编号的异性缘
int n;
int mp[510][510];
unordered_map<int, int>xb; //xb为1是男性,为2是女性
vector<PII>man,woman; //分别存男生和女生的异性缘
bool cmp(PII x, PII y)
{
if(x.second == y.second) return x.first < y.first; //如果异性缘一样,按编号递增排序
return x.second > y.second; //按异性缘降序排序
}
int main()
{
memset(mp, 0x3f, sizeof mp); //设初始距离为无穷
cin >> n;
for(int i = 1; i <= n; i ++ )
{
int k;
char c;
cin >> c >> k;
for(int j = 1; j <= k; j ++ )
{
int x,y;
scanf("%d:%d", &x, &y);
mp[i][x] = y; //有向图
}
if(c == 'M') xb[i] = 1;
else xb[i] = 2;
}
for(int i = 1; i <= n; i ++ ) mp[i][i] = 0;
for(int k = 1; k <= n; k ++ )
{
for(int i = 1; i <= n; i ++ )
{
for(int j = 1; j <= n; j ++ )
{
mp[i][j] = min(mp[i][j], mp[i][k] + mp[k][j]);
}
}
}
//以上为Floyd最短路的模板
for(int i = 1; i <= n; i ++ ) //i = 1开始遍历,遍历每个异性和i的距离
{
int maxx = -1;
for(int j = 1; j <= n; j ++ )
{
if(i != j && xb[i] != xb[j]) //如果是异性就取距离最大值
{
maxx = max(maxx, mp[j][i]);
}
}
if(xb[i] == 1) man.push_back({i, 1.0 / maxx});
else woman.push_back({i, 1.0 / maxx});
}
sort(man.begin(), man.end(), cmp);
sort(woman.begin(), woman.end(), cmp);
//按题目要求输出即可
cout << woman[0].first;
for(int i = 1; i < woman.size(); i ++ )
{
if(woman[i].second == woman[i - 1].second) cout << " " << woman[i].first;
else break;
}
cout << '\n';
cout << man[0].first;
for(int i = 1; i < man.size(); i ++ )
{
if(man[i].second == man[i - 1].second) cout << " " << man[i].first;
else break;
}
return 0;
}
7-5 计算图
分数 20
全屏浏览题目
切换布局
作者 陈翔
单位 浙江大学
“计算图”(computational graph)是现代深度学习系统的基础执行引擎,提供了一种表示任意数学表达式的方法,例如用有向无环图表示的神经网络。 图中的节点表示基本操作或输入变量,边表示节点之间的中间值的依赖性。 例如,下图就是一个函数
f(x1,x2)=lnx1+x1x2−sinx2
的计算图。
现在给定一个计算图,请你根据所有输入变量计算函数值及其偏导数(即梯度)。 例如,给定输入x1=2,x2=5,上述计算图获得函数值 f(2,5)=ln(2)+2×5−sin(5)=11.652;并且根据微分链式法则,上图得到的梯度 ∇f=[∂f/∂x1,∂f/∂x2]=[1/x1+x2,x1−cosx2]=[5.500,1.716]。
知道你已经把微积分忘了,所以这里只要求你处理几个简单的算子:加法、减法、乘法、指数(ex,即编程语言中的 exp(x) 函数)、对数(lnx,即编程语言中的 log(x) 函数)和正弦函数(sinx,即编程语言中的 sin(x) 函数)。
友情提醒:
- 常数的导数是 0;x 的导数是 1;ex 的导数还是 ex;lnx 的导数是 1/x;sinx 的导数是 cosx。
- 回顾一下什么是偏导数:在数学中,一个多变量的函数的偏导数,就是它关于其中一个变量的导数而保持其他变量恒定。在上面的例子中,当我们对 x1 求偏导数 ∂f/∂x1 时,就将 x2 当成常数,所以得到 lnx1 的导数是 1/x1,x1x2 的导数是 x2,sinx2 的导数是 0。
- 回顾一下链式法则:复合函数的导数是构成复合这有限个函数在相应点的导数的乘积,即若有 u=f(y),y=g(x),则 du/dx=du/dy⋅dy/dx。例如对 sin(lnx) 求导,就得到 cos(lnx)⋅(1/x)。
如果你注意观察,可以发现在计算图中,计算函数值是一个从左向右进行的计算,而计算偏导数则正好相反。
输入格式:
输入在第一行给出正整数 N(≤5×104),为计算图中的顶点数。
以下 N 行,第 i 行给出第 i 个顶点的信息,其中 i=0,1,⋯,N−1。第一个值是顶点的类型编号,分别为:
- 0 代表输入变量
- 1 代表加法,对应 x1+x2
- 2 代表减法,对应 x1−x2
- 3 代表乘法,对应 x1×x2
- 4 代表指数,对应 ex
- 5 代表对数,对应 lnx
- 6 代表正弦函数,对应 sinx
对于输入变量,后面会跟它的双精度浮点数值;对于单目算子,后面会跟它对应的单个变量的顶点编号(编号从 0 开始);对于双目算子,后面会跟它对应两个变量的顶点编号。
题目保证只有一个输出顶点(即没有出边的顶点,例如上图最右边的 -
),且计算过程不会超过双精度浮点数的计算精度范围。
输出格式:
首先在第一行输出给定计算图的函数值。在第二行顺序输出函数对于每个变量的偏导数的值,其间以一个空格分隔,行首尾不得有多余空格。偏导数的输出顺序与输入变量的出现顺序相同。输出小数点后 3 位。
输入样例:
7
0 2.0
0 5.0
5 0
3 0 1
6 1
1 2 3
2 5 4
输出样例:
11.652
5.500 1.716
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
const int N=5e4+10;
int n,f[N],dg[N],s,nxt[N][2],vis[N],x;
db a[N],f1[N],f2[N];
vector<int> vec;
vector<db> ans;
void dfs(int u) {
if(vis[u])return;
vis[u]=1;
if(f[u]==0)f1[u]=a[u],f2[u]=u==x?1:0;
else if(f[u]==1) {
int v1=nxt[u][0],v2=nxt[u][1];
dfs(v1),dfs(v2);
f1[u]=f1[v1]+f1[v2],f2[u]=f2[v1]+f2[v2];
} else if(f[u]==2) {
int v1=nxt[u][0],v2=nxt[u][1];
dfs(v1),dfs(v2);
f1[u]=f1[v1]-f1[v2],f2[u]=f2[v1]-f2[v2];
} else if(f[u]==3) {
int v1=nxt[u][0],v2=nxt[u][1];
dfs(v1),dfs(v2);
f1[u]=f1[v1]*f1[v2],f2[u]=f2[v1]*f1[v2]+f1[v1]*f2[v2];
} else if(f[u]==4) {
int v=nxt[u][0];
dfs(v),f1[u]=exp(f1[v]),f2[u]=exp(f1[v])*f2[v];
} else if(f[u]==5) {
int v=nxt[u][0];
dfs(v),f1[u]=log(f1[v]),f2[u]=f2[v]/f1[v];
} else if(f[u]==6) {
int v=nxt[u][0];
dfs(v),f1[u]=sin(f1[v]),f2[u]=cos(f1[v])*f2[v];
}
}
int main() {
scanf("%d",&n);
for(int i=0; i<n; ++i) {
scanf("%d",&f[i]);
if(f[i]==0) {
scanf("%lf",&a[i]);
vec.push_back(i);
} else if(f[i]>=1&&f[i]<=3) {
int u,v;
scanf("%d%d",&u,&v);
nxt[i][0]=u,nxt[i][1]=v,dg[u]++,dg[v]++;
} else if(f[i]>=4&&f[i]<=6) {
int u;
scanf("%d",&u);
nxt[i][0]=u,dg[u]++;
}
}
for(int i=0; i<n; ++i)if(!dg[i])s=i;
for(int i:vec)x=i,memset(vis,0,sizeof vis),dfs(s),ans.push_back(f2[s]);
printf("%.3f\n",f1[s]);
for(int i=0; i<ans.size(); ++i)printf("%.3f%c",ans[i]," \n"[i==ans.size()-1]);
return 0;
}