作者 陈越
单位 浙江大学
一门武功能否传承久远并被发扬光大,是要看缘分的。一般来说,师傅传授给徒弟的武功总要打个折扣,于是越往后传,弟子们的功夫就越弱…… 直到某一支的某一代突然出现一个天分特别高的弟子(或者是吃到了灵丹、挖到了特别的秘笈),会将功夫的威力一下子放大N倍 —— 我们称这种弟子为“得道者”。
这里我们来考察某一位祖师爷门下的徒子徒孙家谱:假设家谱中的每个人只有1位师傅(除了祖师爷没有师傅);每位师傅可以带很多徒弟;并且假设辈分严格有序,即祖师爷这门武功的每个第i
代传人只能在第i-1
代传人中拜1个师傅。我们假设已知祖师爷的功力值为Z
,每向下传承一代,就会减弱r%
,除非某一代弟子得道。现给出师门谱系关系,要求你算出所有得道者的功力总值。
输入在第一行给出3个正整数,分别是:N(≤105)——整个师门的总人数(于是每个人从0到N−1编号,祖师爷的编号为0);Z——祖师爷的功力值(不一定是整数,但起码是正数);r ——每传一代功夫所打的折扣百分比值(不超过100的正数)。接下来有N行,第i行(i=0,⋯,N−1)描述编号为i的人所传的徒弟,格式为:
Ki ID[1] ID[2] ⋯ ID[Ki]
其中Ki是徒弟的个数,后面跟的是各位徒弟的编号,数字间以空格间隔。Ki为零表示这是一位得道者,这时后面跟的一个数字表示其武功被放大的倍数。
在一行中输出所有得道者的功力总值,只保留其整数部分。题目保证输入和正确的输出都不超过1010。
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
using namespace std;
const int MAX_PEOPLE = 1e5 + 5;
vector lineage[MAX_PEOPLE];
int total_people;
int amplification[MAX_PEOPLE];
double initial_power, discount_rate, total_power;
// 深度优先搜索函数
void dfs(int person_id, double current_power) {
if (amplification[person_id]) {
total_power += current_power * amplification[person_id];
}
for (int &apprentice_id : lineage[person_id]) {
dfs(apprentice_id, current_power * discount_rate);
}
}
int main() {
cin >> total_people >> initial_power >> discount_rate;
discount_rate = (100.0 - discount_rate) / 100.0;
for (int i = 0; i < total_people; i++) {
int num_apprentices;
cin >> num_apprentices;
if (num_apprentices == 0) {
cin >> amplification[i];
} else {
for (int j = 0; j < num_apprentices; j++) {
int apprentice_id;
cin >> apprentice_id;
lineage[i].push_back(apprentice_id);
}
}
}
dfs(0, initial_power); //从祖师爷开始计算功力
cout << (int)total_power; //输出所有得道者的总功力,保留整数部分
return 0;
}
作者 DAI, Longao
单位 杭州百腾教育科技有限公司
龙龙是“饱了呀”外卖软件的注册骑手,负责送帕特小区的外卖。帕特小区的构造非常特别,都是双向道路且没有构成环 —— 你可以简单地认为小区的路构成了一棵树,根结点是外卖站,树上的结点就是要送餐的地址。
每到中午 12 点,帕特小区就进入了点餐高峰。一开始,只有一两个地方点外卖,龙龙简单就送好了;但随着大数据的分析,龙龙被派了更多的单子,也就送得越来越累……
看着一大堆订单,龙龙想知道,从外卖站出发,访问所有点了外卖的地方至少一次(这样才能把外卖送到)所需的最短路程的距离到底是多少?每次新增一个点外卖的地址,他就想估算一遍整体工作量,这样他就可以搞明白新增一个地址给他带来了多少负担。
输入第一行是两个数 N 和 M (2≤N≤105, 1≤M≤105),分别对应树上节点的个数(包括外卖站),以及新增的送餐地址的个数。
接下来首先是一行 N 个数,第 i 个数表示第 i 个点的双亲节点的编号。节点编号从 1 到 N,外卖站的双亲编号定义为 −1。
接下来有 M 行,每行给出一个新增的送餐地点的编号 Xi。保证送餐地点中不会有外卖站,但地点有可能会重复。
为了方便计算,我们可以假设龙龙一开始一个地址的外卖都不用送,两个相邻的地点之间的路径长度统一设为 1,且从外卖站出发可以访问到所有地点。
注意:所有送餐地址可以按任意顺序访问,且完成送餐后无需返回外卖站。
对于每个新增的地点,在一行内输出题目需要求的最短路程的距离。
7 4
-1 1 1 1 2 2 3
5
6
2
4
2
4
4
6
#include
using namespace std;
const int MAX_NODES = 100010;
int parentNode[MAX_NODES]; //存储每个节点的父节点编号,外卖站的父节点为 -1
int visitDepth[MAX_NODES]; //记录从节点到已访问或根节点的距离
int maxReachedDepth = 0; //当前访问路径中访问到的最深深度
//深度优先搜索,返回新增的边数贡献
int dfsCompute(int currentNode, int currentDepth) {
//如果到达根节点(父节点为 -1)或节点已被访问
if (parentNode[currentNode] == -1 || visitDepth[currentNode] != 0) {
//更新最大深度:当前深度 + 已访问深度
maxReachedDepth = max(maxReachedDepth, currentDepth + visitDepth[currentNode]);
//到根节点来回的边数为 currentDepth * 2
return currentDepth * 2;
}
//继续向父节点探索,深度加一
int addedEdges = dfsCompute(parentNode[currentNode], currentDepth + 1);
//设置当前节点的已访问深度为父节点的已访问深度 + 1
visitDepth[currentNode] = visitDepth[parentNode[currentNode]] + 1;
return addedEdges;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int numNodes, numQueries;
cin >> numNodes >> numQueries;
//读入每个节点的父节点
for (int i = 1; i <= numNodes; i++) {
cin >> parentNode[i];
}
int totalDistance = 0; //累积的双向边数总和
while (numQueries--) {
int deliveryPoint;
cin >> deliveryPoint;
//计算新增的双向边数
totalDistance += dfsCompute(deliveryPoint, 0);
//最短路径 = 累积双向边数 - 最大深度(单程)
int minTravel = totalDistance - maxReachedDepth;
cout << minTravel << '\n';
}
return 0;
}
作者 DAI, Longao
单位 杭州百腾教育科技有限公司
有 2k 名选手将要参加一场锦标赛。锦标赛共有 k 轮,其中第 i 轮的比赛共有 2k−i 场,每场比赛恰有两名选手参加并从中产生一名胜者。每场比赛的安排如下:
第 k 轮唯一一场比赛的胜者就是整个锦标赛的最终胜者。
举个例子,假如共有 8 名选手参加锦标赛,则比赛的安排如下:
已知每一名选手都有一个能力值,其中第 i 名选手的能力值为 ai。在一场比赛中,若两名选手的能力值不同,则能力值较大的选手一定会打败能力值较小的选手;若两名选手的能力值相同,则两名选手都有可能成为胜者。
令 li,j 表示第 i 轮第 j 场比赛 败者 的能力值,令 w 表示整个锦标赛最终胜者的能力值。给定所有满足 1≤i≤k 且 1≤j≤2k−i 的 li,j 以及 w,请还原出 a1,a2,⋯,an。
第一行输入一个整数 k(1≤k≤18)表示锦标赛的轮数。
对于接下来 k 行,第 i 行输入 2k−i 个整数 li,1,li,2,⋯,li,2k−i(1≤li,j≤109),其中 li,j 表示第 i 轮第 j 场比赛 败者 的能力值。
接下来一行输入一个整数 w(1≤w≤109)表示锦标赛最终胜者的能力值。
输出一行 n 个由单个空格分隔的整数 a1,a2,⋯,an,其中 ai 表示第 i 名选手的能力值。如果有多种合法答案,请输出任意一种。如果无法还原出能够满足输入数据的答案,输出一行 No Solution
。
请勿在行末输出多余空格。
3
4 5 8 5
7 6
8
9
7 4 8 5 9 8 6 5
2
5 8
3
9
No Solution
#include
using namespace std;
static const int MAX_ROUNDS = 18;
// loserStrength[round][match] 存储第 round 轮第 match 场比赛的败者能力
int loserStrength[MAX_ROUNDS + 1][1 << MAX_ROUNDS];
// playerStrength[i] 存储第 i 号选手的能力值(1-indexed)
vector playerStrength;
int rounds; // 总轮数
// 尝试在第 roundIndex 轮、第 matchIndex 场比赛中,胜者能力为 winnerStrength 的情况下,重构选手能力值
bool reconstructBracket(int roundIndex, int matchIndex, int winnerStrength) {
int loserVal = loserStrength[roundIndex][matchIndex];
// 第一轮:直接分配两名选手
if (roundIndex == 1) {
if (winnerStrength >= loserVal) {
int leftPlayer = 2 * matchIndex - 1;
int rightPlayer = 2 * matchIndex;
playerStrength[leftPlayer] = loserVal;
playerStrength[rightPlayer] = winnerStrength;
return true;
}
return false;
}
// 胜者能力必须不小于败者能力
if (winnerStrength < loserVal) {
return false;
}
// 子比赛索引
int leftMatch = 2 * matchIndex - 1;
int rightMatch = 2 * matchIndex;
// 情况1:胜者在左子区,败者在右子区
if (reconstructBracket(roundIndex - 1, leftMatch, winnerStrength)
&& reconstructBracket(roundIndex - 1, rightMatch, loserVal)) {
return true;
}
// 情况2:胜者在右子区,败者在左子区
if (reconstructBracket(roundIndex - 1, leftMatch, loserVal)
&& reconstructBracket(roundIndex - 1, rightMatch, winnerStrength)) {
return true;
}
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> rounds;
// 读取每轮的败者能力
for (int r = 1; r <= rounds; ++r) {
int matchCount = 1 << (rounds - r);
for (int m = 1; m <= matchCount; ++m) {
cin >> loserStrength[r][m];
}
}
int championStrength;
cin >> championStrength;
int playerCount = 1 << rounds;
playerStrength.assign(playerCount + 1, 0);
// 从最终比赛开始重构
if (!reconstructBracket(rounds, 1, championStrength)) {
cout << "No Solution";
return 0;
}
// 输出所有选手的能力值
for (int i = 1; i <= playerCount; ++i) {
cout << playerStrength[i] << (i < playerCount ? ' ' : '\n');
}
return 0;
}
作者 陈越
单位 浙江大学
所有元素为非负整数,且各行各列的元素和都等于 7 的 3×3 方阵称为“吉利矩阵”,因为这样的矩阵一共有 666 种。
本题就请你统计一下,把 7 换成任何一个 [2,9] 区间内的正整数 L,把矩阵阶数换成任何一个 [2,4] 区间内的正整数 N,满足条件“所有元素为非负整数,且各行各列的元素和都等于 L”的 N×N 方阵一共有多少种?
输入在一行中给出 2 个正整数 L 和 N,意义如题面所述。数字间以空格分隔。
在一行中输出满足题目要求条件的方阵的个数。
7 3
666
#include
using namespace std;
const int MAX_SIZE = 5; // 最大的矩阵阶数
int targetSum, matrixSize, solutionCount = 0; // 目标和、矩阵阶数、符合条件的方阵数量
int rowSum[MAX_SIZE], colSum[MAX_SIZE]; // 每行和每列的当前累积和
// 深度优先搜索递归函数
void search(int cellIndex) {
// 如果所有单元格都已经填写完毕,说明找到一个符合条件的方阵
if (matrixSize * matrixSize == cellIndex) {
solutionCount++; // 统计结果
return;
}
// 遍历当前单元格可以填入的所有可能的非负整数值
for (int value = 0; value <= targetSum; value++) {
int rowIndex, colIndex; // 当前单元格所在的行和列
rowIndex = cellIndex / matrixSize; // 行索引
colIndex = cellIndex % matrixSize; // 列索引
// 如果填入当前值会导致行或列的和超过目标和,则跳过
if (rowSum[rowIndex] + value > targetSum || colSum[colIndex] + value > targetSum) continue;
// 如果是最后一行,则确定当前列的值,使得列的和正好等于目标和
if (rowIndex == matrixSize - 1) value = targetSum - colSum[colIndex];
// 如果是最后一列,则确定当前行的值,使得行的和正好等于目标和
if (colIndex == matrixSize - 1) value = targetSum - rowSum[rowIndex];
// 更新当前行和列的累积和
rowSum[rowIndex] += value;
colSum[colIndex] += value;
// 递归处理下一个单元格
search(cellIndex + 1);
// 回溯,撤销当前选择
rowSum[rowIndex] -= value;
colSum[colIndex] -= value;
}
}
int main() {
// 输入目标和和矩阵阶数
cin >> targetSum >> matrixSize;
// 从第一个单元格开始搜索
search(0);
// 输出符合条件的方阵总数
cout << solutionCount;
}