M
签到。
勾股定理列出来然后观察即可,最后答案是
K
签到,直接输出那个玩意就行。
I
题意等价于求二分答案的时间复杂度,如果n是2的次幂,答案就是__lg(n);如果不是的话,就要多搜索一次。
如何高效判断 n 是否是 2 的次幂? 可以采用
if ((n & (n - 1)) != 0) ans++;由于 & 的优先级较低,这里的括号应当是一个都不能少的,可以自行尝试。
D
建图之后dfs。
dfs 的时候要注意如何高效识别 当前的节点 cur 是哪条路过来的,可以考虑建立一个前驱数组,也可以直接在 dfs 的参数上加上。
最后查询二进制 1 的数量,可以用 __builtin_popcount 做到 o1 查询。
void solve()
{
int n, q;
cin >> n >> q;
vector<int> a(n + 1);
vector<vector<int>> adj(n + 1, vector<int>());
vector<int> ans(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
adj[u].emplace_back(v);
adj[v].emplace_back(u);
}
vector<int> pre(n + 1, 0); // 记录前驱节点
ans[1] = a[1];
function<void(int)> dfs = [&](int cur)
{
for (auto nxt : adj[cur])
{
if (nxt == pre[cur])
// 如果 nxt 是 cur 的前驱节点 说明绕回去了,不选
{
continue;
}
pre[nxt] = cur;
ans[nxt] = a[nxt] & ans[cur];
dfs(nxt);
}
};
dfs(1);
map<int, int> anss;
for (int i = 1; i <= n; i++)
{
auto cur = __builtin_popcount(ans[i]);
anss[cur]++;
}
while (q--)
{
int op;
cin >> op;
cout << anss[op] << endl;
}
}A
我们要计算 1~n 的 所有 约数和。
什么是约数呢,比方说 n = 6 那么它的约数就有 1, 2, 3, 6 。
可以看出来,约数指的是能整除n的数字。
那么 对于数字2,从1到6到底有多少数字能被2整除呢?
能被2整除的数字 一定可以写成2k的形式,那也就是找1到6有多少个2k 问题转化为计算k的最大值
显然在这个例子里, 。
因此 我们得到一个小结论,计算 1 ~ n 有多少个数字整除 i 只需要计算 即可。
那么这道题我们就成功的转化为
int ans = 0;
for (int i = 1; i <= n; i++) ans += n / i;但是 这样做是很有可能超时的,因此我们有必要学习数论分块。
数论分块,也称整除分块,是一种 用来解决形如 的问题 的优化技巧。
观察一个重要性质:对于固定的 n , 的取值是有限的。
关键定理 :对于正整数 n , 最多只有 种不同的取值。
在一个 相等 的区间内,我们显然有
那么我们如果已经知道一个区间的左端点是 也就可以通过这个式子 计算出 区间的右端点
特别注意的是,这里的除法都是整除,不要觉得好看就轻易化简。
在计算出区间左右端点之后,我们累计答案,然后更新左端点,再次计算即可。
void solve()
{
int n;
cin >> n;
int ans = 0;
int l = 1, r;
while (l <= n)
{
r = n / (n / l);
auto tot = r - l + 1; // 有多少个数字 满足 n/i 不变
ans += tot * (n / l);
l = r + 1;
}
cout << ans;
}或者我们也可以精简一点,写成
void solve()
{
int n;
cin >> n;
int ans = 0;
for (int l = 1, r; l <= n; l = r + 1)
{
r = n / (n / l);
ans += (r - l + 1) * (n / l);
}
cout << ans;
}问题得到解决。