CSP2022 J2 练习十四

寄的死死的,被吊着打

Problem A 可见点数

欧拉函数模板题。

结论:一个点 \((x, y)\) 是可见的当且仅当 \(x \perp y\)

证明:

假设存在一个点 \((x, y)\)\(\gcd(x, y) = g > 1\) 能被看到,那么 \((x, y)\)\(\left(\dfrac{x}{g}, \dfrac{y}{g}\right)\) 都在同一条直线上,那么 \((x, y)\) 就会被 \(\left(\dfrac{x}{g}, \dfrac{y}{g}\right)\) 挡住,就看不到,矛盾。

#include <bits/stdc++.h>
using namespace std;

#define all(v) v.begin(), v.end()
#define rall(v) v.rbegin(), v.rend()
#define sz(v) (int)v.size()
#define allarr(v, len) v.begin() + 1, v.begin() + len + 1

typedef long long LL;
typedef pair<int, int> PII;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const int N = 100005;
int primes[N], cnt;
bool st[N];
int phi[N];

void init(int n)
{
	phi[1] = 1;
	for (int i = 2; i <= n; i ++ )
	{
		if (!st[i]) primes[cnt ++ ] = i, phi[i] = i - 1;
		for (int j = 0; primes[j] * i <= n; j ++ )
		{
			st[primes[j] * i] = 1;
			if (i % primes[j] == 0)
			{
				phi[i * primes[j]] = phi[i] * primes[j];
				break;
			}
			phi[i * primes[j]] = phi[i] * (primes[j] - 1);
		}
	}
}

int main()
{
	cin.tie(nullptr) -> sync_with_stdio(false);
	int n;
	cin >> n;
	if (n == 1)
	{
		cout << 0 << "\n";
		return 0;
	}
	init(n);
	LL ans = 1;
	for (int i = 1; i < n; i ++ ) ans += 2LL * phi[i];
	cout << ans << "\n";
	return 0;
}

Problem B 射击

反悔贪心。

先按照时间进行排序,如果当前的窗户可以打破,就检查它是否优于堆顶元素(大根堆),如果可以就反悔,更新答案。

一定一定要判堆是否空,否则 RE。

#include <bits/stdc++.h>
using namespace std;

#define all(v) v.begin(), v.end()
#define rall(v) v.rbegin(), v.rend()
#define sz(v) (int)v.size()
#define allarr(v, len) v.begin() + 1, v.begin() + len + 1

typedef long long LL;
typedef pair<int, int> PII;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const int N = 200005;
int n;
PII a[N]; // first: time second: val

int main()
{
	cin.tie(nullptr) -> sync_with_stdio(false);
	cin >> n;
	for (int i = 1; i <= n; i ++ ) cin >> a[i].first >> a[i].second;
	sort(a + 1, a + n + 1);
	priority_queue<int, vector<int>, greater<int>> q;
	LL ans = 0;
	for (int i = 1; i <= n; i ++ )
	{
		if (a[i].second < 0) continue;
		if (a[i].first <= sz(q))
		{
			if (a[i].second > q.top())
			{
				ans -= q.top();
				ans += a[i].second;
				q.pop();
				q.push(a[i].second);
			}
		}
		else
		{
			ans += a[i].second;
			q.push(a[i].second);
		}
	}
	cout << ans << "\n";
	return 0;
}

Problem C 创世纪

我们按照题意,若 \(i\) 限制 \(j\),则连一条 \(i \rightarrow j\) 的边。

性质 1:入度为 \(0\) 的点一定不能被选(这个挺显然)

性质 2:同一个强连通分量内,一定可以选出 \(\left\lfloor \dfrac{d}{2} \right\rfloor\) 个元素,\(d\) 是强连通分量的大小。

那么此时我们可以进行 拓扑排序(不算严格意义上的,因为可能有环),先处理入度为 \(0\) 的所有答案,同时进行标记。

然后再遍历每个元素 \(i\),此时如果 \(i\) 没被标记过,证明 \(i\) 处在一个强连通分量中,而且其中的一个元素被标记过,所以导致 \(i\) 没被标记,dfs 处理出强连通分量的大小,更新答案。

#include <bits/stdc++.h>
using namespace std;

#define all(v) v.begin(), v.end()
#define rall(v) v.rbegin(), v.rend()
#define sz(v) (int)v.size()
#define allarr(v, len) v.begin() + 1, v.begin() + len + 1

typedef long long LL;
typedef pair<int, int> PII;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const int N = 1e6 + 5;
int n;
int to[N];
int in[N];
bool vis[N];

int main()
{
	cin.tie(nullptr) -> sync_with_stdio(false);
	cin >> n;
	for (int i = 1; i <= n; i ++ ) cin >> to[i], in[to[i]] ++ ;
	queue<int> q;
	for (int i = 1; i <= n; i ++ ) if (!in[i]) q.push(i), vis[i] = 1;
	int ans = 0;
	while (!q.empty())
	{
		int u = q.front(); q.pop();
		int v = to[u];
		if (vis[v]) continue;
		vis[v] = 1;
		-- in[to[v]], ans ++ ;
		if (!in[to[v]] && !vis[to[v]])
			q.push(to[v]), vis[to[v]] = 1;
	}
	for (int i = 1; i <= n; i ++ )
		if (!vis[i])
		{
			function<int(int)> dfs = [&](int u)
			{
				if (vis[u]) return 0;
				vis[u] = 1;
				return dfs(to[u]) + 1;
			};
			ans += dfs(i) / 2;
		}
	cout << ans << "\n";
	return 0;
}

Problem D [PA2014]Kuglarz

显然我们必须知道每一个元素 \(i\) 的奇偶性才能确定是否有球,所以有两种方式来知道 \(i\) 的奇偶性。

  • 花费 \(c_{i, i}\) 直接得知
  • 查询 \(c_{i, j}\)\(c_{i + 1, j}\)

这看起来很像一个 区间 dp,但是 \(n \le 2000\)\(\mathcal{O}(n ^ 3)\) 无法通过。

待更新

posted @ 2022-12-03 12:23  tmjyh09  阅读(29)  评论(0编辑  收藏  举报