2017年第八届蓝桥杯B组决赛

一、36进制

题目描述

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

对于 16 进制,我们使用字母 A−F 来表示 10 及以上的数字。

如法炮制,一直用到字母 Z,就可以表示 36 进制。

36 进制中,A 表示 10,Z 表示 35,AA 表示370。

你能算出 MANY 表示的数字用 10 进制表示是多少吗?

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

思路:直接计算36进制的10进制值,没什么好说的。

#include <iostream>
#include <string>
using namespace std;
int main()
{
  string str_num = "MANY";
  int num = 0;
  for(int i = 0; i < str_num.size(); ++ i)
  {
    if(str_num[i] >= '0' && str_num[i] <= '9')
    {
      num = num * 36 + str_num[i] - '0';
    }
    else
    {
      num = num * 36 + str_num[i] - 'A' + 10;
    }
  }
  cout << num;
  return 0;
} 

二、瓷砖样式

题目描述

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

小明家的一面装饰墙原来是 3×10 的小方格。 现在手头有一批刚好能盖住 2 个小方格的长方形瓷砖。 瓷砖只有两种颜色:黄色和橙色。

小明想知道,对于这么简陋的原料,可以贴出多少种不同的花样来。 小明有个小小的强迫症:忍受不了任何 2×2 的小格子是同一种颜色。 (瓷砖不能切割,不能重叠,也不能只铺一部分。另外,只考虑组合图案,请忽略瓷砖的拼缝) 显然,对于 2×3 个小格子来说,口算都可以知道:一共 10 种贴法,如下图所示:

图片描述

但对于 3×10 的格子呢?肯定是个不小的数目,请你利用计算机的威力算出该数字。

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

思路:首先明确一点,3*10的格子上要用面积为2的矩形块填满,因此总共需要15块,我们可以分别枚举15块矩形块的放置方式(横向、竖向),以及矩形块的颜色。

#include <iostream>
#include <cstring>
#include <unordered_set>
using namespace std;

unordered_set<string> blockState;
int blocks[4][15];

bool checkSquare(int x, int y)
{
	int value = blocks[x][y];
	return value == blocks[x + 1][y] && value == blocks[x][y + 1] && value == blocks[x + 1][y + 1];
}

bool checkBlocks()
{
  for(int i = 1; i <= 2; ++ i)
  {
    for(int j = 1; j <= 9; ++ j)
    {
      //if((blocks[i][j] + blocks[i + 1][j] + blocks[i][j + 1] + blocks[i + 1][j + 1]) % 4 == 0)
      //if((blocks[i][j] == blocks[i + 1][j] && blocks[i][j + 1] == blocks[i + 1][j + 1]) &&  blocks[i][j] == blocks[i + 1][j + 1])
	  if(checkSquare(i,j))
	  {
        return false;
      }
    }
  }
  return true;
}

void DFS(int x, int y)
{
  if(x > 3)
  {
  	//cout << x << " " << y << endl;
    if(checkBlocks())
    {
      string str = "";
      for(int i = 1; i <= 3; ++ i)
      {
      	for(int j = 1; j <= 10; ++ j)
      	{
      	  str.append(1, '0' + blocks[i][j]);
      	}
      }
      blockState.insert(str); 
    }
    return;
  }
  int nx = y == 10 ? x + 1 : x;
  int ny = y == 10 ? 1 : y + 1;
  
  if(blocks[x][y] == -1)
  {
  	if(y < 10 && blocks[x][y] == -1 && blocks[x][y + 1] == -1)//可以向右放
    {
	  blocks[x][y] = blocks[x][y + 1] = 0;
	  DFS(nx,ny);
	  blocks[x][y] = blocks[x][y + 1] = 1;
	  DFS(nx,ny);
	  blocks[x][y] = blocks[x][y + 1] = -1;
	}
	if(x < 3 && blocks[x][y] == -1 && blocks[x + 1][y] == -1)//可以向右放
	{
	  blocks[x][y] = blocks[x + 1][y] = 0;
	  DFS(nx,ny);
	  blocks[x][y] = blocks[x + 1][y] = 1;
	  DFS(nx,ny);
	  blocks[x][y] = blocks[x + 1][y] = -1;
	}
  }
  else{
  	DFS(nx,ny);
  }
}

