[考试记录] 2024.9.16 csp-s模拟赛30
T1 不相邻集合
服了,考场上拉一泡权值线段树,硬是没调过来。然后一下考就知道是哪的问题了,服了~
维护两个东西:一是以 为右端点的最长可重集的长度,二是以 为右端点的最长可重集长度。两者一加再减一就是答案。然后考场上愣是想不到。这玩意用权值线段树很好维护,考虑到因为是以 为右端点的最长长度就是把以 中的最长长度加一即可。
#include<bits/stdc++.h> using namespace std; constexpr int B = 1 << 25; char buf[B], *p1 = buf, *p2 = buf; #define gt() (p1==p2 && (p2=(p1=buf)+fread(buf, 1, B, stdin), p1==p2) ? EOF : *p1++) template <typename T> inline void rd(T &x){ x = 0; int f = 0; char ch = gt(); for(; !isdigit(ch); ch = gt()) f ^= ch == '-'; for(; isdigit(ch); ch = gt()) x = (x<<1) + (x<<3) + (ch^48); x = f ? -x : x; } char obuf[B], *O = obuf; #define pt(ch) (O-obuf==B && (fwrite(obuf, 1, B, stdout), O=obuf), *O++ = (ch)) template <typename T> inline void wt(T x){ if(x < 0) pt('-'), x = -x; if(x >= 10) wt(x / 10); pt(x % 10 ^ 48); } #define fw fwrite(obuf, 1, O - obuf, stdout) constexpr int N = 3e5 + 5, T = 1e6 + 5, M = 5e5 + 1; int n, x, ans, mx, rt, as1, as2; namespace ST{ int cnt, ls[T], rs[T], f[T], g[T]; inline void fadd(int &id, int l, int r, int x, int val){ if(!id) id = ++cnt; if(l == r){ f[id] = max(f[id], val); return; } int mid = (l + r) >> 1; if(x <= mid) fadd(ls[id], l, mid, x, val); else fadd(rs[id], mid+1, r, x, val); f[id] = max(f[ls[id]], f[rs[id]]); } inline void gadd(int &id, int l, int r, int x, int val){ if(!id) id = ++cnt; if(l == r){ g[id] = max(g[id], val); return; } int mid = (l + r) >> 1; if(x <= mid) gadd(ls[id], l, mid, x, val); else gadd(rs[id], mid+1, r, x, val); g[id] = max(g[ls[id]], g[rs[id]]); } inline int fquery(int &id, int l, int r, int x, int y){ if(!id || x > y) return 0; if(x <= l && r <= y) return f[id]; int mid = (l + r) >> 1, ans = 0; if(x <= mid) ans = max(ans, fquery(ls[id], l, mid, x, y)); if(y > mid) ans = max(ans, fquery(rs[id], mid+1, r, x, y)); return ans; } inline int gquery(int &id, int l, int r, int x, int y){ if(!id || x > y) return 0; if(x <= l && r <= y) return g[id]; int mid = (l + r) >> 1, ans = 0; if(x <= mid) ans = max(ans, gquery(ls[id], l, mid, x, y)); if(y > mid) ans = max(ans, gquery(rs[id], mid+1, r, x, y)); return ans; } } bitset<M> vis; int main(){ rd(n); while(n--){ rd(x); if(vis[x]){ wt(ans), pt(' '); continue; } vis[x] = 1; as1 = ST::fquery(rt, 1, M, 1, x-2) + 1; ST::fadd(rt, 1, M, x, as1); as2 = ST::gquery(rt, 1, M, x+2, M) + 1; ST::gadd(rt, 1, M, x, as2); ans = max(ans, as1 + as2 - 1); wt(ans), pt(' '); } return fw, 0; }
T2 线段树
solution1
对于 的部分,直接建树模拟即可。
solution 2
对于 的部分。考虑遍历树的每一层,发现每一层树上节点的编号是有序的,那么找出 和 包含的完整段编号即可用等差数列求和计算出贡献。对于第 层,起始编号为 ,块长为 。那么 完全包含的块的编号为:
同理 完全包含的块的编号为:
每一层完全包含的块构成等差数列,带公式即可。
solution 3
对于 的部分,机房大佬打表得出答案为 。但是由于 极大,实际上不好打(?打不了)。
正解
令 表示有 个叶子,根节点编号为 的线段树的答案。显然有 。很好理解:当根节点的编号加上 时,儿子节点加上 ,孙子加上 ……显然一次函数, 只和树的形态有关。又因为:
那就暴力拆狮子:
记搜即可。
#include<bits/stdc++.h> using namespace std; constexpr int B = 1 << 25; char buf[B], *p1 = buf, *p2 = buf; #define gt() (p1==p2 && (p2=(p1=buf)+fread(buf, 1, B, stdin), p1==p2) ? EOF : *p1++) template <typename T> inline void rd(T &x){ x = 0; int f = 0; char ch = gt(); for(; !isdigit(ch); ch = gt()) f ^= ch == '-'; for(; isdigit(ch); ch = gt()) x = (x<<1) + (x<<3) + (ch^48); x = f ? -x : x; } char obuf[B], *O = obuf; #define pt(ch) (O-obuf==B && (fwrite(obuf, 1, B, stdout), O=obuf), *O++ = (ch)) template <typename T> inline void wt(T x){ if(x < 0) pt('-'), x = -x; if(x >= 10) wt(x / 10); pt(x % 10 ^ 48); } #define fw fwrite(obuf, 1, O - obuf, stdout) #define it __int128 #define int long long #define ls (id << 1) #define rs (id << 1 | 1) constexpr int M = 1e9 + 7; int T, n, x, y; unordered_map<int, int> k, b; inline int getk(int num){ if(k.count(num)) return k[num]; return k[num] = ((it)getk(num>>1) * 2 % M + (it)getk((num+1)>>1) * 2 % M + 1) % M; } inline int getb(int num){ if(b.count(num)) return b[num]; return b[num] = ((it)getb(num>>1) + (it)getk(num>>1) + (it)getb((num+1)>>1)) % M; } inline int work(int id, int num){ return ((it)getk(num) * id % M + (it)getb(num)) % M; } inline int getans(int id, int l, int r, int x, int y){ if(x <= l && r <= y) return work(id, r-l+1); int mid = (l + r) >> 1, ans = 0; if(x <= mid) ans = ((it)ans + (it)getans(ls, l, mid, x, y)) % M; if(y > mid) ans = ((it)ans + (it)getans(rs, mid+1, r, x, y)) % M; return ans; } signed main(){ k[1] = 1, b[1] = 0; rd(T); while(T--){ rd(n), rd(x), rd(y); wt(getans(1, 1, n, x, y)); pt('\n'); } return fw, 0; }
T3 魔法师
昨天刚弄完拆绝对值的,今天弄拆最大值的。
默认法杖为 , 咒语为 。如果 ,那么有 。所以,对每个物品维护一个价值 。
那么就可以把这个 放到权值线段树上去,同时对法杖和咒语分别维护一下区间最小的 和 。再稍微 pushup 一下即可。对于删除操作,使用 mutiset 维护线段树节点即可。注:卡空。
#include<bits/stdc++.h> using namespace std; const int N = 5e5 + 100, inf = 1e8, M = 25e4; int Q, T, ans, rt; namespace ST{ int cnt, ls[N<<1], rs[N<<1], l[N<<1], r[N<<1], num[N<<1], A[N<<1][2], B[N<<1][2]; multiset<int> ta[N][2], tb[N][2]; inline void pushup(int id){ A[id][0] = min(A[ls[id]][0], A[rs[id]][0]), A[id][1] = min(A[ls[id]][1], A[rs[id]][1]); B[id][0] = min(B[ls[id]][0], B[rs[id]][0]), B[id][1] = min(B[ls[id]][1], B[rs[id]][1]); num[id] = min({num[ls[id]], num[rs[id]], A[ls[id]][1] + A[rs[id]][0], B[ls[id]][0] + B[rs[id]][1]}); } inline void build(int &id, int x, int y){ if(!id) id = ++cnt; l[id] = x, r[id] = y, num[id] = inf; A[id][0] = A[id][1] = B[id][0] = B[id][1] = inf; if(x == y){ ta[x][0].insert(inf), ta[x][1].insert(inf); tb[x][0].insert(inf), tb[x][1].insert(inf); return; } int mid = (x + y) >> 1; build(ls[id], x, mid), build(rs[id], mid+1, y); } inline void add(int id, int ps, bool k, int a, int b){ if(l[id] == r[id]){ ta[ps][k].insert(a), tb[ps][k].insert(b); num[id] = min((*ta[ps][0].begin()) + (*ta[ps][1].begin()), (*tb[ps][0].begin()) + (*tb[ps][1].begin())); A[id][k] = *ta[ps][k].begin(); B[id][k] = *tb[ps][k].begin(); return; } int mid = (l[id] + r[id]) >> 1; add(((ps <= mid) ? ls[id] : rs[id]), ps, k, a, b); pushup(id); } inline void del(int id, int ps, bool k, int a, int b){ if(l[id] == r[id]){ if(ta[ps][k].find(a) == ta[ps][k].end()) return; ta[ps][k].erase(ta[ps][k].find(a)), tb[ps][k].erase(tb[ps][k].find(b)); num[id] = min((*ta[ps][0].begin()) + (*ta[ps][1].begin()), (*tb[ps][0].begin()) + (*tb[ps][1].begin())); A[id][k] = *ta[ps][k].begin(); B[id][k] = *tb[ps][k].begin(); return; } int mid = (l[id] + r[id]) >> 1; del(((ps <= mid) ? ls[id] : rs[id]), ps, k, a, b); pushup(id); } } int main(){ // freopen("30.in", "r", stdin), freopen("out.out", "w", stdout); ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin>>Q>>T; ST::build(rt, 1, 5e5+1); for(int i=1, opt, t, a, b; i<=Q; ++i){ cin>>opt>>t>>a>>b; if(T) a ^= ans, b ^= ans; if(opt != 2) ST::add(rt, (t ? b-a+M : a-b+M), t, a, b); else ST::del(rt, (t ? b-a+M : a-b+M), t, a, b); ans = ST::num[rt] >= inf ? 0 : ST::num[rt]; cout<<ans<<'\n'; } return 0; }
T4 园艺
机房大佬用拐两次的方法骗分直接AC……
30pts
很典的区间DP,设 和 分别表示拔完 的草的价值并停留在 和 。 。
100pts
上面的dp无法继续优化,考虑换一种计算贡献的形式。
考虑这么一个事,如果从 点直接前往第 株草可以获得 的贡献,对于每一株草来说,这个贡献是最基础的,不能再少了。如果刚开始时从 向左走 步,那么在 右侧的草的贡献
#include<bits/stdc++.h> using namespace std; constexpr int B = 1 << 23; char buf[B], *p1 = buf, *p2 = buf, obuf[B], *O = obuf; #define gt() (p1==p2 && (p2=(p1=buf) + fread(buf, 1, B, stdin), p1==p2) ? EOF : *p1++) template <typename T> inline void rd(T &x){ x = 0; int f = 0; char ch = gt(); for(; !isdigit(ch); ch = gt()) f ^= ch == '-'; for(; isdigit(ch); ch = gt()) x = (x<<1) + (x<<3) + (ch^48); x = f ? -x : x; } #define pt(ch) (O-obuf==B && (fwrite(obuf, 1, B, stdout), O=obuf), *O++ = (ch)) template <typename T> inline void wt(T x){ if(x < 0) pt('-'), x = -x; if(x >= 10) wt(x / 10); pt(x % 10 ^ 48); } #define fw fwrite(obuf, 1, O - obuf, stdout) #define int long long #define it __int128 #define p pair<int, int> constexpr int N = 2e6 + 5; int n, k, s[N], dp[N]; struct XiaoLe{ int tail, head; p st[N]; inline bool check(p a, p b, p c){ return ((it)(b.second - a.second) * (c.first - b.first) > (it)(c.second - b.second) * (b.first - a.first)) ? 1 : 0; } inline void push_back(p node){ while(head < tail && check(st[tail-1], st[tail], node)) --tail; st[++tail] = node; } inline void push_front(p node){ while(head < tail && check(node, st[head], st[head+1])) ++head; st[--head] = node; } inline p get_ltor(int k){ while(head < tail && (it)(st[tail].second - st[tail-1].second) > (it)k * (st[tail].first - st[tail-1].first)) --tail; return st[tail]; } inline p get_rtol(int k){ while(head < tail && (it)(st[head+1].second - st[head].second) < (it)k * (st[head+1].first - st[head].first)) ++head; return st[head]; } }xl[2]; // 0 : l->r 1 : r->l signed main(){ rd(n), rd(k); for(int i=2, a; i<=n; ++i) rd(a), s[i] = s[i-1] + a; memset(dp, 0x7f, sizeof(int) * (n+1)); dp[k] = 0; for(int i=1; i<=n; ++i) dp[k] += llabs(s[k] - s[i]); int tot = n - 1, l = k, r = k; xl[0].head = xl[1].head = k; xl[0].tail = xl[1].tail = k - 1; xl[0].push_back(p{k, dp[k] + 2*(n-k)*s[k]}); xl[1].push_front(p{k, dp[k] - 2*(k-1)*s[k]}); while(tot--){ if(l == 1 || r == n) break; p ans = xl[0].get_rtol(-2*s[l-1]); dp[l-1] = ans.second + 2*ans.first*s[l-1] - 2*n*s[l-1]; ans = xl[1].get_ltor(-2*s[r+1]); dp[r+1] = ans.second + 2*ans.first*s[r+1] - 2*s[r+1]; if(dp[l-1] <= dp[r+1]) --l, xl[1].push_front({l, dp[l] - 2*(l-1)*s[l]}); else ++r, xl[0].push_back({r, dp[r] + 2*(n-r)*s[r]}); } wt(min(dp[1], dp[n])); return fw, 0; }
本文作者:XiaoLe_MC
本文链接:https://www.cnblogs.com/xiaolemc/p/18425910
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步