『模拟赛题解』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)\),问有多少个序列满足以下条件:

  1. 序列是递增的。
  2. 所有数字属于区间 \([A,B]\)(包括 \(A\)\(B\))。
  3. 序列中的所有数字两两互质。

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\),有选与不选的两种选择:

  1. 选择:从最大的数为 \(i - 1\),状态为 \(s\) 且不包含 \(i\) 的质因子的状态做转移(即从 \(s\) 的补集的所有子集转移)
  2. 不选择:直接从最大的数为 \(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;
}
posted @ 2023-09-26 19:37  Clyfort  阅读(91)  评论(0编辑  收藏  举报