2024CCPC东北四省赛暨全国邀请赛VP题解
要是我还是大一新生就好了,这样就不会场场自闭了(
J.Breakfast
从未见识过的题目,简单到发昏,甚至没看见输入是固定的,直接计算即可。
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
const int N = 1e6 + 7;
const int mod = 1e9 + 7;
void solve()
{
int n,m;
cin >> n >> m;
double res = 0.6 * n + m;
cout << fixed << setprecision(2) << res << '\n';
}
D.nIM gAME
很Guess的一道题,先手必输,手玩一下发现,当进行到最后是,先手将赢的时候,后手总有办法能破坏先手的策略。
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
const int N = 1e6 + 7;
const int mod = 1e9 + 7;
void solve()
{
ll n;
cin >> n;
cout << "lose" << '\n';
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int _ = 1;
cin >> _;
while (_--)
{
solve();
}
return 0;
}
E.Checksum
赛时都过的很慢,其实很简单(
首先很容易发现,A序列的影响只与A中1的个数有关。
然后k的数据范围又很小,可以考虑枚举k,也就是B中1的个数。
这样的话总共1的个数就确定了,由于只保留k位,再此时1的总个数 mod (1<<k)
这样的话B就确定下来了,此时直接检查B是否满足要求即可
这个题我被坑的点是B可以有前导零(
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
const int N = 1e6 + 7;
const int mod = 1e9 + 7;
void solve()
{
int n, k;
cin >> n >> k;
string s;
cin >> s;
int cnt = 0;
for (auto x : s)
{
if (x == '1')
{
cnt++;
}
}
for (int i = 0; i <= k; i++)
{
int px = cnt + i;
px %= (1 << (k));
int ok = 0;
for (int j = 0; j < k; j++)
{
if (px >> j & 1)
{
ok++;
}
}
if (ok == i)
{
for (int j = k - 1; j >= 0; j--)
{
if (px >> j & 1)
{
cout << 1;
}
else
cout << 0;
}
cout << '\n';
return;
}
}
cout << "None" << '\n';
}
A.Paper Watering
很典的一道题,也十分考验细心程度(爽吃罚时
首先我们可以对一个数一直进行平方操作来产生不同的数(注意1要特判
而且只要最初的底数不同,后续不会产生相同的数字
对于开平方操作,只要被开数不是完全平方数,那么新产生的数也满足上述条件
由于一个数被开根的次数很少(log级别),所以我们直接模拟即可,在过程中统计答案
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
const int N = 1e6 + 7;
const int mod = 1e9 + 7;
void solve()
{
ll x, k;
cin >> x >> k;
ll ans = 1;
if (x != 1)
ans += k;
ll res = 0;
ll ps = k;
while (1)
{
if (ps == 0)
break;
if (x == 1)
break;
res++;
ll pre = x;
x = sqrt(x);
ps--;
if (ps == 0)
break;
ll y = x * x;
if (y != pre && x != 1)
{
res += ps;
}
}
ans += res;
cout << ans;
}
L. Bracket Generation
给人很亲切的感觉,却又无能为力(
官方题解感觉说的太抽象了(蒟蒻看不懂
这里写一个较为容易的写法
我们将第一种操作后的序列视为最基本的括号序列,可以发现,
只要满足第二种操作内序列已被构造出来的情况,我们就可以在剩余的操作中任选一次进行该操作
对于多重嵌套的序列也一样。
这样我们可以进行一次括号匹配,来判断每次操作二有几种选择,时间复杂度O(n)
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
const int N = 1e3 + 7;
const int mod = 998244353;
const double eps = 1e-9;
void solve()
{
string s;
cin >> s;
stack<int> st;
vector<int> v;
for (int i = 0; i < s.size(); i++)
{
if (s[i] == '(')
{
st.push(i);
}
else
{
if (st.top() == i - 1)
{
v.push_back(1);
}
else
v.push_back(2);
st.pop();
}
}
reverse(v.begin(), v.end());
ll ans = 1, cnt = 0;
for (int i = 0; i < v.size(); i++)
{
cnt++;
if (v[i] == 2)
{
ans = ans * cnt % mod;
}
}
cout << ans << '\n';
}
M.House
vp时被此题折磨2小时无果下班(不要碰几何!!!
教训是不要再用解析式的方式(中学思维)写算法题,不然被精度和码量双重折磨,害
这里建议使用官方题解的方式,配合叉积真的很好写(叉积早用早爽
(n^2)复杂度枚举射线,对于位于射线左侧(叉积<0)的判断能否成为房顶
对于另一侧的判断能否构成底部(使用map维护距离,点积判断垂直
最后相乘即可得到贡献。
总复杂度O(n^3 * log n)
而且有个很方便的地方是直接使用int64即可,无需考虑精度问题(win
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
const int N = 1e3 + 7;
const int mod = 998244353;
struct Point
{
ll x, y;
ll operator^(Point b) const
{
return x * b.y - y * b.x;
}
ll operator*(Point b) const
{
return x * b.x + y * b.y;
}
} p[N];
int n;
Point operator-(Point x, const Point &y)
{
x.x -= y.x, x.y -= y.y;
return x;
}
ll dis(Point a, Point b)
{
return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
}
ll solve(Point A, Point B)
{
Point V = (B - A);
ll L = 0, R = 0;
map<ll, int> mp1, mp2;
for (int i = 1; i <= n; i++)
{
Point M = (p[i] - A);
if ((V ^ M) == 0)
continue;
if ((V ^ M) < 0)
{
if (dis(p[i], A) == dis(p[i], B))
{
L++;
}
}
else
{
Point Q = p[i] - B, P = p[i] - A;
if (Q * V == 0)
{
mp2[dis(p[i], B)]++;
}
if (V * P == 0)
{
mp1[dis(p[i], A)]++;
}
}
}
// cout << L << '\n';
for (auto [x, y] : mp1)
{
if (mp2[x])
{
R++;
}
}
// cout << L << ' ' << R << '\n';
return L * R;
}
void solve()
{
// int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> p[i].x >> p[i].y;
}
ll ans = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (i == j)
continue;
ans += solve(p[i], p[j]);
}
}
cout << ans << '\n';
}
I.Password
不是哥们,正解真1e8啊(
题目要求需要保证每一个数都存在于一个连续的排列当中,问方案数,考虑dp求解
令f[i]表示以i结尾刚好构成一个新的1-k的排列,最后答案就是f[n]
考虑转移,对于新加入的j个数字,使得构成以i结尾的排列,
令加入j个数字新产生的方案数为g[j],则\(f[i] = \sum^k_{j=1}{f[i-j] * g[j]}\)
考虑如何求解g[i],因为只有添加完i个数字之后才可以构成新排列,所以在添加完\([1,i)\)个数字的过程中
不能构成新排列,我们可以容斥
总的方案数有\(i!\),我们可以枚举断点j,使得添加完第j个不构成新排列,方案数是\(j!\),此时注意,后面的
\([j,i]\)添加后要构成新排列(满足g的定义),所以要再乘上\(g[i-j]\)
则\(g[i] = i! - \sum_{j=1}^{i-1}{j! * g[i-j]}\)
注意初始化\(f[k] = k!\)
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
const int mod = 998244353;
const int N = 1e5 + 7;
ll jc[N];
ll f[N], g[N];
void init()
{
jc[0] = 1;
for (int i = 1; i <= 100000; i++)
{
jc[i] = jc[i - 1] * i % mod;
}
}
void solve()
{
int n, k;
cin >> n >> k;
for (int i = 1; i <= k; i++)
{
g[i] = jc[i];
for (int j = 1; j < i; j++)
{
g[i] = g[i] + ((-(jc[j] * g[i - j] % mod)) % mod + mod) % mod;
g[i] %= mod;
}
}
f[k] = jc[k];
for (int i = k + 1; i <= n; i++)
{
for (int j = 1; j <= k; j++)
{
if (j > i)
break;
f[i] = f[i] + f[i - j] * g[j] % mod;
f[i] %= mod;
}
}
cout << (f[n] + mod) % mod << '\n';
}
H.Meet
使得最大值最小,二分答案,考虑如何check
这里采用官方题解的思路,感觉比较巧妙
设你二分的距离为k
枚举m个点对,如果两个点的距离小于等于k,则任意点都可以
如果距离大于k,我们不妨令1为根
对于一个点对(x,y),我们先使得x满足要求
如果x到LCA(x,y)的距离大于k,则以x的k级祖先的任意儿子为根,x所产生的贡献满足k
否则的话,设x的合法贡献为p,x与y的距离为dis,y的贡献为q,则需满足\(p+q=dis\)
因为\(p<=k\),所以\(q>=dis-k\),则y的dis - k - 1级祖先的子树上所有节点都不能作为根
y也同理
用dfs序处理上述问题则会得到若干区间,使用差分维护,最后检查是否存在交集即可成功check
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int mod = 998244353;
const int N = 1e5 + 7;
vector<pii> p;
struct HLD
{
int n;
std::vector<int> siz, top, dep, parent, in, out, seq;
std::vector<std::vector<int>> adj;
int cur;
HLD() {}
HLD(int n)
{
init(n);
}
void init(int n)
{
this->n = n;
siz.resize(n);
top.resize(n);
dep.resize(n);
parent.resize(n);
in.resize(n);
out.resize(n);
seq.resize(n);
cur = 0;
adj.assign(n, {});
}
void addEdge(int u, int v)
{
adj[u].push_back(v);
adj[v].push_back(u);
}
void work(int root = 0)
{
top[root] = root;
dep[root] = 0;
parent[root] = -1;
dfs1(root);
dfs2(root);
}
void dfs1(int u)
{
if (parent[u] != -1)
{
adj[u].erase(std::find(adj[u].begin(), adj[u].end(), parent[u]));
}
siz[u] = 1;
for (auto &v : adj[u])
{
parent[v] = u;
dep[v] = dep[u] + 1;
dfs1(v);
siz[u] += siz[v];
if (siz[v] > siz[adj[u][0]])
{
std::swap(v, adj[u][0]);
}
}
}
void dfs2(int u)
{
in[u] = cur++;
seq[in[u]] = u;
for (auto v : adj[u])
{
top[v] = v == adj[u][0] ? top[u] : v;
dfs2(v);
}
out[u] = cur;
}
int lca(int u, int v)
{
while (top[u] != top[v])
{
if (dep[top[u]] > dep[top[v]])
{
u = parent[top[u]];
}
else
{
v = parent[top[v]];
}
}
return dep[u] < dep[v] ? u : v;
}
int dist(int u, int v)
{
return dep[u] + dep[v] - 2 * dep[lca(u, v)];
}
int jump(int u, int k)
{
if (dep[u] < k)
{
return -1;
}
int d = dep[u] - k;
while (dep[top[u]] > d)
{
u = parent[top[u]];
}
return seq[in[u] - dep[u] + d];
}
};
void solve()
{
int n, m;
cin >> n >> m;
HLD s(n);
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
u--, v--;
s.addEdge(u, v);
}
for (int i = 0; i < m; i++)
{
int x, y;
cin >> x >> y;
x--, y--;
p.push_back({x, y});
}
s.work();
auto check = [&](int k)
{
vector<int> c(n + 1, 0);
for (auto [x, y] : p)
{
int dis = s.dist(x, y);
if (dis <= k)
{
c[0] += 2;
continue;
}
int fa = s.lca(x, y);
for (int j = 0; j <= 1; j++)
{
int disfa = s.dist(fa, x);
if (disfa > k)
{
int kfa = s.jump(x, k);
c[s.in[kfa]]++;
c[s.out[kfa]]--;
}
else
{
int kfa = s.jump(y, dis - k - 1);
if (kfa == -1)
return false;
c[0] += 1;
c[s.in[kfa]]--;
c[s.out[kfa]]++;
}
swap(x, y);
}
}
if (c[0] >= 2 * m)
return true;
for (int i = 1; i < n; i++)
{
c[i] += c[i - 1];
if (c[i] >= 2 * m)
{
return true;
}
}
return false;
};
int l = 0, r = n;
while (l < r)
{
int mid = (l + r) >> 1;
if (check(mid))
r = mid;
else
l = mid + 1;
}
cout << l << '\n';
}
F.Factor
本来毫无头绪,一看官方题解说直接爆搜,才1e8个答案,一下子简单起来
在k进制下使得能完整表示,那么可以直接理解为\(p*k^{+\infty} = aq(a为正整数)\)
所以对于q进行质因数分解后,里面的质因子要么是p的质因子(有最高次限制),要么是k的因子,无限制
所以我们分解完p和k后直接爆搜即可
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int mod = 998244353;
const int N = 1e5 + 7;
ll p, x, k;
vector<ll> pp, pk, cntp;
ll ans = 0;
void dfs(int ok, ll now, int id)
{
if (ok == 0 && id == pp.size())
{
dfs(1, now, 0);
return;
}
// cerr << 1 << '\n';
if (ok && id == pk.size())
{
ans++;
return;
}
if (ok == 0)
{
for (int i = 0; i <= cntp[id]; i++)
{
dfs(ok, now, id + 1);
if (now > x / pp[id])
{
return;
}
now *= pp[id];
}
}
else
{
for (int i = 0;; i++)
{
dfs(ok, now, id + 1);
if (now > x / pk[id])
{
return;
}
now *= pk[id];
}
}
}
void solve()
{
cin >> p >> x >> k;
map<ll, int> mp; // 标记k的因子;
for (int i = 2; i <= k / i; i++)
{
if (k % i == 0)
{
while (k % i == 0)
{
k /= i;
}
pk.push_back(i);
mp[i] = 1;
}
}
if (k > 1)
pk.push_back(k);
for (int i = 2; i <= p / i; i++)
{
if (p % i == 0)
{
int cnt = 0;
while (p % i == 0)
{
cnt++;
p /= i;
}
if (mp.count(i))
continue;
pp.push_back(i);
cntp.push_back(cnt);
}
}
if (p > 1 && !mp[p])
{
pp.push_back(p);
cntp.push_back(1);
}
dfs(0, 1, 0);
cout << ans << '\n';
}