好序列的个数
题目描述
现在你面前有一棵n个节点的树(全连通无环图)。树上的边只有2种颜色,红色或者黑色。现在还给你一个整数k,考虑下面这个k个节点的序列[a1, a2, ..., ak]。
[a1, a2, ..., ak]如果是”好序列“当且仅当满足下面的条件:
- 我们要走一条从a1开始到ak结束的路径。
- 从a1开始,到a2走一条a1到a2的最短路。然后从a2开始,继续走一条到a3的最短路,以此类推,最终到a(k-1)和ak。
- 走的路径中至少包含一条黑色的边。
总共有n^k(n的k次方种路径方案),那么有多少路径是“好序列”呢?这个值可能非常大,输出的结果对(10^9+7)取模就可以。
输入描述
第一行是2个整数n和k,其中(2 <= n <= 10^5, 2 <= k <= 100),n表示树的节点个数,k表示序列的长度。
下面n-1行,每行包含3个整数,u[i], v[i], w[i],其中1 <= u[i], v[i] <= n, w[i] = 0或1。u[i], v[i]表示这两个节点之间有一条边,w[i]表示这条边的颜色,其中0表示红色,1表示黑色。
输出描述
输出所有“好序列”的个数模(10^9+7)。
解题思路
好序列的数量 = 全部路径的数量 - 非好序列的数量 = 全部路径的数量 - 仅含红边的路径的数量。
代码实现
typedef long long ll;
const int N = 1e5 + 5, MOD = 1e9 + 7;
int h[N], e[N << 1], ne[N << 1], idx;
bool w[N << 1], st[N];
/**
* 建图
* @param a 边的起点
* @param b 边的终点
* @param c 边的权重,0红,1黑
*/
void add(int a, int b, bool c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
/**
* 快速幂
* @return x^n % MOD
*/
ll kms(ll x, ll n) {
ll res = 1 % MOD;
while (n) {
if (n & 1)res = res * x % MOD;
x = x * x % MOD;
n >>= 1;
}
return res;
}
/**
* 求包含节点x、仅含红边、不含已访问红边的子图的白边的数量
* @param x 节点编号
* @param f 节点x从节点f拓展而来
*/
int dfs(int x, int f) {
int res = 1;
st[x] = true;
for (int i = h[x]; i != -1; i = ne[i]) {
int j = e[i];
if (j != f && !st[j] && !w[i])res += dfs(j, x);
}
return res;
}
int main() {
memset(h, -1, sizeof h);
memset(st, 0, sizeof st);
int n, k;
scanf("%d%d", &n, &k);
for (int i = 1, a, b, c; i < n; i++) {
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
add(b, a, c);
}
ll res = 0;
for (int i = 1; i <= n; i++)
if (!st[i])
res = (res + kms(dfs(i, -1), k)) % MOD;
res = (kms(n, k) + MOD - res) % MOD;
printf("%lld", res);
return 0;
}
时间复杂度:$O(n)$。
空间复杂度:$O(n)$。
计算累计平方和
题目描述
给定一个32位int型正整数,我们定义如下操作,取其十进制各位数字的平方和,并不断重复这个操作。如果某次操作完成后得到的结果是1,则返回true;否则继续执行,直到证明永远不会得到结果为1,返回false。
输入描述
输入一个m(1<=m<=1000),表示查询组数。
接下来m行,每一行为一个32位int型正整数。
输出描述
对于每次查询,如果满足题目描述,则输出"true",反之输出"false" (不要输出引号)。
解题思路
在重复操作的过程中,如果出现循环,则说明永远无法得到结果。
代码实现
/**
* 计算x的各位平方之和
*/
int cal(int x) {
int res = 0, t;
while (x) {
t = x % 10, x /= 10;
res += t * t;
}
return res;
}
int main() {
int m, x;
scanf("%d", &m);
while (m--) {
scanf("%d", &x);
unordered_set<int> st;
while (st.find(x) == st.end()) {
if (x == 1)break;
st.insert(x);
x = cal(x);
}
printf(x == 1 ? "true\n" : "false\n");
}
return 0;
}
时间复杂度:$O(10^2*8)$,即 $O(1)$。各位平方之和最多 $10^2*8$ 种可能。
空间复杂度:$O(1)$。
版本升级判定
题目描述
给定两个版本号,只有在版本号更高的时候,才可以升级。「.」号作为分割符使用,版本号中只有数和.号。
输入描述
第一行为m(1<=m<=100000),表示测试组数,接下来m行,表示m次查询。
每行两个版本号,空格分隔。一个版本号中最多只会出现3个「.」。每个版本号中数字1<=x<=100。
输出描述
对于每一次查询,输出是否可以升级,是则输出"true",否则输出"false" (引号不要输出)。
解题思路
按题意进行判断即可。
代码实现
/**
* 判断是否可以从s升级到t
*/
bool comp(const char *s, const char *t) {
int i = 0, j = 0;
while (s[i] != '\0' && t[j] != '\0') {
int x = 0;
while (s[i] != '\0' && s[i] != '.')x = x * 10 + s[i] - '0', i++;
if (s[i] == '.')i++;
int y = 0;
while (t[j] != '\0' && t[j] != '.')y = y * 10 + t[j] - '0', j++;
if (t[j] == '.')j++;
if (x == y)continue;
return x < y;
}
while (t[j] != '\0') {
if (t[j] != '.' && t[j] != '0')return true;
j++;
}
return false;
}
int main() {
int m;
char s[20], t[20];
scanf("%d", &m);
while (m--) {
scanf("%s%s", s, t);
printf(comp(s, t) ? "true\n" : "false\n");
}
return 0;
}
时间复杂度:$O(m)$。
空间复杂度:$O(m)$。
合并内容流
题目描述
合并两个内容流,实现隔4个插入1个,如果合并完还有剩下,则加内容流尾部。
输入描述
第1行表示第一种类型的内容,第2行表示第二种类型的内容,字符数量<=100,空格分隔,比如说
1 2 3 4 5 6 7 8 9
a b c
输出描述
合并两种内容流,输出
1 2 3 4 a 5 6 7 8 b 9 c
解题思路
模拟即可。
代码实现
int main() {
const int N = 1e2 + 5;
char s[2][N];
scanf("%[^\n]\n%[^\n]", s[0], s[1]);
int i = 0, j = 0, k = 0;
while (true) {
bool f = k % 5 == 4;
int &idx = f ? j : i;
printf("%c ", s[f][idx]);
idx++;
if (s[f][idx] == '\0') {
printf("%s", s[!f] + (f ? i : j));
break;
};
idx++, k++;
}
return 0;
}
时间复杂度:$O(1)$。
空间复杂度:$O(1)$。
END
详细题解:由于本平台不能完全支持Markdown语法,查看详细题解请移步至 A 或 B 或 C
文章文档:公众号 字节幺零二四
回复关键字可获取本文文档。
文章声明:题目来源 牛客 平台,如有侵权,请联系删除!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。