当前位置: 首页 > news >正文

Say 题选记(9.21 - 9.27)

P2048 [NOI2010] 超级钢琴

如何求长度在 \([L,R]\) 的子串中,子串和前 \(k\) 大的那些。
首先显然可以转化为前缀和。考虑 \(k = 1\) 的情况,把以 \(i(1 \le i \le n)\) 为右端点,\(j \in [i - R + 1, i - L + 1]\) 为左端点中最大的字串再求一遍最大值即可。这个用 st 表可以做。然后 \(k > 1\) 的思路类似这题。都是开一个优先队列,每次取最大的,然后进行分裂,把新的可能的最大的推进去,进行 \(k\) 遍即可。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef tuple<int, int, int, int, int> tpi;
const int N = 500005;
int a[N], n, sum[N], st[N][20], k, L, R;
int get(int x, int y){return sum[x] < sum[y] ? x : y;
}
int query(int l, int r){if(l > r) return 1e9;int d = __lg(r - l + 1);return get(st[l][d], st[r - (1 << d) + 1][d]);
}
priority_queue<tpi> q;
int main(){cin.tie(nullptr)->sync_with_stdio(0);cin >> n >> k >> L >> R;for(int i = 1; i <= n; ++i) cin >> a[i], sum[i] = sum[i - 1] + a[i], st[i][0] = i;for(int j = 1; (1 << j) <= n; ++j){for(int i = 0; i + (1 << j) - 1 <= n; ++i){st[i][j] = get(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);}}for(int i = L; i <= n; ++i){int l = i - R, r = i - L;l = max(l, 0);int pos = query(l, r);q.emplace(sum[i] - sum[pos], i, pos, l, r);}ll ans = 0;while(k){auto [val, i, t, _l, _r] = q.top();q.pop();ans += val;if(_l < t){int pos = query(_l, t - 1);q.emplace(sum[i] - sum[pos], i, pos, _l, t - 1);}if(t < _r){int pos = query(t + 1, _r);q.emplace(sum[i] - sum[pos], i, pos, t + 1, _r);}--k;}cout << ans;return 0;
}

P4436 [HNOI/AHOI2018] 游戏

