第四届“传智杯”全国大学生IT技能大赛 民间题解
因为本人参加的是洛谷的同步赛,可能题目顺序与正式赛选手不同?
今年题目难度普遍偏低。只有 D,F 还好。
有些题就不粘代码了。
A
按题目给的公式计算即可。注意应在最后的答案中去掉小数部分。
B
按照题意模拟即可。注意答案要与 \(0\) 取 \(\max\) 。
C
按照题意模拟即可。注意应该先做乘法在做除法,或者把后面的值用 double
或 float
数据类型的变量存储,并在最后去掉小数部分。
时间复杂度 \(\mathcal O(n)\)。
D
算法一
设 \(x \oplus y = p\) ,则 \(x \oplus p = y\)。因此我们要找有多少 \(p\) 满足 \(x \oplus p < x\) 。
考虑到 \(x \oplus y\) 的值最大为 \(2^{21} - 1\) ,因此我们用线性筛筛出 \(\le 2.1 \times 10^6\) 的所有素数,然后把它们插入一个 01Trie
。
然后我们要求的答案在 01Trie
中其实是一个个字数内的数的个数的和。
在遍历 \(x\) 的每一个二进制位的过程中,设该二进制位为 \(c\),当前节点编号为 \(u\),\(tr_{u,0/1}\) 表示下一个分别是 \(0,1\) 的位置的编号。
如果 \(c=1\),那个 \(tr_{u,0}\) 内的所有子树一定合法,我们加上它子树内的数的个数,然后让 \(u \to rt_{u,1}\),即进入右子树。
如果 \(c=0\),那个子树都不能造成贡献,我们直接进入 \(tr_{u,0}\),即左子树。
如果某个非起始位置 \(u=0\) ,直接退出即可。
时间复杂度为 \(\mathcal O((Cnt + m) \log V)\),其中 \(Cnt\) 是质数个数,\(V\) 是值域。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2.1e6 + 10;
int T, x;
int ans[MAXN];
int prim[MAXN], Cnt = 0;
bool vis[MAXN], flag[MAXN];
void Init() {
int M = 2100000;
for(int i = 2; i <= M; ++i) {
if(!vis[i]) prim[++Cnt] = i;
for(int j = 1; j <= Cnt && i * prim[j] <= M; ++j) {
vis[i * prim[j]] = true;
if(i % prim[j] == 0) break;
}
}
}
namespace Trie {
int tr[MAXN << 2][2], node_num = 0, siz[MAXN << 2];
void Insert(int x) {
int u = 0;
siz[0] ++;
for(int j = 21; j >= 0; --j) {
int c = (x >> j) & 1;
if(!tr[u][c]) tr[u][c] = ++node_num;
u = tr[u][c];
siz[u] ++;
}
}
int Query(int x) {
int u = 0, res = 0;
for(int j = 21; j >= 0; --j) {
int c = (x >> j) & 1;
if(c) {
res = res + siz[tr[u][c]];
u = tr[u][c ^ 1];
} else {
u = tr[u][c];
}
if(!u) return res;
}
return res;
}
}
int main() {
Init();
for(int i = 1; i <= Cnt; ++i) {
Trie::Insert(prim[i]);
}
scanf("%d", &T);
while(T--) {
scanf("%d", &x);
int res = Trie::Query(x);
printf("%d\n", res);
}
return 0;
}
算法二
考虑上面那个问题的本质。
其实问题可以再次转化成有多少 \(p\) 满足:如果设 \(x\) 与 \(p\) 第一个不同的位置 \(i\), \(x\) 在 \(i\) 这一位上为 \(1\)。
进一步分析可以发现,如果 \(x\) 的第 \(i\) 位为 \(1\),那么所有 \(2^{i} \le p < 2^{i+1}\) 的质数都是合法的。
这其实和 \(p\) 的最高位数有关,按照这个预处理个数即可。
总复杂度 \(\mathcal O((Cnt + m) \log V)\)。
#include<bits/stdc++.h> // 这个代码虽然没交但应该没有问题
using namespace std;
const int MAXN = 2.1e6 + 10;
int T, x;
int ans[MAXN], a[200];
int prim[MAXN], Cnt = 0;
bool vis[MAXN], flag[MAXN];
int read() {
int s = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
void Init() {
int M = 2100000;
for(int i = 2; i <= M; ++i) {
if(!vis[i]) prim[++Cnt] = i;
for(int j = 1; j <= Cnt && i * prim[j] <= M; ++j) {
vis[i * prim[j]] = true;
if(i % prim[j] == 0) break;
}
}
}
int main() {
Init();
for(int i = 1; i <= Cnt; ++i) {
int x = prim[i];
int lst, cnt = 0;
while(x) {
if(x & 1) lst = cnt;
x >>= 1, ++ cnt;
}
a[lst] ++;
}
cin >> T;
while(T--) {
int Ans = 0;
cin >> x;
for(int i = 0; i <= 21; ++i)
if(x & (1 << i)) Ans += a[i];
printf("%d\n", Ans);
}
return 0;
}
E
题意可以转化成有 \(k\) 个可重集,每次向里面插入 \(p\) 个数对 \((a,b)\) ,每个数对 \(a,b\) 表示向第 \(a\) 个集合中插入一个值为 \(b\) 的数。
查询的话就是第 \(x\) 个集合中在 \([y_{\min},y_{\max}]\) 内的数有多少个。
发现 \(k,n\) 都很小,对于每个集合维护一个树状数组即可。
时间复杂度 \(\mathcal O(n \log n)\)。
#include<bits/stdc++.h>
using namespace std;
int n, k, M = 1000;
struct Bit {
int sum[1010];
int lb(int x) { return x & -x; }
void Modify(int x, int k) { for(; x <= M; x += lb(x)) sum[x] += k; }
int Query(int x) { int res = 0; for(; x; x -= lb(x)) res += sum[x]; return res; }
}bit[1010];
int main() {
cin >> n >> k;
for(int i = 1, opt, p; i <= n; ++i) {
cin >> opt >> p;
if(opt == 1) {
for(int j = 1, x, y; j <= p; ++j) {
cin >> x >> y;
bit[x].Modify(y, 1);
}
} else {
int amax, amin;
cin >> amin >> amax;
int res = bit[p].Query(amax) - bit[p].Query(amin - 1);
cout << res << "\n";
}
}
}
F
考虑利用 \(dfs\) 序转化到序列上。
考虑把深度当成主席树的值域维护,每个位置对应深度权值为 \(1\)。
操作一可以直接用一个 lst
变量记录。
操作二就可以直接查询 \([dfn_x, dfn_x + siz_x - 1]\) 这段区间中 \(dep_{pre_i} \ge lst\) 的位置有多少。
注意特判一下一开始整个树都是绿的。
时间复杂度 \(\mathcal O(n \log n)\) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
struct edge {
int to, nxt;
}e[MAXN << 1];
int head[MAXN], num_edge = 1;
int n, m;
int root[MAXN];
int siz[MAXN], dfn[MAXN], pre[MAXN], dep[MAXN], Cnt = 0;
namespace Hjt {
#define ls lson[now_]
#define rs rson[now_]
int node_num = 0, siz[MAXN << 5], lson[MAXN << 5], rson[MAXN << 5];
void Insert(int &now_, int pre_, int l, int r, int pos, int k) {
now_ = ++node_num;
siz[now_] = siz[pre_] + k;
ls = lson[pre_], rs = rson[pre_];
if(l == r) return ;
int mid = (l + r) >> 1;
if(mid >= pos) Insert(ls, lson[pre_], l, mid, pos, k);
else Insert(rs, rson[pre_], mid + 1, r, pos, k);
}
int Query(int now_, int pre_, int L, int R, int l, int r) {
if(L <= l && r <= R) return siz[now_] - siz[pre_];
int mid = (l + r) >> 1, ans = 0;
if(mid >= L) ans += Query(ls, lson[pre_], L, R, l, mid);
if(mid < R) ans += Query(rs, rson[pre_], L, R, mid + 1, r);
return ans;
}
}
void add_edge(int from, int to) { e[++num_edge] = (edge){to, head[from]}, head[from] = num_edge; }
void dfs(int u, int fa) {
dep[u] = dep[fa] + 1;
dfn[u] = ++Cnt, pre[Cnt] = u, siz[u] = 1;
for(int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to;
if(v == fa) continue;
dfs(v, u);
siz[u] += siz[v];
}
}
int main() {
cin >> n >> m;
for(int i = 1, u, v; i < n; ++i) {
cin >> u >> v;
add_edge(u, v), add_edge(v, u);
}
dfs(1, 0);
for(int i = 1; i <= n; ++i) {
Hjt::Insert(root[i], root[i - 1], 1, n + 1, dep[pre[i]], 1);
}
for(int i = 1, opt, x, lst = n + 1; i <= m; ++i) {
cin >> opt >> x;
if(opt == 1) lst = x;
else {
int res = Hjt::Query(root[dfn[x] + siz[x] - 1], root[dfn[x] - 1], lst, n + 1, 1, n + 1);
cout << res << "\n";
}
}
return 0;
}
G
注意题目要求是第 \(x,y\) 个质数。
其实是一道诈骗题。
发现如果两个数满足相异或为 \(1\) 那么他们两个数的值必定只差 \(1\),而这样的质数对只有 \((2,3)\) 这一对,特判一下即可。
时间复杂度 \(\mathcal O(T)\) 。