int main()
{
  memset(blocks, -1, sizeof(blocks));
  DFS(1, 1);
  cout << blockState.size();
  return 0;
}

[注]:1.题目中有要求"只考虑组合图案,请忽略瓷砖的拼缝",也就是说颜色排列相同的视为同一种方案,不考虑瓷砖之间的缝隙也就是不考虑到底每块瓷砖如何摆放。

2.在考虑每个2*2的格子不能有相同颜色时,可以用颜色对应的值累加后对4取模判断。

三、希尔伯特曲线

题目描述

本题为代码补全填空题,请将题目中给出的源代码补全,并复制到右侧代码框中,选择对应的编译语言(C/Java)后进行提交。若题目中给出的源代码语言不唯一,则只需选择其一进行补全提交即可。复制后需将源代码中填空部分的下划线删掉,填上你的答案。提交后若未能通过,除考虑填空部分出错外,还需注意是否因在复制后有改动非填空部分产生错误。

希尔伯特曲线是以下一系列分形曲线 H_nHn​ 的极限。我们可以把 H_n​ 看作一条覆盖 2^n×2^n 方格矩阵的曲线,曲线上一共有 2^n×2^n 个顶点(包括左下角起点和右下角终点),恰好覆盖每个方格一次。

图片描述

H_n(n > 1)可以通过如下方法构造:

  1. 将 H_n-1 顺时针旋转 90 度放在左下角;

  2. 将 H_n-1 逆时针旋转 90 度放在右下角;

  3. 将 2 个 H_n-1 分别放在左上角和右上角;

  4. 用 3 条单位线段把 4 部分连接起来。

对于 H_n 上每一个顶点 p ,我们定义 p 的坐标是它覆盖的小方格在矩阵中的坐标(左下角是(1, 1),右上角是(2^n, 2^n),从左到右是 X 轴正方向,从下到上是 Y 轴正方向),定义 p 的序号是它在曲线上从起点开始数第几个顶点(从 1 开始计数)。

以下程序对于给定的 n (n<=30) 和 p 点坐标 (x, y),输出 p 点的序号。

请仔细阅读分析源码,填写划线部分缺失的内容。

源代码

C

#include <stdio.h>

long long f(int n, int x, int y) {
    if (n == 0) return 1;
    int m = 1 << (n - 1);
    if (x <= m && y <= m) {
        return f(n - 1, y, x);
    }
    if (x > m && y <= m) {
        return 3LL * m * m + f(n - 1, ________________ , m * 2 - x + 1); //  填空
    }
    if (x <= m && y > m) {
        return 1LL * m * m + f(n - 1, x, y - m);
    }
    if (x > m && y > m) {
        return 2LL * m * m + f(n - 1, x - m, y - m);
    }
}

int main() {
    int n, x, y;
    scanf("%d %d %d", &n, &x, &y); 
    printf("%lld", f(n, x, y));

    return 0;
}

Java

import java.util.Scanner;
    
public class Main {
    public static long f(int n, int x, int y) {
    if (n == 0) return 1;
        int m = 1 << (n - 1);
        if (x <= m && y <= m) {
                return f(n - 1, y, x);
        }
        if (x > m && y <= m) {
                return 3L * m * m + f(n - 1, ________________ , m * 2 - x + 1); //填空
        }
        if (x <= m && y > m) {
                return 1L * m * m + f(n - 1, x, y - m);
        }
        if (x > m && y > m) {
                return 2L * m * m + f(n - 1, x - m, y - m);
        }
    return -1;
    }
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
          int n = in.nextInt();
    int x = in.nextInt();
    int y = in.nextInt();
        
