特立独行的幸福
思路
先预处理出在
[
1
,
1
0
4
]
[1,10^4]
[1,104] 的内每个数是否为幸福数。
如果从数字
i
i
i 开始最终能迭代到
1
1
1,则标记
i
s
[
i
]
=
1
is[i]=1
is[i]=1 表示数字
i
i
i 为幸福数,同时在迭代过程中,每个依赖数字
i
i
i 的数字
j
j
j 都记录下依赖的数字
i
i
i ,这样就可以对每个数字查询其依赖的数字是否有存在区间
[
a
,
b
]
[a,b]
[a,b] 内的。
在迭代过程中,每迭代一个数字要标记其已经被迭代过,如果迭代到一个数字之前已经被迭代过,则说明进入死循环,这种情况则不为幸福数。
判断素数用试除法判断即可,如果为素数输出双倍数值,否则原数值输出。
细节
迭代停止的条件是迭代到 1 1 1 或者迭代到已经迭代过的数字,而不是迭代到小于 10 10 10 的数字
代码实现
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 5;
int a, b;
int is[N], v[N];
vector<int> h[N];
// 求x的数位平方和
int g(int x)
{
int res = 0;
while (x) {
res += pow(x % 10, 2);
x /= 10;
}
return res;
}
// 判断x是否为素数
int check(int x)
{
if (x <= 3)
return x == 2;
for (int i = 2; i <= x / i; i++) {
if (x % i == 0)
return 0;
}
return 1;
}
int main()
{
cin >> a >> b;
for (int i = 1; i < N; i++) {
set<int> all;
int j = i, flag = 1;
// flag 表示是否为幸福数
while (j != 1) {
// 如果迭代到已经迭代过的数字,则不为幸福数
if (all.count(j)) {
flag = 0;
break;
}
all.insert(j);
j = g(j);
}
if (flag) {
is[i] = 1;
v[i] = all.size();
for (int x : all) {
if (x != i)
h[x].push_back(i);
}
}
}
int f = 0;
for (int i = a; i <= b; i++) {
if (is[i] == 1) {
int can = 1;
// h[i] 为数字i依赖的数字集合
for (int j : h[i]) {
if (a <= j && j <= b)
can = 0;
// 判断是否有依赖数字j在[a,b]内
}
if (!can)
continue;
f = 1;
if (check(i))
v[i] *= 2;
cout << i << ' ' << v[i] << '\n';
}
}
if (!f)
cout << "SAD";
}
图着色问题
思路
因为点数不多,所以在每次查询中,直接遍历每个点的领接表,看是否存在相邻点的颜色相同即可。
判断颜色是否恰好
k
k
k 种,可以用
c
+
+
c++
c++ 的
s
e
t
set
set 去重,然后直接查询
s
e
t
set
set 的大小即为颜色种数。
复杂度
空间复杂度 O ( n ) O(n) O(n),时间复杂度 O ( n 2 ) O(n^2) O(n2)
代码实现
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, m, k, q, w[N];
vector<int> h[N];
int main()
{
cin >> n >> m >> k;
for (int i = 1; i <= m; i++) {
int a, b;
cin >> a >> b;
h[a].push_back(b);
h[b].push_back(a);
}
cin >> q;
while (q--) {
int flag = 1;
set<int> all;
for (int i = 1; i <= n; i++) {
cin >> w[i];
all.insert(w[i]);
}
for (int i = 1; i <= n; i++) {
// h[i] 为节点i的领接表,节点j与节点i相邻
for (int j : h[i]) {
if (w[i] == w[j])
flag = 0;
}
}
// sz 为颜色种数
int sz = all.size();
if (sz != k)
flag = 0;
cout << (flag ? "Yes" : "No") << '\n';
}
}
大众情人
思路
需要对于每个人求他到其他人的最短路,这涉及到多源最短路,做法有很多:直接跑一次
F
l
o
y
d
Floyd
Floyd 算法,或者对每个点跑一次单源最短路,
b
e
l
l
m
a
n
−
f
o
r
d
bellman-ford
bellman−ford算法,
d
i
j
k
s
t
r
a
dijkstra
dijkstra算法都可以。
对每个人
u
u
u,求出所有异性离他最远的最短路
w
w
w,注意这里是异性
v
v
v 对到
u
u
u 的最短路,不是从
u
u
u 到
v
v
v 的最短路。
接着,对两种性别的人都按
w
w
w 进行一次升序排序,如果
w
w
w 相同按编号升序,则从而使得两种性别中的 “大众情人” 都排在最前面,且按编号递增排列。
(题目中要求值最大的,经测试发现实际上要求值最小的)
最后把两种性别的 “大众情人” 输出即可,判断是否是对应性别的 “大众情人”,只需要判断和第一个人的
w
w
w 是否相等即可。
复杂度
时间复杂度和空间复杂度均为 O ( n 2 ) O(n^2) O(n2)
代码实现
#include <bits/stdc++.h>
using namespace std;
const int N = 505;
int n, m;
string s[N];
int g[N][N];
// p1[1] 为 离异性的最远最短路
// p1[0] 为 编号
// p2 同理
int cmp(array<int, 2> p1, array<int, 2> p2)
{
// 按最短路升序,次之按编号升序
if (p1[1] != p2[1])
return p1[1] < p2[1];
return p1[0] < p2[0];
}
int main()
{
cin >> n;
// 初始化为正无穷
memset(g, 0x3f, sizeof(g));
for (int i = 1; i <= n; i++) {
cin >> s[i];
int k;
cin >> k;
while (k--) {
int x, y;
scanf("%d:%d", &x, &y);
g[i][x] = min(g[i][x], y);
}
}
// 到达自己的最短路为0
for (int i = 1; i <= n; i++) {
g[i][i] = 0;
}
// floyd所求多源最短路
for (int k = 1; k <= n; k++) {
for (int u = 1; u <= n; u++) {
for (int v = 1; v <= n; v++) {
g[u][v] = min(g[u][v], g[u][k] + g[k][v]);
}
}
}
vector<array<int, 2>> h1, h2;
for (int i = 1; i <= n; i++) {
int ma = 0;
for (int j = 1; j <= n; j++) {
if (s[i] != s[j])
ma = max(ma, g[j][i]);
}
// 按照性别分类
if (s[i] == "F")
h1.push_back({ i, ma });
else
h2.push_back({ i, ma });
}
sort(h1.begin(), h1.end(), cmp);
sort(h2.begin(), h2.end(), cmp);
int f = 0;
for (auto it : h1) {
if (it[1] == h1[0][1]) {
if (f)
cout << ' ';
cout << it[0];
f = 1;
}
}
cout << '\n';
f = 0;
for (auto it : h2) {
if (it[1] == h2[0][1]) {
if (f)
cout << ' ';
cout << it[0];
f = 1;
}
}
}
龙龙送外卖
思路
可以发现,如果要求最后要返回根节点,那么经过的每条边最少可以只经过两次(一来一回)。
具体走法:
1,开始时可以任意选择某个要求访问的节点,然后从根节点走向它。
2,如果当前所在节点的子树内,有未被访问的要求访问的节点,那么就向下访问它,否则就返回根节点。
例如上面这棵树,打钩的是要访问的节点,要用最短路程访问完所有要求访问的节点,然后返回根节点,走法有多种:
(
1
−
>
2
−
>
3
−
>
2
−
>
5
−
>
1
−
>
8
−
>
1
−
>
11
−
>
1
)
,
(
1
−
>
2
−
>
5
−
>
2
−
>
3
−
>
1
−
>
11
−
>
1
−
>
8
−
>
1
)
,
(
1
−
>
11
−
>
1
−
>
8
−
>
1
−
>
2
−
>
3
−
>
2
−
>
5
−
>
1
)
(1->2->3->2->5->1->8->1->11->1),(1->2->5->2->3->1->11->1->8->1),(1->11->1->8->1->2->3->2->5->1)
(1−>2−>3−>2−>5−>1−>8−>1−>11−>1),(1−>2−>5−>2−>3−>1−>11−>1−>8−>1),(1−>11−>1−>8−>1−>2−>3−>2−>5−>1)
可以发现,访问完最后一个节点不返回,那么减少的路程为该节点的深度,因此,如果当前要访问的节点中最深深度为 m a ma ma,那么访问完所有要访问的节点,且不返回根节点的最短路程为 2 ∗ s − m a 2*s-ma 2∗s−ma。
每新增一个要访问的节点
u
u
u,有两种情况:
1,节点
u
u
u 在已经需要经过的路径上。
比如上图中,原先需要访问节点
3
,
5
3,5
3,5,新增访问节点
2
2
2,对最短路程是不影响的,因为访问
3
,
5
3,5
3,5 的过程中必然能访问到节点
2
2
2。
2,和情况
1
1
1 相反,节点
u
u
u 不在已经需要经过的路径上。
对于这种情况,从节点
u
u
u 一直向上走,直到遇到需要访问的节点或者根节点,经过的所有边即为新增加的需要经过的边。
在向上走的过程中可以同时标记下经过的节点,这样后面新增访问节点的时候,就可以直接查询该节点是否在已经需要经过的路径。
对于节点的深度,可以先进行预处理,先从根节点跑一遍
b
f
s
bfs
bfs 或者
d
f
s
dfs
dfs 计算即可。
根据不同的情况计算出新增的边数,与之前计算得到的需要经过的总边数相加,即可得到当前需要经过的总边数
s
s
s。同时再记录下需要访问的节点的最深深度
m
a
ma
ma,即可计算出新增访问节点
u
u
u 后的最短路程。
细节
注意根节点不一定是节点 1 1 1,需要在输入的时候记录下父节点为 − 1 -1 −1 的节点作为根节点。
复杂度
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)
代码实现
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int n, m;
int root, ma, sum;
int fa[N], st[N];
int dep[N];
vector<int> h[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> fa[i];
if (fa[i] != -1)
h[fa[i]].push_back(i);
else
root = i;
}
// 预处理节点深度
queue<int> q;
q.push(root);
while (q.size()) {
int u = q.front();
q.pop();
for (int v : h[u]) {
dep[v] = dep[u] + 1;
q.push(v);
}
}
st[root] = 1;
// 初始需要先标记根节点,才能处理第一个访问节点
for (int i = 1; i <= m; i++) {
int x;
cin >> x;
// 从新增访问节点u向上走直到找到已经标记的节点为止
if (x > 0 && !st[x]) {
int len = 0, xi = x;
while (xi > 0 && !st[xi]) {
st[xi] = 1;
xi = fa[xi];
sum++;
}
ma = max(ma, dep[x]);
}
// sum为当前需要经过的总边数,ma为需要访问的节点的最大深度
cout << sum * 2 - ma << '\n';
}
}
家庭房产
思路
用并查集维护即可,每个并查集的祖先节点都维护三个属性——总人数,总房产套数,总面积。
细节
在并查集祖先节点合并的时候,总是大的祖先节点合并到小的祖先节点,这样就能减少一个对最小编号的维护。
复杂度
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)
代码实现
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6+5;
int n;
int p[N];
vector<int> h[N];
// h[i] 存与编号为i的人有关系的人的编号
double s1[N], s2[N], s3[N];
// s1为总房产套数,s2为总面积,s3为总人数
void init()
{
for (int i = 0; i < N; i++) {
p[i] = i;
s3[i] = 1;
}
}
int find(int x)
{
if (p[x] != x)
p[x] = find(p[x]);
return p[x];
}
void merge(int x, int y)
{
x = find(x), y = find(y);
if (x == y)
return;
if (x > y)
swap(x, y);
p[y] = x;
s1[x] += s1[y];
s2[x] += s2[y];
s3[x] += s3[y];
}
struct val {
int id, v3;
double v1, v2;
};
int cmp(val p1, val p2)
{
if (fabs(p1.v2 - p2.v2) <= 1e-3) {
return p1.id < p2.id;
}
return p1.v2 >= p2.v2;
}
signed main()
{
init();
cin >> n;
set<int> all;
// all存所有出现的编号
for (int i = 1; i <= n; i++) {
int id, f1, f2, k;
cin >> id >> f1 >> f2 >> k;
all.insert(id);
vector<int> tmp(k);
for (int& x : tmp) {
cin >> x;
}
cin >> s1[id] >> s2[id];
if (f1 != -1) {
all.insert(f1);
h[id].push_back(f1);
}
if (f2 != -1) {
all.insert(f2);
h[id].push_back(f2);
}
for (int x : tmp) {
all.insert(x);
h[id].push_back(x);
}
}
for (int i : all) {
for (int j : h[i]) {
merge(i, j);
}
}
vector<val> ans;
for (int i : all) {
if (find(i) == i) {
ans.push_back({ i, (int)s3[i], s1[i] / s3[i], s2[i] / s3[i] });
}
}
sort(ans.begin(), ans.end(), cmp);
cout << ans.size() << '\n';
for (auto it : ans) {
printf("%04lld %lld %.3lf %.3lf\n", it.id, it.v3, it.v1, it.v2);
// 注意格式化输出要和数据类型对应
}
}
重排链表
思路
因为地址用五位数表示,所以可以开大小至少为
1
0
6
10^6
106 的数组
n
e
,
v
ne,v
ne,v,
v
[
i
]
v[i]
v[i] 表示地址为
i
i
i 的结点的数字,
n
e
[
i
]
ne[i]
ne[i] 表示地址为
i
i
i 的结点的下一个结点地址,然后从第
1
1
1 个结点开始遍历链表,从而得到顺序排列的链表结点,用一个数组
a
l
l
all
all 存下来。
注意到题目的重排规则是先尾结点,再头结点,然后再尾结点,再头结点…,这样子循环,所以可以用两个指针
p
1
,
p
2
p_1,p_2
p1,p2 分别指向头尾结点的下标,如果链表上的结点数为
n
n
n,那么初始时
p
1
=
0
,
p
2
=
n
−
1
p_1=0,p_2 = n-1
p1=0,p2=n−1,然后循环
n
n
n 次,循环奇数次时取下标为
p
2
p_2
p2 的结点(
a
l
l
[
p
2
]
all[p_2]
all[p2]),然后
p
2
p_2
p2 减一,偶数次则反之,取
a
l
l
[
p
1
]
all[p_1]
all[p1],
p
1
p_1
p1 加一。
细节
该题数据有毛病,有个测试点会出现多余结点,所以在重排的时候循环次数应该是链表上的结点数,对应上面的 a l l all all 的大小。
复杂度
时间复杂度 O ( n log n ) O(n\log n) O(nlogn),主要是排序的复杂度,空间复杂度 O ( n ) O(n) O(n)
代码实现
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e6 + 5;
int h, n;
int v[N], ne[N];
vector<array<int, 3>> all, ans;
signed main()
{
cin >> h >> n;
for (int i = 1; i <= n; i++) {
int id;
cin >> id >> v[id] >> ne[id];
}
for (int i = h; i != -1; i = ne[i]) {
all.push_back({ i, v[i], ne[i] });
}
n = all.size();
// 注意可能存在多余节点,因此n应该为链表上的节点数all.size()
int p1 = 0, p2 = n - 1;
for (int i = 0; i < n; i++) {
// 因为循环变量从0开始,所以和思路的奇偶性判断相反
if (i % 2 == 0) {
ans.push_back(all[p2--]);
} else {
ans.push_back(all[p1++]);
}
}
for (int i = 0; i < n; i++) {
if (i == n - 1) {
printf("%05lld %lld -1", ans[i][0], ans[i][1]);
} else {
printf("%05lld %lld %05lld\n", ans[i][0], ans[i][1], ans[i + 1][0]);
}
}
}
秀恩爱分得快
思路
开个三维数组
g
[
i
]
[
j
]
[
k
]
g[i][j][k]
g[i][j][k],表示性别为
i
i
i 的编号为
j
j
j 的人与编号为
k
k
k 的异性的亲密度。
i
=
0
i=0
i=0 表示男生,
i
=
1
i=1
i=1 表示女生,则当性别为
i
i
i 时,
i
⊕
1
i \oplus 1
i⊕1 为异性。
注意输入的时候,女性的编号前面有
′
−
′
'-'
′−′,可能有
′
−
0
′
'-0'
′−0′的情况,所以读入编号的时候以字符串读入,然后数字部分再转为数字。
然后按题意对亲密度累加即可,如果
A
,
B
A,B
A,B 是彼此亲密度最高的则输出
A
,
B
A,B
A,B,否则输出
A
A
A 亲密度最高的若干人,再输出
B
B
B 的。
复杂度
时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( n 2 ) O(n^2) O(n2)
代码实现
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e3 + 5;
int n, m;
double g[2][N][N];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int k;
cin >> k;
vector<int> a, b;
for (int j = 1; j <= k; j++) {
string s;
cin >> s;
if (s[0] == '-') {
// 用stoi函数转成数字
b.push_back(stoi(s.substr(1)));
} else {
a.push_back(stoi(s));
}
}
for (int x : a) {
for (int y : b) {
g[0][x][y] += 1.0 / k;
g[1][y][x] += 1.0 / k;
}
}
}
string sa, sb;
cin >> sa >> sb;
int xa, xb, pa = 0, pb = 0;
if (sa[0] == '-') {
xa = stoi(sa.substr(1));
pa = 1;
} else {
xa = stoi(sa);
}
if (sb[0] == '-') {
xb = stoi(sb.substr(1));
pb = 1;
// 我wa的地方,少写了“pb=1”
} else {
xb = stoi(sb);
}
double ma = 0, mb = 0;
for (int i = 0; i < n; i++) {
ma = max(g[pa][xa][i], ma);
mb = max(mb, g[pb][xb][i]);
}
// 浮点数判断相等
if (fabs(g[pa][xa][xb] - ma) <= 1e-6 && fabs(g[pb][xb][xa] - mb) <= 1e-6) {
cout << sa << ' ' << sb << '\n';
} else {
for (int i = 0; i < n; i++) {
if (fabs(g[pa][xa][i] - ma) <= 1e-6) {
cout << sa << ' ';
if (!pa)
cout << '-';
cout << i << '\n';
}
}
for (int i = 0; i < n; i++) {
if (fabs(g[pb][xb][i] - mb) <= 1e-6) {
cout << sb << ' ';
if (!pb)
cout << '-';
cout << i << '\n';
}
}
}
}
多项式A除以B
思路
多项式除法 A/B 的步骤
令多项式
A
A
A 的当前最高项为
k
1
∗
x
g
1
k_1*x^{g_1}
k1∗xg1,多项式
B
B
B 的最高项为
k
2
∗
x
g
2
k_2*x^{g_2}
k2∗xg2。
如果
g
1
<
g
2
g_1<g_2
g1<g2 则停止,多项式
A
A
A 剩下的项为余数。
否则,商增加的项为
k
=
k
1
k
2
∗
x
g
1
−
g
2
k = \frac{k_1}{k_2}*x^{g_1-g_2}
k=k2k1∗xg1−g2,多项式
A
A
A 减去
B
∗
k
B*k
B∗k。
例如,
A
=
2
x
3
+
3
x
2
+
4
x
+
6
,
B
=
x
+
2
A=2x^3+3x^2+4x+6,B=x+2
A=2x3+3x2+4x+6,B=x+2,
1,
A
A
A 的最高项为
2
x
3
2x^3
2x3,
k
=
2
x
3
x
=
2
x
2
k = \frac{2x^3}{x}=2x^2
k=x2x3=2x2,
A
−
B
∗
k
=
(
2
x
3
+
3
x
2
+
4
x
+
6
)
−
(
x
+
2
)
∗
(
2
x
2
)
=
−
x
2
+
4
x
+
6
A-B*k = (2x^3+3x^2+4x+6)-(x+2)*(2x^2)=-x^2+4x+6
A−B∗k=(2x3+3x2+4x+6)−(x+2)∗(2x2)=−x2+4x+6,此时的商为
2
x
2
2x^2
2x2。
2,
A
A
A 的最高项为
−
x
2
-x^2
−x2,
k
=
−
x
2
x
=
−
x
k = \frac{-x^2}{x} = -x
k=x−x2=−x,
A
−
B
∗
k
=
(
−
x
2
+
4
x
+
6
)
−
(
x
+
2
)
∗
(
−
x
)
=
6
x
+
6
A-B*k = (-x^2+4x+6)-(x+2)*(-x) = 6x+6
A−B∗k=(−x2+4x+6)−(x+2)∗(−x)=6x+6,商增加
−
x
-x
−x 变成
2
x
2
−
x
2x^2-x
2x2−x。
3,
A
A
A 的最高项为
6
x
6x
6x,
k
=
6
x
x
=
6
k = \frac{6x}{x} = 6
k=x6x=6,
A
−
B
∗
k
=
(
6
x
+
6
)
−
(
x
+
2
)
∗
(
6
)
=
−
6
A-B*k = (6x+6)-(x+2)*(6) = -6
A−B∗k=(6x+6)−(x+2)∗(6)=−6,商增加
6
6
6 变成
2
x
2
−
x
+
6
2x^2-x+6
2x2−x+6。
4,
A
A
A 的最高项为
6
6
6,指数小于
B
B
B 的最高项
x
x
x,此时的
A
=
6
A=6
A=6 为余数,商为
2
x
2
−
x
+
6
2x^2-x+6
2x2−x+6。
细节
1,余数的最高项指数要小于
B
B
B 的指数
2,题目保留系数舍入后不小于
0.1
0.1
0.1 的项,因此保留项的时候需要判断系数是否不小于
0.05
0.05
0.05。
3,
0
0
0 多项式输出
0
0
0.0
0 \ 0 \ 0.0
0 0 0.0
复杂度
令多项式 A , B A,B A,B的最高项指数为 N , M N,M N,M,最坏时间复杂度和空间复杂度均为 O ( N ∗ M ) O(N*M) O(N∗M),
代码实现
#include <bits/stdc++.h>
using namespace std;
const int N = 2877;
int n, m;
double c[N];
// c[i] 表示多项式A中指数为i的项的系数
array<int, 2> b[N];
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) {
int x, y;
cin >> x >> y;
c[x] = y;
}
cin >> m;
for (int i = 1; i <= m; i++) {
int x, y;
cin >> x >> y;
b[i] = { x, y };
}
vector<pair<int, double>> ans;
// 题目没给出数据范围,经测试,测试点的指数最大为2876
for (int i = N - 1; i >= b[1][0]; i--) {
int d = i - b[1][0];
// d 为增加的项指数
double k = c[i] / b[1][1];
// k 为增加的项的系数
// b[1][1] 为B的最高项系数
if (fabs(k) >= 0.05) {
ans.push_back({ d, k });
for (int j = 1; j <= m; j++) {
int xi = b[j][0], yi = b[j][1];
c[xi + d] -= k * yi;
}
}
}
if (!ans.size()) {
cout << "0 0 0.0";
} else {
cout << ans.size();
for (auto it : ans) {
printf(" %d %.1lf", it.first, it.second);
}
}
cout << '\n';
vector<pair<int, double>> ans1;
int f = 0;
// 注意余数的指数不应该大于除数的指数
// 因此要从除数指数的下一位b[1][0]-1开始取
// 从N-1开始取的话,会因为浮点误差导致多取
for (int i = b[1][0] - 1; i >= 0; i--) {
if (fabs(c[i]) >= 0.05) {
ans1.push_back({ i, c[i] });
}
}
if (!ans1.size()) {
cout << "0 0 0.0\n";
} else {
cout << ans1.size();
for (auto it : ans1) {
printf(" %d %.1lf", it.first, it.second);
}
}
}