『模拟赛题解』9.25 NOIP 模拟赛
9.25 NOIP 模拟赛
T1. 序列
Description
小 Y 酷爱的接龙游戏正是这样。玩腻了成语接龙之后,小 Y 决定尝试无平方因子二元合数接龙,规则如下:
现有 \(n\) 个不超过 \(10^6\) 的合数,每个均可表示为 \(a = p \times q\)( \(p, q\) 为两个互异素数)。
若 \(a = p_1 \times q_1(p_1 < q_1), b = p_2 \times q_2(p_2 < q_2)\),当且仅当 \(p_2 = q_1\) 时 \(b\) 能接在 \(a\) 后面。
请问从给定的这 \(n\) 个数中选数接龙,最长可以形成一个包含多少数的接龙序列。
Solution
考场思路
如下。
正解
先将 \(1 \sim 10^6\) 以内的质数全部处理出来。
对于每个合数 \(x\),都处理出 \(x = p_x \times q_x\)。
定义 \(f_x\) 为从第 \(x\) 开始的最长的接龙序列,则 \(f_x = \max \{f_{q_i} + 1, f_x\}\)。
怎么处理 \(f_{q_i}\) 呢?只需要用二分去 DFS
就行了。
Code
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6;
int n, cnt;
int p[maxn + 5], fp[maxn + 5], f[maxn + 5];
bool vis[maxn + 5];
struct node
{
int p, q;
friend bool operator < (node a, node b)
{
return (a.p == b.p) ? (a.q < b.q) : (a.p < b.p);
}
} d[maxn];
void prime()
{
for (int i = 2; i <= maxn; i ++)
{
if (!vis[i])
p[++ cnt] = i;
for (int j = 1; j <= cnt && p[j] * i <= maxn; j ++)
{
vis[i * p[j]] = 1;
if (i % p[j] == 0)
break;
}
}
}
int getf(int x)
{
if (f[x])
return f[x];
int pos = lower_bound(fp + 1, fp + n + 1, x) - fp;
for (int i = pos; i <= n; i ++)
{
if (d[i].p > x)
break;
f[x] = max(getf(d[i].q) + 1, f[x]);
}
return f[x];
}
int main()
{
freopen("chain.in", "r", stdin);
freopen("chain.out", "w", stdout);
cin >> n;
prime();
for (int i = 1; i <= n; i ++)
{
int x;
cin >> x;
for (int j = 1; j <= cnt; j ++)
{
if (x % p[j] == 0)
{
d[i].p = p[j];
d[i].q = x / p[j];
fp[i] = d[i].p;
break;
}
}
}
sort (d + 1, d + n + 1);
sort (fp + 1, fp + n + 1);
for (int i = 1; i <= n; i ++)
f[d[i].p] = max(getf(d[i].q) + 1, f[d[i].p]);
int ans = 0;
for (int i = 1; i <= maxn; i ++)
ans = max(ans, f[i]);
cout << ans;
return 0;
}
T2. 生成最小树
Description
你有一个含 \(n\) 个点,\(m\) 条边的图。
现在选出一棵生成树,你每次可以选择树上一条边使其边权 \(-1\),问至少需要操作多少次之后这棵树会成为图的最小生成树?
保证图完全连通且不含重边。
注: 图中节点编号从 \(1\) 到 \(n\)。
Solution
考场思路
没写。
正解
对于一条非树边 \((x, y)\),可以对应到生成树上的一条路径 \((x \to y)\)。
不难发现,如果 \(w_{x, y} < \max \{w_{(x\to y)}\}\),那么去掉这条最大边,而加上 \((x, y)\),显然新的生成树的权值和会更小。
即实际上,我们需要修改生成树上的边,使得生成树上的每条路径 \((x \to y)\) 都小于非树边 \((x,y)\)。
但如果用暴力更新,复杂度会到达 \(O(n^2)\),显然超时。
想到如果对非树边进行从小到大的排序,那么如果某条生成树上的边已经被修改,则后面不会被修改了。
Code
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int n, m, cnt;
long long ans;
int head[maxn], d[maxn], fa[maxn], v[maxn];
map < pair <int, int>, int > p;
struct Edge
{
int to, nxt, w;
} edge[maxn];
struct node
{
int u, v, w;
bool f;
friend bool operator < (node x, node y)
{
if (x.f == y.f)
return x.w < y.w;
return x.f > y.f;
}
} edge2[maxn];
void addedge(int x, int y, int w)
{
edge[++ cnt].to = y;
edge[cnt].w = w;
edge[cnt].nxt = head[x];
head[x] = cnt;
}
void dfs(int x, int f)
{
d[x] = d[f] + 1;
fa[x] = f;
for (int i = head[x]; i; i = edge[i].nxt)
{
int y = edge[i].to;
if (y == f)
continue;
v[y] = edge[i].w;
dfs(y, x);
}
}
void getans(int x, int w)
{
if (w < v[x])
{
ans += (long long)v[x] - w;
v[x] = w;
}
}
int solve(int x, int y, int w)
{
if (x == y)
return (x + y) / 2;
int k;
if (d[x] == d[y])
{
getans(x, w);
getans(y, w);
k = solve(fa[x], fa[y], w);
}
else if (d[x] > d[y])
{
getans(x, w);
k = solve(fa[x], y, w);
}
else
{
getans(y, w);
k = solve(x, fa[y], w);
}
if (x != k)
fa[x] = k;
if (y != k)
fa[y] = k;
return k;
}
int main()
{
freopen("mintree.in", "r", stdin);
freopen("mintree.out", "w", stdout);
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i ++)
{
int u, v, w;
scanf("%d%d%d", &edge2[i].u, &edge2[i].v, &edge2[i].w);
if (edge2[i].u > edge2[i].v)
swap(edge2[i].u, edge2[i].v);
p[{edge2[i].u, edge2[i].v}] = i;
}
for (int i = 1; i < n; i ++)
{
int x, y;
scanf("%d%d", &x, &y);
if (x > y)
swap(x, y);
edge2[p[{x, y}]].f = 1;
}
sort (edge2 + 1, edge2 + m + 1);
for (int i = 1; i < n; i ++)
{
addedge(edge2[i].u, edge2[i].v, edge2[i].w);
addedge(edge2[i].v, edge2[i].u, edge2[i].w);
}
d[1] = 1;
dfs(1, 0);
for (int i = n; i <= m; i ++)
solve(edge2[i].u, edge2[i].v, edge2[i].w);
printf("%lld", ans);
return 0;
}
T3. 互质序列
Description
给出两个数 \(A, B(A \le B)\),问有多少个序列满足以下条件:
- 序列是递增的。
- 所有数字属于区间 \([A,B]\)(包括 \(A\) 和 \(B\))。
- 序列中的所有数字两两互质。
Solution
考场思路
没写。
正解
我们观察到 \(1 \le A, B \le 10^{18}, B - A \le 100\),所以我们的突破口只能在 \(B-A\) 。
不难发现,
\(\because B-A \le 100 \\ \therefore A, B \ \ 之间的共同的质因子 \le 100\)
因此考虑状压 dp。但是 \(100\) 以内的质数有 \(25\) 个,\(2^{25}\) 的数组显然会炸,如何优化?发现 \(\ge 50\) 的质数并不全都需要记录状态,所以空间可以缩小至 \(2^{23}\)。
对于每次新加入的一个数 \(i\),有选与不选的两种选择:
- 选择:从最大的数为 \(i - 1\),状态为 \(s\) 且不包含 \(i\) 的质因子的状态做转移(即从 \(s\) 的补集的所有子集转移)
- 不选择:直接从最大的数为 \(i - 1\) 做转移。
Code
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1 << 23;
long long a, b, ans;
int p[25] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97};
long long f[maxn];
vector <int> v;
int cnt[25];
signed main()
{
freopen("rlprime.in", "r", stdin);
freopen("rlprime.out", "w", stdout);
cin >> a >> b;
f[0] = 1;
for (long long i = a; i <= b; i ++)
for (int j = 0; j < 25; j ++)
if (i % p[j] == 0)
cnt[j] ++;
for (int i = 0; i < 25; i ++)
if (cnt[i] > 1)
v.push_back(p[i]);
for (long long i = a; i <= b; i ++)
{
int k = 0;
for (int j = 0; j < v.size(); j ++)
{
if (i % v[j] == 0)
k |= (1 << j);
}
int k2 = ((1 << v.size()) - 1) ^ k;
for (int j = k2; j; j = (j - 1) & k2)
f[j | k] += f[j];
f[k] += f[0];
}
for (int i = 0; i < (1 << v.size()); i ++)
ans += f[i];
cout << ans - 1;
return 0;
}