首先可以先把没有锁的房间并在一起看。
部分分给了一些提示,当 \(y \le x\) 恒成立时,只有可能是左边房门开右边房门,并且如果左边的房间能开锁到底右边的房间,那么右边房间能到的左边房间也能到。因此考虑记忆化。左边更新时直接从右边能到的最远的看能不能打开门锁,复杂度就是线性了。
\(y > x\) 时也是同理,也可以记忆化。难处貌似是既有 \(y \le x\) 也有 \(y > x\) 时转移顺序不知道怎么搞。如果 \(y \le x\) 就连一条 \(x + 1 \to x\) 的边,表示得先更新 \(x + 1\) 再更新 \(x\),反过来同理,那么就可以用拓扑排序钦定转移顺序。
注意细节,如果左右都可以暴力扩展时,要同时向两边扩,因为有可能右边的钥匙在向左还没扩到的地方。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
int p, n, f[N], m, e[N][2], l[N], r[N], g[N][2], odeg[N], ideg[N];
int main(){cin.tie(nullptr)->sync_with_stdio(0);cin >> n >> m >> p;for(int i = 1; i <= n; ++i) l[i] = r[i] = f[i] = i;for(int i = 1; i <= m; ++i){int u, t;cin >> u >> t;e[u][1] = t, e[u + 1][0] = t;}for(int i = 1; i <= n - 1; ++i) if(!e[i][1]) f[i + 1] = l[i + 1] = l[i];for(int i = n; i >= 2; --i) if(!e[i][0]) r[i - 1] = r[i];for(int i = 1; i <= n; ++i){// cout << f[i] << ' ' << l[i] << ' ' << r[i] << '\n';if(!e[i][1]) continue;if(e[i][1] <= i) g[f[i + 1]][odeg[f[i + 1]]++] = f[i], ++ideg[f[i]];else g[f[i]][odeg[f[i]]++] = f[i + 1], ++ideg[f[i + 1]];}queue<int> q;for(int i = 1; i <= n; ++i){if(!ideg[i] && f[i] == i) q.push(i);}while(!q.empty()){int u = q.front(); q.pop();// cout << u << ":\n";int x = u, y = u;while(1){bool fl = 0;if(x < n && e[r[f[x]]][1] >= l[u] && e[r[f[x]]][1] <= r[u]) x = r[f[x]] + 1, r[u] = r[f[x]], fl = 1;if(y > 1 && e[l[f[y]]][0] <= r[u] && e[l[f[y]]][0] >= l[u]) y = l[f[y]] - 1, l[u] = l[f[y]], fl = 1; if(!fl) break;}for(int k = 0; k < odeg[u]; ++k){// cout << g[u][k] << '\n';--ideg[g[u][k]];if(!ideg[g[u][k]]) q.push(g[u][k]);}}while(p--){int x, y;cin >> x >> y;cout << (l[f[x]] <= y && r[f[x]] >= y ? "YES" : "NO") << '\n';}return 0;
}

P4517 [JSOI2018] 防御网络

给定一颗点仙人掌,求点集的所有子集的最小斯坦纳树权值之和。

点仙人掌的性质类似于基环树。还是考虑边的贡献。如果 \((u,v)\) 是一条树边,那么贡献是 \((2^{siz_u} - 1)(2^{siz_v} - 1)\)。环上的情况相对复杂。记 \(L\) 为环上被选择的点相邻的最大值(包括最后一个和第一个的距离),那么贡献为环长 \(c\) 减去 \(L\)。那么就可以考虑枚举 \(L\),和断环为链的地方,做一遍 dp,统计方案。限制就是相邻点距离。前缀和优化,可以做到 \(O(N^3)\)

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e2 + 5, mod = 1e9 + 7;
vector<int> e[N], vcc[N];
int n, m, cnt, dfn[N], low[N], tsp, st[N], tp;
bitset<N> on;
ll pw[N], val[N], f[N][2], ans, sumf[N][2];
ll qpow(ll a, ll b){ll res = 1;for(;b; b >>= 1, (a *= a) %= mod){if(b & 1) (res *= a) %= mod;}return res;
}
void tarjan(int u){dfn[u] = low[u] = ++tsp;st[++tp] = u;for(int v : e[u]){if(!dfn[v]){tarjan(v);low[u] = min(low[u], low[v]);if(low[v] == dfn[u]){++cnt;// cout << v << ' ' << u << ' ' << cnt << '\n';while(1){vcc[cnt].emplace_back(st[tp]);if(st[tp] == v) break;--tp;}--tp, vcc[cnt].emplace_back(u);}}else low[u] = min(dfn[v], low[u]);}
}
int getsz(int u){int sz = 1;on[u] = 1;for(int v : e[u]){if(!on[v]) sz += getsz(v); }return sz;
}
int main(){cin.tie(nullptr)->sync_with_stdio(0);cin >> n >> m;pw[0] = 1;for(int i = 1; i <= n; ++i) (pw[i] = pw[i - 1] * 2) %= mod;for(int i = 1; i <= m; ++i){int u, v;cin >> u >> v;e[u].emplace_back(v);e[v].emplace_back(u);}tarjan(1);for(int i = 1; i <= cnt; ++i){on.reset();for(int u : vcc[i]) on.set(u, 1);// cout << '\n';int c = vcc[i].size();for(int j = 0; j < c; ++j){int sz = getsz(vcc[i][j]);// cout << sz << ' ';val[j + 1] = pw[sz] - 1;}// cout << '\n';if(c == 2){ (ans += val[1] * val[2]) %= mod; continue; }for(int L = 1; L < c; ++L){for(int u = 1; u <= c; ++u){sumf[u - 1][0] = sumf[u - 1][1] = 0;sumf[u][0] = f[u][0] = val[u];sumf[u][1] = f[u][1] = 0;// cout << u << '\n';for(int v = u + 1; v <= c; ++v){int l = max(v - L, u - 1), dist;f[v][0] = (val[v] * (sumf[v - 1][0] - sumf[l][0])) % mod;l = max(v - L - 1, u - 1);f[v][1] = 0;if(v - L >= u) (f[v][1] = val[v] * (f[v - L][0] + sumf[v - 1][1] - sumf[l][1])) %= mod;if((dist = c - v + u) <= L){(ans += (c - L) * f[v][1]) %= mod;if(dist == L) (ans += (c - L) * f[v][0]) %= mod;}// cout << v << ' ' << f[v][0] << ' ' << f[v][1] << '\n';(sumf[v][0] = sumf[v - 1][0] + f[v][0]) %= mod;(sumf[v][1] = sumf[v - 1][1] + f[v][1]) %= mod;}}}}// cout << ans << '\n';cout << (ans * qpow(pw[n], mod - 2)) % mod;return 0;
}

P8100 [USACO22JAN] Counting Haybales P

考虑什么情况下 \(i\) 必须放在 \(j\) 的前面。由于只有 \(|h_i - h_{i + 1}| = 1\) 时才能交换两个数,并且认为相同的数之间不能交换。那么就可以考虑对于 \(i < j \land |h_i - h_j| \ne 1\) 都连一条 \((i,j)\) 的边,表示 \(i\) 必须放在 \(j\) 的前面。对这个 DAG 进行拓扑序计数,就是答案。
但是一般 DAG 是做不了的,发现奇偶的性质,即 \(h_i\) 为奇的和 \(h_i\) 为偶的实际上已经按原序列的顺序排好了。那么只用考虑两条链之间的拓扑限制。考虑 dp,\(dp_{i,j}\) 为第一条链前 \(i\) 个,第二条链前 \(j\) 个的方案数。如果发现 \(j + 1\) 的所有入边都在 \(\le i\) 的范围内了,那么就可以转移到 \(dp_{i, j + 1}\),表示这一位放 \(j + 1\)\(i + 1\) 同理。
这种考虑限制的想法可以稍微注意。

Code
#include <bits/stdc++.h>
using namespace std;
const int N = 5e3 + 5, mod = 1e9 + 7;
int n, a[N], dp[N][N], pos[2][N], pre[N], cnt[2];
void solve(){cin >> n;cnt[0] = cnt[1] = 0;for(int i = 1; i <= n; ++i){cin >> a[i];int x = a[i] & 1;pos[x][++cnt[x]] = i;pre[i] = 0;for(int j = i - 1; j >= 1; --j){if(x != (a[j] & 1) && abs(a[i] - a[j]) != 1){pre[i] = j;break;}}}dp[0][0] = 1;for(int i = 0; i <= cnt[0]; ++i){for(int j = 0; j <= cnt[1]; ++j){if(i == 0 && j == 0) continue;dp[i][j] = 0;if(i && pre[pos[0][i]] <= pos[1][j]) (dp[i][j] += dp[i - 1][j]) %= mod;if(j && pre[pos[1][j]] <= pos[0][i]) (dp[i][j] += dp[i][j - 1]) %= mod; }}cout << dp[cnt[0]][cnt[1]] << '\n';
}
signed main(){ios::sync_with_stdio(0);cin.tie(0), cout.tie(0);int T;cin >> T;while(T--) solve();return 0;
}
http://www.hskmm.com/?act=detail&tid=17414

相关文章:

  • 9月25日
  • 3D 高斯训练速度和消耗 - MKT
  • 完整教程:【PyTorch实战:文本分类】23、BERT文本分类实战指南:从原理到PyTorch落地
  • 常见进制
  • 9.25总结
  • Day08-C:\Users\Lenovo\Desktop\note\code\JavaSE\Basic\src\com\David\array-ArrayDemo01~07
  • yolov10_float16.tflite TO yolov10_int8.tflite
  • ansible注意的和错误代码分析
  • 用 Rust 和 Tesseract OCR 识别验证码
  • 基于寄存器地址amp;标准外设库的LED流水灯
  • 用 Swift 和 Tesseract OCR 实现验证码识别
  • Rust 和 Tesseract OCR 实现验证码识别
  • AI-Powered-ToDo-List
  • Netty:完成RPC服务(实战)
  • Python 在 Web 开发中的应用与趋势
  • LLM MOE的进化之路
  • 相交链表-leetcode
  • AtCoder ARC114 总结 (A-C)
  • 告别单张保存!PPT 图片无损批量提取,这 3 种方法亲测有效!
  • ?模拟赛(2) 赛后总结
  • 日总结 8
  • 完整教程:讲一下ZooKeeper的持久化机制
  • 2025.9.25 sos dp小记
  • 英语_阅读_A farmer dream_待读
  • docker 私有仓库 harbor
  • vite+ts取别名@
  • 掌握C2重定向器:红蓝队攻防实战指南
  • 2025秋_3
  • day004
  • 软件测试团队准备解散了......