2023年省选记录
Day 1 3/13
昨天晚上没睡好,今天状态很差。晚上19:15开始做题,预计做两道。
19:40开始看第一题题解。没有想到任何关键点上,我是傻逼。接下来睡了一会。20:15开始写。
就写了一道,开颓,埋了。
联合省选2022 填树
给定一棵
个点的无根树,对每个点 给定一个区间 ,求对于所有树上路径,如果将这个路径上每一个点填上其对应区间中的一个数,且最终所有数极差小于等于 ,那么共有多少种方案,以及这些方案中填上的数的总和。 数据范围:
。
可以说是我做的第二道拉格朗日插值优化DP题,就从第一步开始想歪了/kk/kk。
首先,如果已经确定路径上数的最小值,那么可以非常容易地计算出任何一个路径的权值和。具体地,暴力DP时间复杂度为
接下来我们需要考虑把
于是做完了。考虑在端点触碰到某些关键点的时候暴力重新算出下面
被卡常了,提示我们不要乱堆递归,这玩意可以直接转移而不用换根的两次dfs。
// Author: kyEEcccccc #include <bits/stdc++.h> using namespace std; using LL = long long; using ULL = unsigned long long; #define F(i, l, r) for (int i = (l); i <= (r); ++i) #define FF(i, r, l) for (int i = (r); i >= (l); --i) #define MAX(a, b) ((a) = max((a), (b))) #define MIN(a, b) ((a) = min((a), (b))) #define SZ(a) ((int)(a).size() - 1) const int N = 205, MOD = 1000000007; LL kpow(LL x, LL k = MOD - 2) { x = x % MOD; LL r = 1; while (k) { if (k & 1) r = r * x % MOD; x = x * x % MOD; k >>= 1; } return r; } int n, K; vector<int> to[N]; LL l[N], r[N], f[N], g[N], ff[N], gg[N]; void dp(int u, int par, LL L) { f[u] = 1, ff[u] = 0; for (int v : to[u]) if (v != par) dp(v, u, L), f[u] += f[v], ff[u] += ff[v]; LL ll = max(l[u], L), rr = min(r[u], L + K), x = max(rr - ll + 1, 0LL); f[u] %= MOD, ff[u] %= MOD; ff[u] = x == 0 ? 0 : (ff[u] * x % MOD + (ll + rr) * x / 2 % MOD * f[u]) % MOD; f[u] = f[u] * x % MOD; } void dp2(int u, int par, LL L) { g[u] = f[u], gg[u] = ff[u]; if (par != 0) { LL ll = max(l[u], L), rr = min(r[u], L + K); LL lp = max(l[par], L), rp = min(r[par], L + K); LL x = max(rr - ll + 1, 0LL), y = max(rp - lp + 1, 0LL); (g[u] += (g[par] - y * f[u] % MOD + MOD) * x) %= MOD; (gg[u] += (gg[par] - y * ff[u] % MOD - (rp + lp) * y / 2 % MOD * f[u] % MOD + 2 * MOD) * x) %= MOD; (gg[u] += (g[par] - y * f[u] % MOD + MOD) * ((rr + ll) * x / 2 % MOD)) %= MOD; } for (int v : to[u]) if (v != par) dp2(v, u, L); } pair<LL, LL> calc(LL L) { LL res = 0, res2 = 0; dp(1, 0, L), dp2(1, 0, L); F(i, 1, n) res += g[i], res2 += gg[i]; --K, dp(1, 0, L + 1), dp2(1, 0, L + 1), ++K; F(i, 1, n) res -= g[i], res2 -= gg[i]; res = (res % MOD + MOD) % MOD; res2 = (res2 % MOD + MOD) % MOD; LL tot = 0, tot2 = 0; F(i, 1, n) if (l[i] <= L && L <= r[i]) tot += 1, tot2 += L; tot %= MOD, tot2 %= MOD; return {((res - tot + MOD) * (MOD + 1 >> 1) % MOD + tot) % MOD, ((res2 - tot2 + MOD) * (MOD + 1 >> 1) % MOD + tot2) % MOD}; } LL inter(const vector<pair<LL, LL>> &pts, LL x) { LL ans = 0; F(i, 0, SZ(pts)) { LL t = 1; F(j, 0, SZ(pts)) if (j != i) t = t * (pts[i].first - pts[j].first) % MOD; t = kpow(t + MOD) * pts[i].second % MOD; F(j, 0, SZ(pts)) if (j != i) t = t * (x - pts[j].first) % MOD; ans += t; } return (ans % MOD + MOD) % MOD; } int main(void) { freopen("tree.in", "r", stdin); freopen("tree.out", "w", stdout); ios::sync_with_stdio(0), cin.tie(nullptr); cin >> n >> K; vector<int> imp; imp.push_back(1); imp.push_back(1000000001); F(i, 1, n) { cin >> l[i] >> r[i]; imp.push_back(l[i]); if (l[i] > K) imp.push_back(l[i] - K); imp.push_back(r[i] + 1); if (r[i] > K - 1) imp.push_back(r[i] - K + 1); } sort(imp.begin(), imp.end()); imp.resize(unique(imp.begin(), imp.end()) - imp.begin()); F(i, 1, n - 1) { int u, v; cin >> u >> v; to[u].push_back(v), to[v].push_back(u); } LL ans = 0, ans2 = 0; #if 0 F(i, 1, 200000) { auto pi = calc(i); ans += pi.first, ans2 += pi.second; } #else int mx = n + 2; F(i, 0, SZ(imp) - 1) { if (imp[i + 1] <= imp[i] + mx) { F(j, imp[i], imp[i + 1] - 1) { auto pi = calc(j); ans += pi.first, ans2 += pi.second; } continue; } vector<pair<LL, LL>> pts, pts2; LL sum = 0, sum2 = 0; F(j, imp[i], imp[i] + mx) { auto pi = calc(j); (sum += pi.first) %= MOD, (sum2 += pi.second) %= MOD; pts.emplace_back(j, sum), pts2.emplace_back(j, sum2); } ans += inter(pts, imp[i + 1] - 1), ans2 += inter(pts2, imp[i + 1] - 1); ans %= MOD, ans2 %= MOD; } #endif cout << ans % MOD << '\n' << ans2 % MOD << '\n'; return 0; }
Day 5 3/17
前几天开摆了,然后生病两天,今天接着写。叶老师说:做省选题不做FJOI,一开就是一道FJOI,还是做了……
FJOI2016 建筑师
给定一个排列,一个元素如果是前缀最大,那么可以从左边看到,如果是后缀最大,那么可以从右边看到。现有
组询问,每次给定 表示长度为 的排列,从左边和右边分别可以看到 和 个元素,求方案数。 数据范围:
。
考虑解的结构,一定是以最大的数为分水岭,左边有
启示我们观察出一些限制以后,先满足比较复杂的限制,再去解决比较简单的限制。比如这道题中,选择最大元和排列合法段都是比较容易满足的,计算也很方便,但是分配其它数比较麻烦,我们考虑先分配完其他数以后,就可以直接确定排列方案数。
// Author: kyEEcccccc #include <bits/stdc++.h> using namespace std; using LL = long long; using ULL = unsigned long long; #define F(i, l, r) for (int i = (l); i <= (r); ++i) #define FF(i, r, l) for (int i = (r); i >= (l); --i) #define MAX(a, b) ((a) = max((a), (b))) #define MIN(a, b) ((a) = min((a), (b))) #define SZ(a) ((int)((a).size()) - 1) const int MOD = 1000000007; LL stir1[50005][205], C[205][205]; signed main(void) { // freopen(".in", "r", stdin); // freopen(".out", "w", stdout); ios::sync_with_stdio(0), cin.tie(nullptr); stir1[0][0] = 1; F(i, 1, 50000) F(j, 1, 200) stir1[i][j] = (stir1[i - 1][j - 1] + stir1[i - 1][j] * (i - 1)) % MOD; F(i, 0, 200) C[i][0] = 1; F(i, 1, 200) F(j, 1, 200) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD; int _; cin >> _; while (_--) { int n, a, b; cin >> n >> a >> b; if (a + b - 2 > n - 1) { cout << "0\n"; continue; } cout << stir1[n - 1][a + b - 2] * C[a + b - 2][a - 1] % MOD << '\n'; } return 0; }
联合省选2022 学术社区
一个学术社区内有
个人,有 条消息。一个消息有唯一一个发出者,它可能是学术消息,或者“某人楼上”或者“某人楼下”。每个人至少发出一条学术消息。现在要求重新排列消息,使得“某人楼上”,或“某人楼下”类消息中符合实际的消息数目最多。输出答案并构造方案。 数据范围:
。 特殊性质:A 没有楼上型消息;B 存在一种方案使得所有楼上楼下消息都是合法的;C 不存在两条消息,使得在只有它们两个构成的排列中,两者均符合实际。
观察题目:楼上楼下型的消息可以转化为一系列规则,形如:如果消息
这个东西如果在 DAG 上,看起来很像最小路径覆盖。先考虑直接按照 DAG 最小路径覆盖做会怎样,也即拆点建立二分图跑最大匹配。我们会得到一些路径和一些环。环需要断开一条边才能真正合法,容易想直接断开得到答案。然而,也许存在这样一种情况,一条路径开头是
考虑这个环对应到题目中,是一连串楼上还是一连串楼下,显然二者对称,考虑楼下型。找到任意一条边断开,然后寻找路径拼接,可以理解为找到一个信息,发送它的人是链尾的楼上对象。后一种信息如果是链头对应的人的那条学术信息,那么就非常合适,因为它自生没有需求。然而,这一个学术信息可能已经有人想要了,也就是有某个信息形如这个类型楼下。对于这种情况,易得我们直接把原先的那个信息从这个学术信息上断开,接到当前环的链头,然后把环拆开的链尾接到学术信息上就可以解决。
接下来只要跑一个二分图匹配。注意到边数很多,这可以通过建立一些人对应的点来解决,网络还是单位网络,Dinic可以做到
代码已咕。
Day 6 3/18
晚上开始发烧。
联合省选2022 卡牌
给定
张卡牌,每张卡牌上写有一个正整数 ;给出 次询问,每次询问给出 个质数 ,求有多少种 的子集,使得其乘积可以被给出的所有质数整除。输出时对 取模。 数据范围:
。
值域很小,只涉及素因子,第一反应就是根号分治。
接下来考虑一下前面的
这题比较套路。我卡住的点是2000的平方根以内的素数不会太多。给我的启示是:一定要牢记自己脑子不好,该算和写的东西不要毛估估。
// Author: kyEEcccccc #include <bits/stdc++.h> using namespace std; using LL = long long; using ULL = unsigned long long; #define F(i, l, r) for (int i = (l); i <= (r); ++i) #define FF(i, r, l) for (int i = (r); i >= (l); --i) #define MAX(a, b) ((a) = max(a, b)) #define MIN(a, b) ((a) = min(a, b)) #define SZ(a) ((int)((a).size()) - 1) const int N = 1000005, M = 1505, MOD = 998244353; const int PR[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41}, TP = 13; int n, m, a[N]; vector<int> pr; int mnp[2005]; map<int, int> rk, rrk; int tot[300][1 << 13], tot0[1 << 13]; LL pw2[N]; signed main(void) { freopen("card.in", "r", stdin); freopen("card.out", "w", stdout); ios::sync_with_stdio(0), cin.tie(nullptr); cin >> n; F(i, 1, n) cin >> a[i]; F(i, 2, 2000) { if (mnp[i] == 0) mnp[i] = i, pr.push_back(i); for (auto p : pr) { if (p * i > 2000) break; mnp[i * p] = p; if (p == mnp[i]) break; } } vector<int> tmp; for (auto pp : pr) if (pp > 41) tmp.push_back(pp), rk[pp] = tmp.size() - 1; pr.swap(tmp); // cerr << pr.size() << '\n'; F(i, 1, n) { int w = 0; F(j, 0, TP - 1) while (a[i] % PR[j] == 0) a[i] /= PR[j], w |= 1 << j; if (a[i] != 1) { int bel = rk[a[i]]; tot[bel][(1 << TP) - w - 1] += 1; } tot0[(1 << TP) - w - 1] += 1; } F(k, 0, TP - 1) for (int j = 0; j < (1 << TP); j += 1 << k + 1) F(t, 0, (1 << k) - 1) tot0[j + t] += tot0[j + t + (1 << k)]; F(i, 0, SZ(pr)) F(k, 0, TP - 1) for (int j = 0; j < (1 << TP); j += 1 << k + 1) F(t, 0, (1 << k) - 1) tot[i][j + t] += tot[i][j + t + (1 << k)]; cin >> m; vector<int> p, t; F(i, 0, TP - 1) rrk[PR[i]] = i; pw2[0] = 1; F(i, 1, n) pw2[i] = pw2[i - 1] * 2 % MOD; F(_, 1, m) { int c; cin >> c; p.clear(); t.clear(); F(i, 0, c - 1) { int x; cin >> x; if (x <= 41) t.push_back(rrk[x]); else p.push_back(rk[x]); } LL ans = 0; F(ww, 0, (1 << t.size()) - 1) { int w = 0; F(i, 0, SZ(t)) if (ww >> i & 1) w |= 1 << t[i]; LL res = 1; int tt = 0; F(i, 0, SZ(p)) res = res * (pw2[tot[p[i]][w]] - 1) % MOD, tt += tot[p[i]][w]; res = res * pw2[tot0[w] - tt] % MOD; if (__builtin_parity(ww)) ans -= res; else ans += res; } cout << (ans % MOD + MOD) % MOD << '\n'; } return 0; }
Day 7 3/18
上午还在发烧,在宿舍呆了一天。
Day 8 3/19
在宿舍呆了一天
Day 9 3/20
今天不能再不做题了,但是还是头疼。
联合省选2022 序列变换
括号序题目,可以考虑括号树。考虑第一种操作,本质是将后一个括号的子树全部塞到前一个括号下面,然后把它本身也接到前一个括号下面。第二种操作则告诉我们子树可以交换,也就是说子树的顺序不重要。最终目的是把括号树变成一条链。
分三种情况讨论。如果
如果
对题目中的操作建立一个好一点的直觉,不要先入为主地死磕一个转化,有时候需要忘掉自己先前的假设,去看一看题目里的操作实际上是什么,有什么条件,哪些条件比较松等等。总之就是建立好的直觉。
// Author: kyEEcccccc #include <bits/stdc++.h> using namespace std; using LL = long long; using ULL = unsigned long long; #define F(i, l, r) for (int i = (l); i <= (r); ++i) #define FF(i, r, l) for (int i = (r); i >= (l); --i) #define MAX(a, b) ((a) = max(a, b)) #define MIN(a, b) ((a) = min(a, b)) #define SZ(a) ((int)((a).size()) - 1) const int N = 400005; int n, A, B, w[N]; string str; struct Node { LL w; int par; vector<int> sub; } btr[N]; int tot_btr; vector<LL> layer[N]; void dfs(int u, int k) { layer[k].push_back(btr[u].w); for (int v : btr[u].sub) dfs(v, k + 1); } LL work(int st) { LL ans = 0; multiset<LL> s; F(t, st, n - 1) { for (LL w : layer[t]) s.insert(w); ans += (s.size() - 2) * *s.begin() + *prev(s.end(), 2); s.erase(prev(s.end(), 2)); } assert(s.size() == 1); return ans; } signed main(void) { // freopen("bracket.in", "r", stdin); // freopen("bracket.out", "w", stdout); ios::sync_with_stdio(0), cin.tie(nullptr); cin >> n >> A >> B >> str; F(i, 1, n) cin >> w[i]; int cur_node = 1, tot_left = 0; btr[1].w = 0; tot_btr = 1; F(i, 0, n * 2 - 1) { if (str[i] == '(') { btr[cur_node].sub.push_back(++tot_btr); btr[tot_btr].par = cur_node; btr[tot_btr].w = w[++tot_left]; cur_node = tot_btr; } else cur_node = btr[cur_node].par; } dfs(1, 0); if (A == 0 && B == 0) cout << "0\n"; else if (A == 1 && B == 1) { LL ans = 0, sum = 0; multiset<LL> s; F(t, 1, n - 1) { for (LL w : layer[t]) sum += w, s.insert(w); LL mn = *s.begin(), mx = *prev(s.end()); ans += sum + mn * ((int)s.size() - 2); sum -= mx; s.erase(prev(s.end())); } cout << ans << '\n'; } else if (A == 0 && B == 1) { LL ans = 0, sum = 0; multiset<LL> s; F(t, 1, n - 1) { for (LL w : layer[t]) sum += w, s.insert(w); LL mx = *prev(s.end()); ans += sum - mx; sum -= mx; s.erase(prev(s.end())); } cout << ans << '\n'; } else { LL sum = 0, mn = numeric_limits<LL>::max(), mx = numeric_limits<LL>::min(); int tot = 0; bool worked = 0; F(t, 1, n - 1) { tot += layer[t].size(); if (tot == 1) { --tot; continue; } if (tot > 2) { if (mx == numeric_limits<LL>::min()) cout << work(t) << '\n'; else { layer[t].push_back(mn); LL ans = sum - mn + work(t); layer[t].pop_back(); layer[t].push_back(mx); MIN(ans, sum - mx + work(t)); cout << ans << '\n'; } worked = 1; break; } for (LL w : layer[t]) sum += w, MAX(mx, w), MIN(mn, w); --tot; } if (!worked) { if (mn == numeric_limits<LL>::max()) cout << "0\n"; else cout << sum - mx << '\n'; } } return 0; }
联合省选2021B 取模
我,真是个大笨蛋啊。
首先考虑枚举模数,其它数全部取个模,然后答案要么是最大的两个数相加减掉模数,要不然就枚举小的那个数,双指针枚举最大的第二个数使得两数之和小于模数,这样是
接下来考虑一个愚蠢无比的优化:从大到小枚举模数,如果答案比当前模数大,就直接结束。这样,你变成
啊?这是为啥?我不理解啊?别急。考虑 枚举模数时 先将
一个暴力剪枝看起来很难卡的时候,它有可能就是对的。证明比猜测难,顺推比证明难,而你是个大笨蛋。
// Author: kyEEcccccc #include <bits/stdc++.h> using namespace std; using LL = long long; using ULL = unsigned long long; #define F(i, l, r) for (int i = (l); i <= (r); ++i) #define FF(i, r, l) for (int i = (r); i >= (l); --i) #define MAX(a, b) ((a) = max(a, b)) #define MIN(a, b) ((a) = min(a, b)) #define SZ(a) ((int)((a).size()) - 1) const int N = 200005; int n, a[N], b[N]; signed main(void) { // freopen(".in", "r", stdin); // freopen(".out", "w", stdout); ios::sync_with_stdio(0), cin.tie(nullptr); cin >> n; F(i, 1, n) cin >> a[i]; sort(a + 1, a + n + 1); int ans = INT_MIN; FF(k, n, 1) { if (k != n && a[k] == a[k + 1]) continue; if (a[k] <= ans) break; F(i, 1, n) b[i] = a[i] % a[k]; sort(b + 1, b + n + 1); MAX(ans, b[n] + b[n - 1] - a[k]); int p = n; F(i, 2, n) { while (p > 1 && b[i] + b[p] >= a[k]) --p; if (p == 1) break; MAX(ans, b[i] + b[p]); } } cout << ans << '\n'; return 0; }
Day 10-12 3/21-23
先补一下一些先前的代码,以及一些赛题。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具