        System.out.println(f(n, x, y));
    }
}

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 256M

思路:首先分析可知,每一个n阶图形,都是由4个n-1阶经过变化拼接后得到的,那么很显然的是,如下的代码段:

    int m = 1 << (n - 1);
    if (x <= m && y <= m) {
        return f(n - 1, y, x);
    }
    if (x > m && y <= m) {
        return 3LL * m * m + f(n - 1, ________________ , m * 2 - x + 1); //  填空
    }
    if (x <= m && y > m) {
        return 1LL * m * m + f(n - 1, x, y - m);
    }
    if (x > m && y > m) {
        return 2LL * m * m + f(n - 1, x - m, y - m);
    }

就是在判断我们要求的点处在哪一个子块中,然后不断递归求解。

举例来说,当我们发现处于第i块个n阶子图时,前面有完整的n-1阶子块,每一个n-1阶子块的路径长度为2^(n-1)*2^(n-1)。

这边解释一下路径长度:实际上,我们看图可以知道,单独一个1阶块是长度是3,但是它作为2阶块的子块时,长度是4(多了一条块与块之间的连接线),高阶同理。

现在我们明白:f(n,x,y)表明的是在n阶图形中,(x,y)图形起点的长度。

 从图中我们可以看出,在2阶图中,左上和右上两块和1阶图保持一直,左下逆时针旋转90°得到1阶图,右下顺时针旋转90°得到一阶图,3阶与2阶的关系也如此。

因此,当我们降阶时,还需要做相应的坐标转换。

我们需要求的空白处对应的是右下这种情况,也就是顺时针旋转的坐标变化。直接考虑转换公式是很难想的,我们可以找一些不变的基准或者是一些变换简单的基准,计算与基准的差异,借助基准的简单变换进行转移:

 当图形顺时针旋转90°后,可以发现,左基准变成了上基准,上基准变成了右基准,因此对于每一个点,变换前我们求与左、上基准的距离,变化后使用新基准变换回来即可。

对于右下图形中的点(x,y),相对于左基准的距离为x-(m+1),相对于上基准的距离为(m-y)。

使用基准进行变化可知:x'=m-(m-y)=y,y'=m-(x-(m+1))=2*m+1-x。

but,到这里还没结束。我们看一下变换前后的路径对应:

变换前的路径到变换后变成了右边所示,但是我们上面说过,函数f求取的是到当前阶图形起点的距离,而实际变换后对应路径变成了到终点的距离,但是,幸运的是,本处图形正好关于x=(m+1)/2这条线是对称的,一次可以通过对称变化将路径投影成为到起点的距离。因此,实际上应该有x''=(m+1)-x'=m+1-y。

#include <stdio.h>

long long f(int n, int x, int y) {
    if (n == 0) return 1;
    int m = 1 << (n - 1);
    if (x <= m && y <= m) {
        return f(n - 1, y, x);
    }
    if (x > m && y <= m) {
        return 3LL * m * m + f(n - 1, m + 1 - y, m * 2 - x + 1); //  填空
    }
    if (x <= m && y > m) {
        return 1LL * m * m + f(n - 1, x, y - m);
    }
    if (x > m && y > m) {
        return 2LL * m * m + f(n - 1, x - m, y - m);
    }
}

int main() {
    int n, x, y;
    scanf("%d %d %d", &n, &x, &y); 
    printf("%lld", f(n, x, y));

    return 0;
}

四、发现环

题目描述

小明的实验室有 N 台电脑,编号 1⋯N。原本这 N 台电脑之间有 N−1 条数据链接相连,恰好构成一个树形网络。在树形网络上,任意两台电脑之间有唯一的路径相连。

不过在最近一次维护网络时,管理员误操作使得某两台电脑之间增加了一条数据链接,于是网络中出现了环路。环路上的电脑由于两两之间不再是只有一条路径,使得这些电脑上的数据传输出现了 BUG。

