2025十一集训——Day1做题
哎哎哎 CF 炸了,生气。
A Weird LCM Operations
题意:1~n 个数,\(O(n/6)\)次操作,选 \(a_i,a_j,a_k\),记 \(x=a_i,y=a_j,z=a_k\),使 \(a_i=\lcm(y,z),a_j=\lcm(x,z),a_k=\lcm(x,y)\)。让整个序列对于每个 \(i<=n\) 存在子序列的 \(gcd\) 为 \(i\)。
考虑到 \(gcd(x,y) = gcd(y,z) = gcd(x,z) = gcd(x,y,z)\) (称其为“关键性质”) 时,操作后这三个数的两两 \(gcd\) 恰为 \(x,y,z\)。
而且对于每个子序列 \(i,2*i\),gcd 为 i,所以 $1-n/2 已经有了,这样搞后面 \(n/2\),每次三个,恰好 \(n/6\)。
怎么搞?
可以从后向前做,对于当前 \(n\),若为奇数,则 \(n-2,n-1,n\) 满足“关键性质”,若为偶数但不为4的倍数,则 \(n/2,n-2,n-1\) 满足 “关键性质”,为4的倍数则 \(n/2-1,n-1,n\)。
做完了。
代码:
点击查看代码
#include <bits/stdc++.h>
#define dbg(x) cout << #x << '=' << x << endl
#define rep(i, l, r) for (int i = (l); i <= (r); i++)
#define frep(i, r, l) for (int i = (r); i >= (l); i--)
//#define int long long
using namespace std;
int n, ans;
void Print(int x, int y, int z) {
cout << x << " " << y << " " << z << "\n";
ans++;
}
void work()
{
cin >> n;
int k = n; ans = ((n + 1) / 2 + 2) / 3;
cout << ans << "\n";
if (((n + 1) / 2) % 3) {Print(1, n - 1, n); k -= 2;}
while (k * 2 > n) {
if (k % 2) Print(k - 2, k - 1, k);
else if (k % 4) Print(k / 2, k - 2, k - 1);
else Print(k / 2 - 1, k - 1, k);
k -= 3;
}
}
int main()
{
std::ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1, opinput = 1;
if (opinput) cin >> T;
while (T--) work();
return 0;
}
B 鬼街
题意:给x、y,两种操作,1.在x放一个检测器,当每个x的质因子上值的和超过y时报警,2.给每个x的质因子上的值加上y,输出这次有多少个检测器报警。
折半警报器板子。
第一次听说这种东西,好冷门。
用于维护类似于这种操作:
有一些大小为常数的集合:
1.单点加(如本题:每个质因子加上y)
2.集合查询是否超过某个值(如本题,查询质因子上值的和)
考虑若集合元素之和超过 \(x\),那么至少有一个元素超过 \(x/size\),因此可以把大报警器拆成size个小报警器,当小报警器被触发是 Check 大报警器。
接下来是关键之处,如果大报警器能触发直接ans,否则的话把集合出了被触发的这个小报警器之外的元素设一个新的报警器,记该位置值为 \(val\),大报警器值为 \(lim\),那么在其他位置设小报警器:\((lim-val)/(size-1)\) 原因同上。
这样的话平均每一次lim减了 \((size-1)/size\),复杂度就是 log 了!
实现很难,大意是每个点开一个堆存小报警器,以lim为关键字(小的lim一定先触发),修改时把能触发的弹出,找到对应的大报警器判断是否触发,没触发就加入新的报警器。
有点卡常谔谔。
(大报警器拆分小报警器的除法都是上取整)
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define dbg(x) cout << #x << '=' << x << endl
#define rep(i, l, r) for (int i = (l); i <= (r); i++)
#define frep(i, r, l) for (int i = (r); i >= (l); i--)
using namespace std;
const int N = 1e5 + 10;
int n, m;
vector<int> d[N];
int Read()
{
int f = 1, s = 0; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = s * 10 + ch - '0'; ch = getchar();}
return f * s;
}
void Init() {
rep(i, 2, n) {
int x = i;
rep(j, 2, sqrt(i)) {
int cnt = 0;
while (x % j == 0) x /= j, cnt++;
if (cnt != 0) d[i].push_back(j);
}
if (x != 1) d[i].push_back(x);
}
}
vector<int> ans;
bool vis[N];
int cntm;
struct node {
int id, tg, val;
};
bool operator<(const node& a, const node& b) {
return a.val > b.val;
}
priority_queue<node> q[N]; //小监视器
int stp[N], nop[N], cnt[N];
#define pi pair<int, int>
#define fi first
#define se second
#define mp make_pair
pair<int, int> s[N]; //大监视器
int Sum(int x) {
int sum = 0;
for (int p : d[x]) sum += cnt[p];
return sum;
}
void work()
{
n = Read(), m = Read();
Init();
int op, x, y, lst = 0;
while (m--) {
op = Read(), x = Read(), y = Read();
y ^= lst;
if (op == 0) {
for (int p : d[x]) cnt[p] += y;
for (int p : d[x]) {
while (!q[p].empty() && cnt[p] >= q[p].top().val) { // 取出超过小监视器的
int id = q[p].top().id, tg = q[p].top().tg; q[p].pop();
if (vis[id]) continue;
if (nop[id] != tg) continue;
int Id = s[id].fi, lim = s[id].se, now = Sum(Id);
if (now >= lim) ans.push_back(id), vis[id] = 1;
else {
int tag = (lim - now - 1) / d[Id].size() + 1;
nop[id]++;
for (int pp : d[Id])
q[pp].push((node){id, nop[id], tag + cnt[pp]});
}
}
}
sort(ans.begin(), ans.end());
cout << (lst = ans.size()) << " ";
for (int p : ans) cout << p << " ";
cout << "\n";
ans.clear();
}
else {
cntm++;
if (y == 0) {ans.push_back(cntm), vis[cntm] = 1; continue;}
int tag = (y - 1) / d[x].size() + 1;
nop[cntm]++;
for (int p : d[x])
q[p].push((node){cntm, nop[cntm], tag + cnt[p]});
stp[cntm] = Sum(x);
s[cntm] = make_pair(x, y + stp[cntm]);
}
}
}
signed main()
{
int T = 1, opinput = 0;
if (opinput) cin >> T;
while (T--) work();
return 0;
}
C 迷宫守卫
题意:一颗满二叉树,每个点可以先走右儿子再走左儿子或者反过来,先手可以选择一些权值之和不超过m的节点钦定只能先左儿子,后手以叶子节点权值序列字典序最小,先手想让字典序最大,问最优策略答案是?
先二分 \(ans_1\),然后令 \(f_x\) 为 x 子树内满足的最小花费,显然 \(f_x = \min(a_x, f_rs)+f_ls\),然后叶子 < ans_1就是INF,否则是0。
这样我们取到一个最大的 \(ans_1\) 显然最优了,并且得到了一个从根走到权值为ans_1的叶节点的链,那么在这个链外相当于整棵树被分成了 logn 个小二叉树,子问题同样可以这么求解。
没啥转化,直接递归暴力做就行。
实现有点难写。
PS:SB的我多测叒没清成功浪费0.5h f**k
代码:
点击查看代码
#include <bits/stdc++.h>
#define dbg(x) cout << #x << '=' << x << endl
#define rep(i, l, r) for (int i = (l); i <= (r); i++)
#define frep(i, r, l) for (int i = (r); i >= (l); i--)
#define int long long
using namespace std;
const int N = 2e5 + 10;
const int INF = 1e16;
int n, m, cnt;
int a[N], ans[N];
#define pi pair<int, int>
#define fi first
#define se second
#define mp make_pair
pi q[N];
int DP(int u, int x) {
if (u >= 1 << n) return (a[u] >= x) ? 0 : INF;
return min(a[u], DP(u << 1 | 1, x)) + DP(u << 1, x);
}
void Dfs(int u, int h) {
if (u >= 1 << n) {
if (a[u] < h) m -= a[u >> 1];
ans[++cnt] = a[u]; return;
}
int ul = u, ur = u, ncnt = 0;
while (ul < (1 << n)) ul = ul << 1;
while (ur < (1 << n)) ur = ur << 1 | 1;
rep(i, ul, ur) q[++ncnt] = mp(a[i], i);
sort(q + 1, q + ncnt + 1);
int l = 1, r = ncnt, p = 0, val = 0;
while (l <= r) {
int mid = l + r >> 1;
int v = DP(u, max(q[mid].fi, h));
if (u > 1 && q[mid].fi < h) v = min(v, DP(u, q[mid].fi) + a[u >> 1]);
if (v > m) r = mid - 1;
else l = mid + 1, p = q[mid].se, val = v;
}
m -= val;
ans[++cnt] = a[p];
h = a[p];
do {
m += min(p & 1 ? INF: a[p >> 1], DP(p ^ 1, h));
Dfs(p ^ 1, h), p >>= 1;
} while (p != u);
}
void work()
{
cin >> n >> m; cnt = 0;
rep(i, 1, (1 << n + 1) - 1) cin >> a[i];
Dfs(1, 0);
rep(i, 1, cnt) cout << ans[i] << ' ';
cout << "\n";
}
signed main()
{
std::ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1, opinput = 1;
if (opinput) cin >> T;
while (T--) work();
return 0;
}
D 春节十二响
(Day -1 过的qwq)
题意:一棵树,节点有点权,将节点分成任意个集合,要求每个集合内的节点不是祖先——后代关系,问(每个集合中的(点的最大值)之和)最小是多少。
很妙的启发式合并。
考虑如果这是个链而且根左右两条小链,显然可以将两边的链各扔进一个堆,每次取堆首,并加上两者较大值(感性理解,用一边最大的消耗另一边的最大,是贡献最少)。
然后扩展到树上,每个点开一个堆,这就叫树上启发式合并。
现在还不行,因为这东西会被卡成 \(O(n^2\log n)\)。
然后才是启发式的常用 trik:
将子树大小小的往大的里插。
这东西就类似于树剖那个重儿子,直接把 \(O(n^2)\) 变成 \(O(n\log n)\)。
而且代码只需加一行:
点击查看代码
if (q[id[u]].size() < q[id[v]].size()) swap(id[u], id[v]);
然后得到了启发式合并。
哦哦下标那里有个 trik,是说我们要交换两个堆,直接换是 \(O(\text {size})\) 的,但是可以每个点存个对应堆的下标,然后直接换下标……
做完了……
点击查看代码
#include <bits/stdc++.h>
#define dbg(x) cout << #x << '=' << x << endl
#define rep(i, l, r) for (int i = (l); i <= (r); i++)
#define frep(i, r, l) for (int i = (r); i >= (l); i--)
#define int long long
using namespace std;
const int N = 2e5 + 10;
int n, cnt;
int a[N], id[N];
vector<int> e[N];
priority_queue<int> q[N];
int b[N], pos;
int Get(int x) {
int p = q[x].top();
q[x].pop();
return p;
}
void Dfs(int u) {
id[u] = ++cnt;
int ax, ay;
for (int v : e[u]) {
Dfs(v);
if (q[id[u]].size() < q[id[v]].size()) swap(id[u], id[v]);
pos = 0;
while (!q[id[v]].empty()) b[++pos] = max(Get(id[u]), Get(id[v]));
rep(j, 1, pos) q[id[u]].push(b[j]);
}
q[id[u]].push(a[u]);
}
void work()
{
cin >> n;
rep(i, 1, n) cin >> a[i];
rep(i, 2, n) {
int f; cin >> f;
e[f].push_back(i);
}
Dfs(1);
int sum = 0;
while (!q[id[1]].empty()) sum += Get(id[1]);
cout << sum << "\n";
}
signed main()
{
std::ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1, opinput = 0;
if (opinput) cin >> T;
while (T--) work();
return 0;
}
其实没什么大结论的题,感觉比较板……
希望能场切 这种吧。
E
咕咕咕
F
咕咕咕
G
咕咕咕
H
咕咕咕
I
咕咕咕
本文来自博客园,作者:zhangxiao666,转载请注明原文链接:https://www.cnblogs.com/zhangxiao666qwq/p/19123148