目录
- ~~本次比赛前两题似乎没有数据,所以代码可能有隐藏的错误~~
- TODO 如果用时间的话,准备自己造一些数据测一下。
- 1、题目名称:小张的手速大比拼
- 题目
- 答案
- 启发式合并
- 另一种思路(非正解,未经充分测试)
- 2、题目名称:坐公交
- 题目
- 答案
- 3、题目名称:三而竭
- 题目
- 答案
- 4、题目名称:争风吃醋的豚鼠
- 题目
- 答案
本次比赛前两题似乎没有数据,所以代码可能有隐藏的错误
TODO 如果用时间的话,准备自己造一些数据测一下。
1、题目名称:小张的手速大比拼
题目
在很久很久以前,小张找到了一颗有 N 个节点的有根树 T 。 树上的节点编号在 1 到 N 范围内。 他很快发现树上的每个节点 i 都有一个对应的整数值 V[i]。 一个老爷爷对他说,给你一个整数 X, 如果你能回答我的 M 个问题,他就给张浩扬购买一些零食。 对于每个问题 Q[i], 请你找到 在 T 中以节点 Q[i] 为根的子树中的所有节点(包括 Q[i])中, 有没有两个节点 A, B (A != B) 的值 V[A] ^ V[B] 的异或和为 X。 如果有这样的两个节点, 请你输出 YES。 否则你需要输出 NO 表示没有节点符合上面的条件。
答案
启发式合并
关于启发式合并,可以参考: OI Wiki 启发式合并
对于每个子树,以大小最大的子树为基础,每次将根节点或其它较小子树合并进去。
子树 YES,必然有当前树 YES。
不太确定复杂度怎么算,以下不一定对……
大概是考虑所有值被加入集合(哈希表)的总次数,每次O(1)。
对于任意一个节点,它的重子树不需要再次被加入哈希表,其他子树需要再次加入哈希表,最坏情况子树节点数一样大,累计O((子树个数-1)*单个子树节点数)
=O(总节点数-单个子树节点数)
,全加起来是O(总结点数+总结点数*(1-1/子树个数)+总结点数*(1-1/子树个数)^2+...)
= O(总结点数*[1+(1-1/子树个数)+(1-1/子树个数)^2+...])
。
假设树有h层,每层k个分支,则有1+k+k^2+...+k^(h-1) = n = (k^h-1)/(k-1) < k^h
。
O(n*[1+((k-1)/k)+((k-1)/k)^2+...+((k-1)/k)^(h-1)])
= O(n*[k^h-(k-1)^h]/[k^(h-1)])
= O(n)
#include<bits/stdc++.h>using namespace std;
const int N = 101000;
int n, x, m;
vector<int> e[N];
int value[N];
int sz[N], son[N];void dfs(int u) {sz[u] = 1;for (const auto &v: e[u]) {dfs(v);sz[u] += sz[v];if (son[u] == 0 || sz[son[u]] < sz[v]) {son[u] = v;}}
}bool ans[N];unordered_set<int> work(int u) {if (!son[u]) {return {value[u]};}auto now = work(son[u]);if (ans[son[u]]) {ans[u] = true;}if (now.count(value[u] ^ x)) {ans[u] = true;}now.insert(value[u]);unordered_set<int> tmp;for (const auto &v: e[u]) {if (v == son[u])continue;tmp = work(v);if (ans[v]) {ans[u] = true;}if (!ans[u]) {for (const auto &it: tmp) {if (now.count(it ^ x)) {ans[u] = true;break;}now.insert(it);}}}return now;
}int main() {cin >> n >> x >> m;int rt = -1;for (int i = 1, fa; i <= n; i++) {scanf("%d", &fa);if (fa == -1) {rt = i;} else {e[fa].push_back(i);}}for (int i = 1; i <= n; i++)scanf("%d", &value[i]);dfs(rt);work(rt);int q;for (int i = 1; i <= m; i++) {scanf("%d", &q);puts(ans[q] ? "YES" : "NO");}return 0;
}
另一种思路(非正解,未经充分测试)
树中两个节点的值异或为x,导致包含它们的最近公共祖先的子树返回YES。
所以先标记最近公共祖先,再dfs2标记最近公共祖先直到根节点。
但是如果整个树的节点值只有两种,时间复杂度O(n^2)
#include<bits/stdc++.h>using namespace std;const int N = 101000;
int n, x, m;
vector<int> e[N];
int dep[N], f[N][20];
unordered_map<int, vector<int>> mp;
bool ans[N];void dfs(int u) {for (int i = 1; i < 20; i++) {f[u][i] = f[f[u][i - 1]][i - 1];}for (const auto &v: e[u]) {dep[v] = dep[u] + 1;f[v][0] = u;dfs(v);}
}int getLCA(int u, int v) {if (dep[u] < dep[v]) {swap(u, v);}for (int i = 19; i >= 0; i--) {if (dep[f[u][i]] >= dep[v]) {u = f[u][i];}}if (u == v) {return u;}for (int i = 19; i >= 0; i--) {if (f[u][i] != f[v][i]) {u = f[u][i];v = f[v][i];}}return f[u][0];
}void dfs2(int u) {for (const auto &v: e[u]) {dfs2(v);ans[u] |= ans[v];}
}int main() {cin >> n >> x >> m;int rt = -1;for (int i = 1, fa; i <= n; i++) {scanf("%d", &fa);if (fa == -1) {rt = i;} else {e[fa].push_back(i);}}dep[rt] = 1;dfs(rt);for (int i = 1, value; i <= n; i++) {scanf("%d", &value);const auto &vList = mp[value ^ x];for (const auto &v: vList) {ans[getLCA(i, v)] = true;}mp[value].push_back(i);}dfs2(rt);int q;for (int i = 1; i <= m; i++) {scanf("%d", &q);puts(ans[q] ? "YES" : "NO");}return 0;
}
/*不是比赛时的用例
5 3 5
-1 1 1 3 3
1 2 1 2 1
1 2 3 4 5*/
2、题目名称:坐公交
题目
公交上有N排凳子,每排有两个凳子,每一排的凳子宽度不一样。有一些内向和外向的人按照顺序上车。 外向的人(0):只会选择没人的一排坐下,如果有很多排符合要求,他会选择座位宽度最小的坐下。 内向的人(1):只会选择有人的一排坐下,如果有很多排符合要求,他会选择座位宽度最大的坐下。 数据保证存在合理。输出每个人所在的排。
答案
题面应该是把内向外向说反了,明明内向才自己坐……
间接排序得到大小序号。
外向找没人的坐,会按顺序坐,坐完后内向的就可以坐了,所以将大小序号入堆。
内向找有人的最大的坐,也就是将堆顶弹出。O( nlogn )
#include<bits/stdc++.h>using namespace std;
const int N = 101000;
int n;
int len[N], r[N];
char s[N * 2];int main() {cin >> n;for (int i = 1; i <= n; i++) {scanf("%d", &len[i]);r[i] = i;}sort(r + 1, r + n + 1, [](int a, int b) {return len[a] < len[b];});
//for(int i=1;i<=n;i++){
// cout<<i<<" : "<<r[i]<<endl;
// }scanf("%s", s + 1);int pos = 0;priority_queue<int> q;for (int i = 1; i <= n * 2; i++) {if (s[i] == '0') {++pos;printf("%d ", r[pos]);q.push(pos);} else {int now = q.top();q.pop();printf("%d ", r[now]);}}return 0;
}
3、题目名称:三而竭
题目
一鼓作气再而衰三而竭。 小艺总是喜欢把任务分开做。 小艺接到一个任务,任务的总任务量是n。 第一天小艺能完成x份任务。 第二天能完成x/k。 。。。 第t天能完成x/(k^(t-1))。 小艺想知道自己第一天至少完成多少才能完成最后的任务。
答案
印象中见过完全一样的题……
份数是整数,相当于每次取整。
第1天x,可以表示为x=a1+a2k+…+ank^(n-1) (0<=ai<k)
第2天 a2+a3k+…+ank^(n-2)
…
第n天an
总数a1+a2*(k+1)+a3*(k ^2+k+1)+…+an*(k ^(n-1)+…+1) >= n
可以对n取模,每次模(k ^(n-1)+…+1) (k ^(n-2)+…+1) … (k+1) (1)
可以求出an…a1
代入x=a1+a2k+…+ank^(n-1)
可以求出答案。
#include <iostream>
#include <string>
#include <sstream>
#include <vector>using namespace std;
using LL = long long;
const int N = 1010;
LL pk[N];
LL sum[N];int main() {LL n, k;cin >> n >> k;pk[0] = 1;sum[0] = pk[0];for (int i = 1; i < N; i++) {pk[i] = pk[i - 1] * k;sum[i] = sum[i - 1] + pk[i];}int pos = 0;for (; n / sum[pos] >= k; pos++) {}LL ans = 0;for (int i = pos; i >= 0; i--) {ans += n / sum[i] * pk[i];n %= sum[i];}cout << ans << endl;return 0;
}
4、题目名称:争风吃醋的豚鼠
题目
N个节点两两建边。 不存在3个节点相互之前全部相连。(3个节点连接成环) 最多能建立多少条边?
答案
二分图性质。
一开始画来画去想了半天
#include <iostream>
#include <string>
#include <sstream>
#include <vector>using namespace std;
using LL = long long;int main() {LL n;cin >> n;cout << (n / 2) * ((n + 1) / 2) << endl;return 0;
}