2023.7.29解题报告
2023.7.29解题报告
T1
我们把每一个一开始给定的危险的数标记一下,然后用埃氏筛的原理,把一开始危险的数的倍数都标记为危险的,然后统计一下就好了。
#include <bits/stdc++.h>
#define int long long
#define N 20000100
#define M 100010
using namespace std;
int n, m, a[N], b[M], ans;
signed main()
{
// freopen("a4.in", "r", stdin);
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= m; i ++)
scanf("%lld", &b[i]);
for(int i = 1; i <= m; i ++)
{
int xx = 1;
while(xx * b[i] <= n) a[xx * b[i]] = 1, xx ++;
}
for(int i = 1; i <= n; i ++)
if(a[i]) ans ++;
cout << ans << endl;
return 0;
}
T2
考试打的暴力。
直接把从 \(2\) 到 \(n\) 的所有父节点处理出来连边,然后倍增 LCA。
#include <bits/stdc++.h>
#define int long long
#define N 1000100
#define endl '\n'
using namespace std;
int n, m, head[N], cnt, dep[N], f[N][21];
struct sb{int u, v, next;}e[N << 1];
inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}
inline void add(int u, int v)
{
e[++ cnt] = (sb){u, v, head[u]};
head[u] = cnt;
}
inline int pd(int x)
{
if(x == 2) return 1;
for(int i = 2; i <= sqrt(x); i ++)
if(x % i == 0) return 0;
return 1;
}
inline int fid(int x)
{
for(int i = x; i >= 2; i --)
if(x % i == 0)
if(pd(i)) return (x / i);
}
inline void dfs(int x, int fa)
{
dep[x] = dep[fa] + 1;
f[x][0] = fa;
for(int i = 1; i <= 20; i ++)
f[x][i] = f[f[x][i - 1]][i - 1];
for(int i = head[x]; i; i = e[i].next)
{
int v = e[i].v;
if(v == fa) continue;
dfs(v, x);
}
}
inline int LCA(int x, int y)
{
if(dep[x] < dep[y]) swap(x, y);
int res = 0;
for(int i = 20; i >= 0; i --)
if(dep[f[x][i]] >= dep[y]) x = f[x][i], res += pow(2, i);
if(x == y) return res;
for(int i = 20; i >= 0; i --)
{
if(f[x][i] != f[y][i])
{
x = f[x][i];
y = f[y][i];
res += pow(2, i) * 2;
}
}
return res + 2;
}
signed main()
{
// freopen("b3.in", "r", stdin);
// freopen("55.out", "w", stdout);
n = read(), m = read();
// cout << "n : " << n << endl;
for(int i = 2; i <= n; i ++)
{
// cout << "cao: " << i << endl;
int j = fid(i);
// cout << "JJJ ::: " << j << endl;
add(i, j);
add(j, i);
}
dfs(1, 0);
for(int i = 1; i <= m; i ++)
{
int u = read(), v = read();
int ans = LCA(u, v);
cout << ans << endl;
}
return 0;
}
我们利用线性筛的原理,我们知道一个质数的最大质因子就是他本身,那么我们就可以直接将质数的 \(f(i)\) 给存起来,然后对于合数的情况,我们发现是被 \(pm[j]\times i\) 筛去的,那么我们就知道,当前合数的 \(f(i)=\max(f[i], pm[j])\)。
然后我们直接暴力跳求 LCA,打个表可以发现深度最大的不会超过 \(24\),所以暴力跳就很快,用倍增在预处理的时候反而会 \(\text{TLE}\)。
#include <bits/stdc++.h>
#define int long long
#define N 10001000
#define endl '\n'
using namespace std;
int n, m, f[N], cnt, pm[N], dep[N];
bitset<N> vis;
inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}
inline void init()
{
for(int i = 2; i <= n; i ++)
{
if(!vis[i]) pm[++ cnt] = i, f[i] = i, dep[i] = 2;
for(int j = 1; j <= cnt && pm[j] * i <= n; j ++)
{
int tmp = pm[j] * i;
vis[tmp] = 1;
f[tmp] = max(f[i], pm[j]);
dep[tmp] = dep[tmp / f[tmp]] + 1;
if(i % pm[j] == 0) break;
}
}
return ;
}
signed main()
{
n = read(), m = read();
dep[1] = f[1] = 1;
init();
while(m --)
{
int u = read(), v = read(), ans = 0;
ans = dep[u] + dep[v];
if(dep[u] < dep[v]) swap(u, v);
while(dep[u] != dep[v]) u = u / f[u];
while(u != v) u = u / f[u], v = v / f[v];
cout << ans - 2 * dep[u] << endl;
}
return 0;
}
T3
暴力,直接枚举 \(x,y\) 然后暴力判断,累加答案。
#include <bits/stdc++.h>
#define int long long
#define N 1000100
using namespace std;
int n, ans;
inline int fid(int x)
{
int res = 0;
for(int i = 1; i < x; i ++)
if((x + i) % (x - i) ==0) res ++;
return res;
}
signed main()
{
cin >> n;
for(int i = 2; i <= n; i ++)
{
int res = fid(i);
// cout << res << endl;
ans += res;
}
cout << ans << endl;
return 0;
}
我们要求的是:
我们设 \(t = y - x\),那么就是:
然后我们发现后面的一个循环实际上是可以直接算的,要分两种情况讨论。
\(t\) 是偶数,那么我们发现里面的都是偶数的情况,答案就是 \(\frac{2n-t}{t} - 1\),减一是因为前面的 \([1,t + 1]\) 里面有一个 \(t\) 的倍数。
\(t\) 是奇数,那么我们发现除了上面的,还需要减去偶数的倍数的情况,因为当倍数是偶数的时候是不合法的。
那么我们就直接减去 \(\frac{2n-t}{2t}\)。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, ans;
signed main()
{
cin >> n;
for(int t = 1; t < n; t ++)
{
ans += (n + n - t) / t - 1;
if(t & 1) ans -= (n + n - t) / (2 * t);
}
cout << ans << endl;
return 0;
}
T4
暴力。
我们直接枚举 \(k\) 个位置填 \([1,n]\) 的数的情况,然后直接暴力计算最小公倍数用 \(map\) 存下来,然后直接遍历一遍 \(n\) 然后输出就好了。
#include <bits/stdc++.h>
#define int long long
#define N 1000100
using namespace std;
int n, k, a[N], mp[N];
inline int LCM()
{
int last = a[1];
for(int i = 2; i <= k; i ++)
{
int gcd = __gcd(last, a[i]);
last = last * a[i] / gcd;
}
return last;
}
inline void dfs(int x)
{
if(x == k + 1)
{
int lcm = LCM();
mp[lcm] ++;
return ;
}
for(int i = 1; i <= n; i ++)
{
a[x] = i;
dfs(x + 1);
}
return ;
}
signed main()
{
cin >> n >> k;
dfs(1);
for(int i = 1; i <= n; i ++)
cout << mp[i] << " ";
return 0;
}= 1; i <= n; i ++)
cout << mp[i] << " ";
return 0;
}
首先,如果一个长为 \(k\) 的序列的最小公倍数是 \(i\),那么这个序列的所有元素都是 \(i\) 的约数。设
的约数有 \(d(i)\) 个。
填 \(k\) 个 \(i\) 的约数的方案是 \(g_{i}=d(i)^{k}\)。但如果要求最小公倍数是 \(i\),则这样任意填可能导致最小
公倍数不是 \(i\)。
设最小公倍数是 \(i\) 的序列个数是 \(f_{i}\),那么可以先求出 \(g_{i}\)(任意填 \(i\) 的约数),再减去最终最小公
倍数不是 \(i\) 的方案数,那么最小公倍数不是 \(i\),就一定是 \(i\) 的某一个约数 \(j\),且最小公倍数是
\(j\) 的数组所有元素一定是 \(i\) 的约数,也就是它一定会在 \(g_{i}\) 中被算进去。
所以 \(f_{i} = g_{i}-\sum_{j|i,j\ne i}^{}f_{j}\)。
这样暴力递推的复杂度是 \(O(n \log n)\) 的,总时间复杂度 \(O(n \log n + n \log k)\)。
#include <bits/stdc++.h>
#define int long long
#define P 998244353
#define N 10001000
using namespace std;
int n, k, d[N], f[N], g[N];
int ksm(int x, int y)
{
int res = 1;
while (y)
{
if (y & 1) res = x * res % P;
x = x * x % P, y >>= 1;
}
return res;
}
signed main()
{
cin >> n >> k;
for(int i = 1; i <= n; i ++)
for(int j = i; j <= n; j += i)
d[j] ++;
for(int i = 1; i <= n; i ++)
f[i] = g[i] = ksm(d[i], k);
for(int i = 1; i <= n; i ++)
for(int j = 2 * i; j <= n; j += i)
f[j] = ((f[j] - f[i]) % P + P) % P;
for(int i = 1; i <= n; i ++)
cout << f[i] << " ";
return 0;
}
.
本文来自博客园,作者:北烛青澜,转载请注明原文链接:https://www.cnblogs.com/Multitree/articles/17590700.html
The heart is higher than the sky, and life is thinner than paper.
浙公网安备 33010602011771号