ABC331

1|0基本情况

第一次打这个,感觉跟CF有点不一样。

A题秒了。

B题就完全背包变种秒了。

C题简单模拟,秒了。

D题明显是二位前缀和,但是后面处理总感觉有点麻烦,就先调到E。

2|0D - Tile Pattern

D - Tile Pattern (atcoder.jp)

We define a function f(A,B,C,D) as follows. (Notice the presence/absence of equal sign in each inequality sign.)

  • f(A,B,C,D)= (the number of black squares among those squares (h,w) with Ah<C and Bw<D).

The sought value is f(A,B,C+1,D+1). (Note the shift by 1.)
While one can directly implement an algorithm that directly evaluates f, the implementation requires a bunch of caseworks because there are four arguments, so we try to avoid it.

What we want is the (d) part:

editorial1

By exploiting a technique on cumulative sums, we can express it as follows:

f(A,B,C,D)=(The number of black squares in (d))=(The number of black squares in (a),(b),(c),(d))(The number of black squares in (a),(b))(The number of black squares in (a),(c))+(The number of black squares in (a))=f(0,0,C,D)f(0,0,C,B)f(0,0,A,D)+f(0,0,A,B)

With this deformation, we can constrain the arguments of f so that A=B=0. That is, if we define

  • g(H,W)= (the number of black squares among those squares (h,w) with 0h<H and 0w<W),

it holds that

f(A,B,C,D)=g(C,D)g(C,B)g(A,D)+g(A,B),

so all that left is to implement g, which has only two arguments.

Now we consider how to evaluate g(H,W). A naive implementation would cost too much time, so we want to make use of the periodicity.
For example, when H=8,W=7, and N=3, one can divide the grid by period as follows:

editorial2

Since the grid has (3×3) periodic pattern, the sub-rectangles are classified into the following four types, all of which coincide with or are smaller than N×N:

  • 4 rectangles of type A whose pattern coincides with the top-left 3×3 region
  • 2 rectangles of type B whose pattern coincides with the top-left 2×3 region
  • 2 rectangles of type C whose pattern coincides with the top-left 3×1 region
  • 1 rectangles of type D whose pattern coincides with the top-left 2×1 region

This holds for a general H,W,N, so the problem can be simplified if we know the number of rectangles of the four types and their dimensions. These can be obtained as the quotients and remainders when H and W are divided by N.

Therefore, by precomputing g(i,j) for all 0iN,0jN, the problem can be solved in O(1) time per query.
The percomputation can be done by applying the technique used for the first figure again, in order to obtain the following recurrence relation:

g(i,j)=(1 if ((i1,j1) is a black square) else 0)+g(i1,j)+g(i,j1)g(i1,j1).

Thus, the precalculation can be done in a total of O(N2) time. (If i=0 or j=0, let g(i,j)=0.)
Therefore, the problem has been solved in a total of O(N2+Q) time, which is fast enough.

其实关键点就是 (1,1)(n,n) 之外的情况。

  • Sample code (C++)