为了恢复正常传输。小明需要找到所有在环路上的电脑,你能帮助他吗?

输入描述

输入范围:

第一行包含一个整数 N 。

以下 N 行每行两个整数 a,b,表示 a 和 b 之间有一条数据链接相连。

其中, 1≤N≤10^5,1≤a,b≤N。

输入保证合法。

输出描述

按从小到大的顺序输出在环路上的电脑的编号,中间由一个空格分隔。

输入输出样例

示例

输入

5
1 2
3 1
2 4
2 5
5 3

输出

1 2 3 5

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 256M

思路:对于环路,其特点为从点X出发,还可以再次到达点X。判环很显然可以用遍历整幅图的方式,分为DFS与BFS。但是要求环上的元素,我们需要更变一下我们的思路:

1.DFS+栈

我们用栈存下每一次DFS遇到的元素,如果遇到了已经在栈内的元素x,那么直接从栈弹元素直到x,期间所有的元素包括x都是在环上的。

#include <iostream>
#include <stack>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 2e5 + 7;

int Head[maxn], To[maxn], Next[maxn], cnt;
bool inStack[maxn];
vector<int> inCircle;

void add(int from, int to)
{
  To[++cnt] = to;
  Next[cnt] = Head[from];
  Head[from] = cnt;
}

//树上增加一条边形成的环只可能有一个
bool DFS(int p, int pre, stack<int>& st)
{
  int q;
  for(int e = Head[p]; e != 0; e = Next[e])
  {
    q = To[e];
    //cout << p << "-" << q << endl;
    if(q == pre) continue;
    if(inStack[q])
	{
	  while(!st.empty() && st.top() != q)
	  {
	    inCircle.push_back(st.top());
	    st.pop();
	  }
	  inCircle.push_back(q);
	  return true;
	}
    st.push(q);
    inStack[q] = true;
    if(DFS(q, p, st))//找到了环
    {
      return true;
    }
    st.pop();
    inStack[q] = false;
  }
  return false;
}

int main()
{
  int N, a, b;
  stack<int> st;
  cin >> N;
  for(int i = 1; i <= N; ++ i)
  {
    cin >> a >> b;
    add(a, b);
    add(b, a);
  }
  st.push(1);
  inStack[1] = true;
  DFS(1, -1, st);
  sort(inCircle.begin(), inCircle.end());
  for(auto p : inCircle)
  {
    cout << p << " ";
  }
  return 0;
}

2.BFS+拓扑排序

对于存在环的情况,我们可以知道拓扑排序后一定会存在遗漏点。现在需要解决的是如何在无向图上进行拓扑排序。

对于本题的情况,首先我们可以明确,本题是一棵树上多了一条边导致生成的环,因此生成的环一定是一个简单环路,即点与点之间首尾相连。

在无向图中,点无法区分入度和出度,或者说 入度=出度=度数。对于这样的简单环路,显而易见该环上的点的度数都是2,而度数为1的点。但需要注意的是,入度为2的点不一定是环中的点:

如上图,根节点的度数为2,但并不是环中的点。我们来讨论一下如何在无向图中使用拓扑排序算法:

1.找到所有度数为1的点,入队列。

2.从队头拿到元素x,将x的所有邻接点的度数减1,若度数减为1,则表明这不是环中的点,入队列备用;但必须知道,如果当前度数没有减为1,也并不代表它一定是环路中的点。

3.重复步骤2直到队列为空,此时遍历所有点的度数,如果度数为2,则必定是环中的点。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 7;
int degree[maxn];
bool visited[maxn];
vector<int> arc[maxn];

void BFS(queue<int>& points)
{
	int p, i, to;
	while(!points.empty())
	{
		p = points.front();
		points.pop();
		for(i = arc[p].size() - 1; i >= 0; -- i)
		{
			to = arc[p][i];
			if(visited[to])
			{
				continue;				
			}
			-- degree[to];
			if(degree[to] == 1)
			{
				visited[to] = true;
				points.push(to);
			}
		}
	}
}

