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)\) 无法通过。
待更新