int N, Q, precalc[1010][1010]; long long g(int H, int W) {//巧妙用了递归来优雅处理各种情况 if (H <= N and W <= N) return precalc[H][W]; int Hq = H / N, Hr = H % N; int Wq = W / N, Wr = W % N; long long ret = 0; ret += g(N, N) * Hq * Wq;//计算n,n ret += g(Hr, N) * Wq;//计算行数超出,列数在范围内的情况,即下侧剩余面积 ret += g(N, Wr) * Hq;//计算列数超出,行数在范围内的情况,即右侧剩余面积 ret += g(Hr, Wr);//计算行列数都超在n之外的那个小格子,即右下侧剩余面积 return ret; } long long f(int A, int B, int C, int D) { return g(C, D) - g(A, D) - g(C, B) + g(A, B); } int main() { cin >> N >> Q; vector<string> S(N); for (auto& s : S) cin >> s; for (int i = 1; i <= N; i++) { for (int j = 1; j <= N; j++) { precalc[i][j] += S[i - 1][j - 1] == 'B'; precalc[i][j] += precalc[i - 1][j]; precalc[i][j] += precalc[i][j - 1]; precalc[i][j] -= precalc[i - 1][j - 1]; } } while (Q--) { int A, B, C, D; cin >> A >> B >> C >> D; cout << f(A, B, C + 1, D + 1) << "\n"; } }

3|0E - Set Meal

E - Set Meal (atcoder.jp)

3|1我的解法

几乎就是暴力,不知道为什么能过。

后面终于分析出了时间复杂度 O(N+L)

虽然看似两重循环,但因为不合格的数对最多就 105,而一旦不失配就立马统计答案并退出循环,所以枚举完所有 ai 最多只会有 L 次失配情况,实际上不可能跑满 O(NM)

signed main() { int N, M, L; std::cin >> N >> M >> L; std::vector<std::pair<int, int>> a(N), b(M); for (int i = 0; i < N; i++) {std::cin >> a[i].first; a[i].second = i;} for (int i = 0; i < M; i++) {std::cin >> b[i].first; b[i].second = i;} std::set<std::pair<int, int>> S; for (int i = 0, x, y; i < L; i++) { std::cin >> x >> y; --x; --y; S.insert({x, y}); } std::sort(rall(b)); i64 ans = 0; //对每个主菜,肯定找对应的最大的 for (int i = 0; i < N; i++) { for (int j = 0; j < M; j++) if (not S.contains({a[i].second, b[j].second})) { ans = std::max(ans, 1LL * a[i].first + b[j].first); break ; } } std::cout << ans << '\n'; return 0; }

3|2STD​

1|0原文

  • 重复以下操作,直到找到可用的套餐:
    • 找到尚未列出的主菜和配菜中价格最高的一对(通过某种方式)。
    • 如果确实提供了这对的套餐,请打印其价格并终止整个过程。

重复操作最多L+1次。因此,一旦我们构建了上述“某种方式”的正确算法,问题就可以被快速解决。

接下来,我们描述如何快速找到价格最高的未见过的主菜和配菜对。为简单起见,我们假设b1b2bM(否则,我们可以在b按降序排序时使用索引)。然后,可以通过以下步骤获得价格最高的一对:

  • cur成为一个辅助变量,它是一个长度为N的数组。cur[i]维护着与主菜i配对的配菜的索引,初始化为cur[i] = 1
  • 准备一个优先队列Q,用于存储(成本,主菜索引)并支持检索具有最大成本的元素。
  • 对于每个i=1,2,,N,将(ai+b1,i)插入到Q中。
  • 可以按以下方式找到尚未列举的价格最高的主菜和配菜对:
    • (c,i)成为Q中的顶部元素。从Q中弹出顶部元素。具有最高价格的未见过的一对是(i,cur[i])
    • 然后,将cur[i]加1。
    • 如果cur[i]不大于M,则将(ai+bcur[i],i)插入到Q中。

该算法的合理性在于证明了Q始终存储着具有最大成本的未见过的一对。如果您了解选举系统中的D'Hondt方法,可能会更容易将其视为类似于D'Hondt方法的算法。

通过适当地实现上述算法,问题可以在总共O(L(logN+logL)+N+MlogM)的时间内解决,这足够快速。

1|0省流

就是对每个主菜维护当前最大可用的配菜,这里用一个 cur 维护(curi 为对于当前主菜的最大可用配菜),先从大到小排序好配菜,如果发现禁用就让 cur[i] + 1

而为了保证第一次找到的合法答案最优,再用大根堆维护上面的所有主菜情况。

虽然但是,正解跑的更慢。

signed main() { int N, M, L; std::cin >> N >> M >> L; std::vector<int> a(N), b(M); for (auto& x : a) {std::cin >> x;} for (auto& x : b) {std::cin >> x;} std::set<std::pair<int, int>> S; for (int i = 0, x, y; i < L; i++) { std::cin >> x >> y; --x; --y; S.emplace(x, y); } std::vector<int> ord_b(M); std::iota(all(ord_b), 0); std::sort(all(ord_b), [&](int i, int j) {return b[i] > b[j];}); std::priority_queue<std::pair<i64, int>> Q;//[该对价格,主菜编号] std::vector<int> cur(N);//维护每个主菜目前能用的最大配菜,一开始都是第0个,这里维护的序号是ord_b的 for (int i = 0; i < N; i++) {//先把每个主菜的最优状况都压入大根堆 Q.emplace(1LL * a[i] + b[ord_b[cur[i]]], i); } while (not Q.empty()) { auto[cost, i] = Q.top(); Q.pop(); int j = cur[i];//对应的最大可能配菜 if (not S.contains({i, ord_b[j]})) {//如果找到了肯定是最大的 std::cout << cost << '\n'; return 0; } cur[i] += 1; if (cur[i] != M) {Q.emplace(1LL * a[i] + b[ord_b[cur[i]]], i);} } return 0; }

4|0F - Palindrome Query

https://atcoder.jp/contests/abc331/tasks/abc331_f

单点修改,区间查询回文

线段树维护哈希经典题。

  • 维护两个哈希值 h1, h2,分别是从左往右的哈希值和从右往左的哈希值,那么判回文即 h1=h2

  • 为了方便合并:

    • 线段树维护 [hashValue,pow] 节点,其中 pow 是用来方便hash合并的:
      • 具体地说,在A在左侧,B在右侧,进行正序hash合并时,要达到结果为hashA * powB + hashB
      • 例如:A=pow(1)=10,B=pow(1234)=10000,hash(11234)=hashA×pow(B)+hashB=1×10000+1234=11234

难度都在代码实现:

constexpr int B = 5; int mod[B] = {998244353, 1000000007, 1000000009, 1000000021, 1000000033}; int base[B]; struct Hash { i64 H1, H2, pow; Hash():H1(0), H2(0), pow(1){} Hash(int V, int b):H1(V), H2(V), pow(base[b]){}//针对单个字符 }; using T = std::array<Hash, B>;//5模哈希 template<class Info> struct SegmentTree {};//略 struct Info { T hash{}; Info(){} Info(int V) { for (int i = 0; i < B; i++) {hash[i] = Hash(V, i);} } }; Info operator+(Info a, Info b) {//合并哈希值 Info c{}; for (int i = 0; i < B; i++) { c.hash[i].H1 = (a.hash[i].H1 * b.hash[i].pow + b.hash[i].H1) % mod[i]; c.hash[i].H2 = (b.hash[i].H2 * a.hash[i].pow + a.hash[i].H2) % mod[i]; c.hash[i].pow = a.hash[i].pow * b.hash[i].pow % mod[i];//base也要同步合并 } return c; } signed main() { std::mt19937_64 rng(time(0)); for (int i = 0; i < B; i++) base[i] = rng() % mod[i]; std::cin.tie(nullptr)->sync_with_stdio(false); int N, Q; std::cin >> N >> Q; std::string s; std::cin >> s; SegmentTree<Info> T(N); for (int i = 0; i < N; i++) {T.modify(i, Info(s[i]));} auto checkPalindrome = [&](int l, int r) { Info V = T.rangeQuery(l, r); for (int i = 0; i < B; i++) { if (V.hash[i].H1 != V.hash[i].H2) {return false;} } return true; }; while (Q--) { int opt; std::cin >> opt; if (opt == 2) { int l, r; std::cin >> l >> r; --l; std::cout << (checkPalindrome(l, r) ? "Yes" : "No") << '\n'; } else { int p; char c; std::cin >> p >> c; --p; T.modify(p, c); } } return 0; }

__EOF__

本文作者Kdlyh
本文链接https://www.cnblogs.com/kdlyh/p/17872891.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   加固文明幻景  阅读(140)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示