int main() 
{
	int N, a, b, i;
	queue<int> points;
	cin >> N;
	for(i = 1; i <= N; ++ i)
	{
		cin >> a >> b;
		degree[a] += 1;
		degree[b] += 1;
		arc[a].push_back(b);
		arc[b].push_back(a);
	}
	for(i = 1; i <= N; ++ i)
	{
		if(degree[i] == 1)
		{
			degree[i] = 0;
			points.push(i);
			visited[i] = true;
		}
	}
	BFS(points);
	for(i = 1; i <= N; ++ i)
	{
		if(degree[i] == 2)
		{
			cout << i << " ";
		}
	}
}

五、对局匹配

题目描述

小明喜欢在一个围棋网站上找别人在线对弈。这个网站上所有注册用户都有一个积分,代表他的围棋水平。

小明发现网站的自动对局系统在匹配对手时,只会将积分差恰好是 K 的两名用户匹配在一起。如果两人分差小于或大于 K,系统都不会将他们匹配。

现在小明知道这个网站总共有 N 名用户,以及他们的积分分别是 A1​,A2​,⋯AN​。

小明想了解最多可能有多少名用户同时在线寻找对手,但是系统却一场对局都匹配不起来(任意两名用户积分差不等于 K)?

输入描述

输入描述

第一行包含两个个整数 N,K。

第二行包含 N 个整数 A1​,A2​,⋯AN​。

其中,1≤N≤10^5,0≤Ai≤10^5,0≤K≤10^5。

输出描述

输出一个整数,代表答案。

输入输出样例

示例

输入

10 0
1 4 2 8 5 7 1 4 2 8

输出

6

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 256M

思路:很显然,当k=0时,答案就是数据中存在多少不同的数。

当k>0的时候,我们发现,实际上,只有那些对k同余的才可能会产生影响。如k=3,那么实际上会互相影响的举例为: 0 3 6 9、2 5 8 11等。那么我们可以分组考虑,即对这k组分别求最大的不匹配数,求和即可。

用dp[i]表示在0~i区间中,与i同组的最大不匹配数,那么很显然,对于i来说:

A.如果选择i,那么不能选择i-k,即dp[i] = num[i] + dp[i - 2 * k] (num[i]表示数值为i的个数)

B.如果不选择i,那么dp[i] = dp[i - k]

#include <iostream>
#include <algorithm>
#include <unordered_set>
using namespace std;
const int maxn = 1e5 + 7;

int dp[maxn];

int main()
{
  int N, k, i, score, maxScore = 0, ans = 0;
  cin >> N >> k;
  if(k == 0)
  {
    unordered_set<int> scores;
    for(i = 1; i <= N; ++ i)
    {
      cin >> score;
      scores.insert(score);
    }
    cout << scores.size() << endl;
    return 0;
  }
  for(i = 1; i <= N; ++ i)
  {
    cin >> score;
    maxScore = max(maxScore, score);
    ++ dp[score]; //直接用计数对dp初始化 省去num数组
  }
  for(i = k; i <= maxScore; ++ i)
  {
    if(i - k * 2 >= 0)
    {
      dp[i] = max(dp[i] + dp[i - k * 2], dp[i - k]);
    }
    else
    {
      dp[i] = max(dp[i - k], dp[i]);
    }
  }
  for(i = 0; i < k; ++ i)
  {
    ans += dp[maxScore - i];
  }
  cout << ans;
  return 0;
}

思路拓展:实际上本题还可以二分不匹配数,然后使用DFS去判断是否合法。(不是100%的解法)

六、铁路观光

题目描述

跳蚤国正在大力发展旅游业,每个城市都被打造成了旅游景点。

许多跳蚤想去其他城市旅游,但是由于跳得比较慢,它们的愿望难以实现。这时,小 C 听说有一种叫做火车的交通工具,在铁路上跑得很快,便抓住了商机,创立了一家铁路公司,向跳蚤国王请示在每两个城市之间都修建铁路。

然而,由于小 C 不会扳道岔,火车到一个城市以后只能保证不原路返回,而会随机等概率地驶向与这个城市有铁路连接的另外一个城市。

跳蚤国王向广大居民征求意见,结果跳蚤们不太满意,因为这样修建铁路以后有可能只游览了 3 个城市(含出发的城市)以后就回来了,它们希望能多游览几个城市。于是跳蚤国王要求小 C 提供一个方案,使得每只跳蚤坐上火车后能多游览几个城市才回来。

小 C 提供了一种方案给跳蚤国王。跳蚤国王想知道这个方案中每个城市的居民旅游的期望时间(设火车经过每段铁路的时间都为 1 ),请你来帮跳蚤国王。

输入描述

输入的第一行包含两个正整数 n、m,其中 n 表示城市的数量,m 表示方案中的铁路条数。

接下来 m 行,每行包含两个正整数u、v,表示方案中城市 u 和城市 v 之间有一条铁路。

保证方案中无重边无自环,每两个城市之间都能经过铁路直接或间接到达,且火车由任意一条铁路到任意一个城市以后一定有路可走。

其中,4≤k≤n≤21,1≤u,v≤n。

输出描述

输出 n 行,第 i 行包含一个实数 B_i,表示方案 B 中城市 i 的居民旅游的期望时间。

你应当输出足够多的小数位数,以保证输出的值和真实值之间的绝对或相对误差不超过 10^−9。

输入输出样例

示例

输入

4 5
1 2
2 3
3 4
4 1
1 3

输出

3.333333333333
5.000000000000
3.333333333333
5.000000000000

运行限制

  • 最大运行时间:2s
  • 最大运行内存: 256M

[注]:自环指的是从x有一条直接到x的边。

思路:本题可能会有人觉得只要求出某一个点X出发并回来的所有路径数,就可以求得每一个路径的概率,也由此可以知道期望,但问题是本题并没有说每一个城市只能经过一次,而一旦存在环路,就会导致路线无限延长,即存在无穷可列个情况,但要明白,无穷可列个情况并不一定会使得期望不存在(详见概率论),因此这个方法不现实。但我们退而求其次,本题允许的误差是1e-9,那么是否可以说明路径到了一定的长度后,误差在这个范围内趋于稳定,这是必然的(无穷级数理论),也就是说整个求和结果变得可知,结果是一个无限逼近的结果,即有上限。但问题在于,枚举到何种程度时误差可控不可知,且这样做,枚举的路径长度一定会比较长,此时复杂度会很高,因此也不会是正解。

[注]:在某些数据下,DFS不考虑环应该是可以过一些特别小的数据的,即一些小图的情况。

本题实际上是一个类流量均衡问题,我们将路径被经过的概率想象成流量,当流量趋于稳定时,对于城市x,只要求其邻接边的流量和即是城市被访问的次数的期望。

1.初始化的时候,x的度数为deg[x],那么x的所有邻接边上的流量即为 1 / deg[x]。即若用p[x][y]表示x->y的流量,则有p[x][y] = 1 / deg[x]。

2.定义in[y]表示流入y的流量,则有:

in[y]=\sum_xp[x][y]

然后。。。还是放郑未老师的题解叭QAQ

解题思路

这个解法是满分解法,对每个点单独计算。

设这个点的度为 d,那么它向外的每条边都有 \cfrac{1}{d} 概率被走。

我们对每条边定义一个有向权值 P(u,v),含义为这条边被走的次数的期望。显然起始点的各边的外向权值为 \cfrac{1}{d}

接下来我们考虑把权值往外进一步扩散。

由于单纯的 BFS/DFS 复杂度高,边界条件不清楚,因此不宜采用这类写法。

由于图中的权值有马尔可夫性(简单理解:无记忆性),确定存在解的话,我们可以迭代得到稳定状态。

这里的权值类似于网络流中的流量,对每个结点需要做到流量平衡。

算法描述:

in(v) 表示 vv 的总入权,入权可以流向相邻边

  • 外层:确认源点

​初始化源点到相邻点的P值

  • 第二层:对每一条边e=<u, v>,反复更新 v 的入权和 P(u,v),直到更新差异很小。

    • 遍历每条边,根据 P(u,v) 更新 in(v): in(v)+=P(u,v);

    • 此时,每个点的 in 值可能发生变化,这将引起其对外 PP 值的变化。对于 u 来说,其入权有一部分是来自 e 的 流入,扣除后可分配权值为 in(u)−P(v,u) 。

​ 当前 u 流入的权值,除去经由 e 流入的之外,均可由 e 流出(e是其中的\cfrac{1}{d-1}​)。若 u 的度为 deg(u),e 将分得其中的 \cfrac{1}{\deg(u)-1}

​ 因此,设当前 u 的总流入权值为 \mathrm{in}(u),则任意 <u,v> 的权值 P(u, v) 需被更新为 \cfrac{\mathrm{in}(u)-P(v, u)}{\deg(u)-1}。注意权值只向外扩,所以当计算的更新值小于当前值时不更新。

​ 继续迭代……

当全图权值均不变化时停止迭代,此时权值和 ∑P(u,v) 即为答案。

注意图规模越大时需要的迭代次数越多,但迭代时间越长,这里需要做一定的权衡。

#include <vector>
#include <iostream>

#define eps 1e-10
#define maxn 25
using namespace std;

int deg[maxn];
vector<int> G[maxn];
double in[maxn];
double P[maxn][maxn];

int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1, u, v; i <= m; ++i) {
        scanf("%d%d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
        ++deg[v], ++deg[u]; // calculate degree
    }
    for (int uu = 1; uu <= n; ++uu) { // 对每个顶点
        memset(P, 0, sizeof P);
        for (size_t j = 0; j < G[uu].size(); ++j)  // 遍历邻居
        {
            int v = G[uu][j];
            P[uu][v] = 1.0 / deg[uu];
            printf("P[%d][%d]=%f\n", uu, v, P[uu][v]);
        } // 初始化P[u][v],源点到相邻点的P值确定
        bool flag = true;
        int T = max(10000, 1000000 / m); // determine number of iterations
        while (flag || T--) { // 控制总迭代次数
            flag = false;
            for (int i = 1; i <= n; ++i) in[i] = 0.0;
//          更新入权:根据P值更新in值(即某点的总入权)
            for (int u = 1; u <= n; ++u) {
                for (size_t j = 0; j < G[u].size(); ++j) {
                    int v = G[u][j];
                    // 增加v的入权,即增加来自当前边的P[u][v]
                    in[v] += P[u][v]; // calculate in for each vertex
                    printf("in[%d] = %f \n", v, in[v]);
                }

            }

            for (int u = 1; u <= n; ++u) {
                for (size_t j = 0; j < G[u].size(); ++j) {
                    int v = G[u][j];
                    // 可分配到uv的权值
                    double diff = (in[u] - P[v][u]) / (deg[u] - 1) - P[u][v]; // update diff
                    if (diff > 0) { // update only if increasing
                        P[u][v] += diff;
                        printf("P[%d][%d] = %f \n",u, v, P[u][v]);
                        if (diff > 1e-15)
                            flag = true; // 意味着还需要更新
                    }
                }
            }
        }// 迭代完成
        double ans = 0.0;
        for (int u = 1; u <= n; ++u) {
            for (size_t i = 0; i < G[u].size(); ++i) {
                int v = G[u][i];
                ans += P[u][v];
            }
        }// ans为所有边的权之和
        printf("%.12lf\n", ans);
    }
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值