2025/11/10~2025/11/16 做题笔记

2025/11/10

C. 圆环(circle)

感觉比 T2 要简单/kk

dp 状态是好想的,非常明显可以设 \(f_{i, j}\) 表示当操作完 \(i\) 操作,另一只手在 \(j\) 位置的最小代价。这个东西的状态转移方程也不难。

  1. 当由不是完成操作的手过来的时候 \(f_{i, p_{i - 1}} = \min f_{i - 1, p_{i - 1}} + Dist(p_{i - 1}, a_i)\)
  2. 当由是完成操作的手过来的时候 \(f_{i, j} = f_{i - 1, j} + Dist(a_{i - 1}, a_i)\)

显然当同一时间需要去两个位置的时候需要禁止第 2 种操作,所以其他位置的值应该设为 \(INF\)。这个东西显然是可以用线段树优化的,只需要维护 \(\min\limits_{l \leq j \leq r}f_{i, j}, \min\limits_{l \leq j \leq r}f_{i, j} - j, \min\limits_{l \leq j \leq r}f_{i, j} + j\) 这样就可以完成转移

区间赋值 tag 没清空,调了好久。标记下放一定要记得清空标记!!!

Code
#include <algorithm>
#include <iostream>

using namespace std;
using ll = long long;

const int kN = 3e5 + 1;
const ll kI = 1e15;

int c, n, m;
struct Op {
  int t, p;
} op[kN];
struct Tr {
  ll p, s, mn, add, clr;
} tr[kN * 4];
struct Info {
  ll p, s;
};
Info Min(Info x, Info y) { return {min(x.p, y.p), min(x.s, y.s)}; }

void Func(int x, ll k) { tr[x].p += k, tr[x].s += k, tr[x].mn += k, tr[x].add += k; }
void Clear(int x) { tr[x].mn = tr[x].p = tr[x].s = kI, tr[x].clr = 1, tr[x].add = 0; }
void Pushdown(int x) {
  if (tr[x].clr)
    Clear(x * 2), Clear(x * 2 + 1);
  Func(x * 2, tr[x].add), Func(x * 2 + 1, tr[x].add), tr[x].add = tr[x].clr = 0;
}
void ChkMin(int p, ll v, int x = 1, int l = 1, int r = n) {
  if (l == r) {
    v < tr[x].mn && (tr[x] = {v + l, v - l, v, 0, 0}, 0);
    return;
  }
  Pushdown(x);
  int m = (l + r) / 2;
  if (p <= m)
    ChkMin(p, v, x * 2, l, m);
  else
    ChkMin(p, v, x * 2 + 1, m + 1, r);
  tr[x].mn = min(tr[x * 2].mn, tr[x * 2 + 1].mn), tr[x].p = min(tr[x * 2].p, tr[x * 2 + 1].p), tr[x].s = min(tr[x * 2].s, tr[x * 2 + 1].s);
}
void Build(int x = 1, int l = 1, int r = n) {
  tr[x] = (l == 1 ? (Tr){1, -1, 0, 0, 0} : (Tr){kI, kI, kI, 0, 0});
  if (l == r)
    return;
  int m = (l + r) / 2;
  Build(x * 2, l, m), Build(x * 2 + 1, m + 1, r);
}
Info Query(int nl, int nr, int x = 1, int l = 1, int r = n) {
  if (nl <= l && r <= nr)
    return {tr[x].p, tr[x].s};
  Pushdown(x);
  int m = (l + r) / 2;
  if (nr <= m)
    return Query(nl, nr, x * 2, l, m);
  if (nl > m)
    return Query(nl, nr, x * 2 + 1, m + 1, r);
  return Min(Query(nl, nr, x * 2, l, m), Query(nl, nr, x * 2 + 1, m + 1, r));
}

int Dist(int l, int r) {
  (l > r) && (swap(l, r), 0);
  return min(r - l, n - r + l);
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> c >> n >> m;
  for (int i = 1; i <= m; i++)
    cin >> op[i].t >> op[i].p;
  sort(op + 1, op + m + 1, [](Op& x, Op& y) { return x.t < y.t; });
  Build(), op[0].p = n;
  for (int i = 1; i <= m; i++) {
    Info L = Query(1, op[i].p), R = Query(op[i].p, n);
    ll v = min({op[i].p + L.s, n - op[i].p + L.p, R.p - op[i].p, n + R.s + op[i].p});
    if (op[i].t == op[i - 1].t)
      Clear(1);
    else
      Func(1, Dist(op[i - 1].p, op[i].p));
    ChkMin(op[i - 1].p, v);
    // cout << tr[1].mn << '\n';
  }
  cout << tr[1].mn;
  return 0;
}

CF2056D Unique Median

一开始没有注意到 \(V \leq 10\)。。。

然后就十分简单了,显然是需要枚举值的。

首先想到 \(>v\) 和 $ < v$ 数量相等的偶长度区间是合法的,但是这并不是必要条件。于是不可行。考虑算不合法的偶长度区间。假设小的那个中位数为 \(a\),可以发现 \(\leq a\) 的数的个数恰好等于 \(\dfrac{len}{2}\)。那么不合法的偶长度区间的充要条件等价于存在一个 \(a\),使得小于等于 \(a\) 的数的个数恰好为 \(\dfrac{len}{2}\)。直接将小于等于 \(v\) 的数变成 1,然后开个桶算一下即可

Code
#include <iostream>
#include <map>

using namespace std;
using ll = long long;

const int kN = 1e5 + 1;

int T, n, a[kN];
map<int, int> mp[2], bef[2];

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> T;
  while (T--) {
    cin >> n;
    for (int i = 1; i <= n; i++)
      cin >> a[i];
    ll ans = 0;
    for (int v = 1; v <= 10; v++) {
      for (auto o : {0, 1})
        mp[o].clear(), bef[o].clear();
      bef[0][0] = 1;
      for (int i = 1, s = 0; i <= n; i++) {
        s += a[i] <= v;
        int o = i & 1;
        if (a[i] == v) {
          for (int o : {0, 1}) {
            for (auto [x, y] : bef[o])
              mp[o][x] += y;
            bef[o].clear();
          }
        }
        ans += mp[o][2 * s - i];
        bef[o][2 * s - i]++;
      }
    }
    cout << 1ll * n * (n + 1) / 2 - ans << '\n';
  }
  return 0;
}
/*
0 0 1 0 0 0 0 1 0 0
*/

2025/11/11

A. 二分图

太唐了,一开始把第二个大样例的图画错了,以为偶度数的特殊性质想错了,重新读了题也没发现怎么错了。于是跳过了

由于欧拉回路,显然一个偶度数的连通块都可有直接走完。考虑对于两个奇数点,存在一条欧拉路径能把他处理掉。但是存在一种特殊情况,就是所有连通块中的奇数点都在同侧,但是不同连通块的奇数点存在在不同侧的情况,这样就会额外消耗代价

Code
#include <algorithm>
#include <iostream>
#include <unordered_map>
#include <vector>

using namespace std;
using ull = unsigned long long;
using i28 = __int128_t;
using ll = long long;

const int kN = 2e4 + 1;

int n, m, fl, cnt, ans, v[kN];
vector<int> e[kN];

void Dfs(int x) {
  if (v[x])
    return;
  if (e[x].size() & 1)
    fl |= x <= n ? 1 : 2;
  v[x] = 1;
  for (int i : e[x])
    Dfs(i);
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m;
  for (int i = 1, x, y; i <= m; i++)
    cin >> x >> y, e[x].push_back(y + n), e[y + n].push_back(x);
  for (int i = 1; i <= 2 * n; i++)
    cnt += (e[i].size() & 1) == 1;
  ans += cnt / 2 - 1, cnt = 0;
  for (int i = 1; i <= n; i++) {
    if (!v[i] && e[i].size()) {
      fl = 0, Dfs(i);
      cnt |= fl == 3, ans += !fl;
    }
  }
  if (cnt) {
    cout << ans;
    return 0;
  }
  cnt = 0;
  for (int i = 1; i <= 2 * n; i++)
    if (e[i].size() & 1)
      cnt |= i <= n ? 1 : 2;
  cout << ans + (cnt == 3) << '\n';
  return 0;
}

B. 斜堆

好像没前面一题那么难。显然左子树一定是形如 \(k^a\),那么可以直接想到预处理 \(k^a\) 次方,那么就只会往一边走了。但是忘记了右子树也有字数大小限制,于是我以为他数量级下降很快,写了个递归斜挂了

实际上下降并不快,还需要处理左子树大小相同的情况,这种情况同意算即可

Code
#include <algorithm>
#include <cassert>
#include <iostream>
#include <unordered_map>

using namespace std;
using ull = unsigned long long;
using i28 = __int128_t;

const int kN = 51, kV = 71;
const ull kI = 1e18;

int q, m, fl, a[kN];
ull n, f[kN][kV];
i28 c[kN][kV];

int Find(int k, ull x) {
  for (int i = 1; i < kV; i++)
    if (c[k][i] + c[k][i - 1] > x)
      return i - 1;
  return -1;
}
ull Dfs(ull x, int k) {
  if (x <= 1)
    return x;
  ull i = Find(k, x - 1), t = (x - c[k][i] - c[k][i - 1] - 1) / (c[k][i] + 1) + 1, rst = x - t * (c[k][i] + 1);
  return t * f[k][i] + ull((i28)1 * t * (t + 1) / 2 * (c[k][i] + 1)) + Dfs(rst, k) + t * rst;
}
ull Calc(ull x, int k) {
  if (a[k] == 1) {
    ull h = __lg(x + 1) - 1, rst = x - ((1ll << (h + 1)) - 1), ans = 0;
    for (ull it = 0; it <= h; it++)
      ans += 1ull * (it + 1) * (1ull << it);
    return ans + 1ull * rst * (h + 2);
  }
  return Dfs(x, k);
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> m >> q;
  for (int i = 1; i <= m; i++) {
    cin >> a[i];
    if (a[i] == 1)
      continue;
    c[i][0] = 1;
    for (int j = 1; j < kV && c[i][j - 1] <= kI; j++)
      c[i][j] = c[i][j - 1] * a[i];
    for (int j = 0; j < kV && c[i][j]; j++)
      f[i][j] = Calc(c[i][j], i);
  }
  fl = 1;
  for (ull n; q--;) {
    cin >> n;
    ull ans = 0;
    for (int i = 1; i <= m; i++)
      ans += Calc(n, i);
    cout << ans << '\n';
  }
  return 0;
}

2025/11/12

爬山中、调题中、写总结中

2025/11/13

CF1228E Another Filling the Grid

md 模数不对调了我好久

考虑前 \(i\) 列有 \(j\) 行已经填过了 1。那么考虑这一行新增填 \(c\) 个 1。

特判 \(c == 0\) 的情况:因为这一列需要有至少一个 1,所以需要减掉没有 1 的情况。或者直接钦定有 \(c\) 个 1。那么剩下的 \(n - c\) 个位置可以随便选:

\[\sum f_{i - 1, j} * \tbinom{j}{c} * (k - 1)^{n - c} \]

否则,被覆盖的行可以随便选:

\[f_{i, j + c} = f_{i - 1, j} * \tbinom{n - j}{c} * k^j * (k - 1)^{n - j - c} \]

Code
#include <iostream>

using namespace std;

const int kN = 251, kM = 1e9 + 7;

int n, k, fac[kN], ifac[kN], f[kN][kN];

int Pow(int x, int y) {
  int res = 1;
  for (; y; y >>= 1, x = 1ll * x * x % kM)
    (y & 1) && (res = 1ll * res * x % kM);
  return res;
}
int C(int n, int m) {
  return 1ll * fac[n] * ifac[m] % kM * ifac[n - m] % kM;
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  fac[0] = ifac[0] = 1;
  for (int i = 1; i < kN; i++)
    fac[i] = 1ll * fac[i - 1] * i % kM, ifac[i] = Pow(fac[i], kM - 2);
  cin >> n >> k;
  f[0][0] = 1;
  for (int i = 1; i <= n; i++) {
    for (int j = 0; j <= n; j++) {
      for (int c = 1; c <= j; c++)
        f[i][j] = (f[i][j] + 1ll * f[i - 1][j] * C(j, c) % kM * Pow(k - 1, n - c) % kM) % kM;
      for (int c = 1; c <= n - j; c++)
        f[i][j + c] = (f[i][j + c] + 1ll * f[i - 1][j] * C(n - j, c) % kM * Pow(k, j) % kM * Pow(k - 1, n - j - c) % kM) % kM;
    }
  }
  cout << f[n][n];
  return 0;
}

还有更加优质的 \(n\log n\) 的做法

直接考虑容斥至少 \(i\) 列不合法。考虑计算一行符合条件的方案:不考虑一行合不合法,方案数为:\((k - 1)^i k^{n - i}\),其中 \((k - 1)^i\) 表示有 \(i\) 列不填 \(1\),剩下的可以任意填。但是现在需要让每一行都合法,那么减去 \((k - 1)^n\) 表示去掉全部不填 1 的方案数。因为有 \(n\) 行,所以乘 \(n\) 遍,再乘上一个组合系数 \(\dbinom{n}{i}\)。最终式子为

\[\sum\limits_{i = 0}^n\tbinom{n}{i}((k - 1)^i k^{n - i} - (k - 1)^n)^n \]

#include <iostream>

using namespace std;

const int kN = 1e6 + 1, kM = 1e9 + 7;

int n, k, fac[kN], ifac[kN];

int Pow(int x, int y) {
  int res = 1;
  for (; y; y >>= 1, x = 1ll * x * x % kM)
    (y & 1) && (res = 1ll * res * x % kM);
  return res;
}
int C(int n, int m) {
  return 1ll * fac[n] * ifac[m] % kM * ifac[n - m] % kM;
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  fac[0] = ifac[0] = 1;
  for (int i = 1; i < kN; i++)
    fac[i] = 1ll * fac[i - 1] * i % kM, ifac[i] = Pow(fac[i], kM - 2);
  cin >> n >> k;
  int ans = 0;
  for (int i = 0; i <= n; i++)
    ans = (ans + ((i & 1) ? -1ll : 1ll) * C(n, i) * Pow((1ll * Pow(k - 1, i) * Pow(k, n - i) % kM - Pow(k - 1, n) + kM) % kM, n) % kM + kM) % kM;
  cout << ans;
  return 0;
}

A. 矩阵操作

菜完了,做了十万年。

很显然一个点肯定优先填同行和同列的位置,并且一个点只能填右下角的区域

考虑 \(nm\) 做法。行从上往下扫和列从左往右扫本质上是相同的,因为前面的多余部分都可以用,但是负数都只能算作代价。同理从下往上扫和从右往左是一样的。

思考从左往右扫应该怎么做:其实直接把前面多余的部分拿过来用即可。从右往左扫即是把后面需要填的部分拿到现在来填

考虑 \(nm\) 做法。一行行的考虑一定是对的,因为能填到下面的部分填到同行一定不劣,而同行的填不了以后都没机会填了。发现我需要填一定是用最近的多余的去填,因为更加前面的位置能覆盖的范围更大一些。于是使用一个栈可以维护。

接下来就是卡住我 1~2h 的东西。因为每次询问不能带 \(log\),我一直在想扫描线。但是发现扫描线根本不可做后重新思考询问清空 \(l~r\) 有什么性质。其实就相当于把中间部分删掉,然后把上半部分和下半部分拼接起来。那维护一个前缀和一个后缀再拼起来就做完了

Code
#include <iostream>
#include <numeric>

using namespace std;
using ll = long long;

const int kN = 5e3 + 2, kM = 5e2 + 2;

int n, m, q, st[kN], a[kN][kN];
ll pr[kN], su[kN], p[kN][kN], s[kN][kN];

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m >> q;
  for (int i = 1; i <= n; i++)
    for (int j = 1; j <= m; j++)
      cin >> a[i][j];
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++)
      p[i][j] = a[i][j] + max(p[i - 1][j], 0ll);
    for (int j = 1, tp = 0; j <= m; j++) {
      if (p[i][j] < 0) {
        for (; tp > 0 && p[i][j] < 0; tp -= !p[i][st[tp]]) {
          ll t = p[i][st[tp]];
          p[i][st[tp]] -= min(t, -p[i][j]), p[i][j] += min(t, -p[i][j]);
        }
      } else
        st[++tp] = j;
    }
    pr[i] = pr[i - 1];
    for (int j = 1; j <= m; j++)
      pr[i] += max(-p[i][j], 0ll);
  }
  for (int i = n; i >= 1; i--) {
    for (int j = 1; j <= n; j++)
      s[i][j] = a[i][j] + min(s[i + 1][j], 0ll);
    for (int j = 1, tp = 0; j <= m; j++) {
      if (s[i][j] < 0) {
        for (; tp > 0 && s[i][j] < 0; tp -= !s[i][st[tp]]) {
          ll t = s[i][st[tp]];
          s[i][st[tp]] -= min(t, -s[i][j]), s[i][j] += min(t, -s[i][j]);
        }
      } else
        st[++tp] = j;
    }
    su[i] = su[i + 1];
    for (int j = 1; j <= m; j++)
      su[i] += max(s[i][j], 0ll);
  }
  ll cnt = 0;
  for (int i = 1; i <= m; i++)
    cnt += max(0ll, p[n][i]);
  cout << pr[n] + cnt << '\n';
  for (int l, r; q--;) {
    cin >> l >> r, l--, r++;
    ll ans = pr[l - 1] + su[r + 1], cnt[2] = {0, 0};
    for (int i = 1; i <= m; i++) {
      cnt[0] += p[l][i], cnt[1] += s[r][i];
      if (cnt[1] < 0) {
        ll t = cnt[0];
        cnt[0] -= min(t, -cnt[1]), cnt[1] += min(t, -cnt[1]);
      }
      ans += max(-cnt[0], 0ll) + max(-cnt[1], 0ll), cnt[0] = max(cnt[0], 0ll), cnt[1] = max(cnt[1], 0ll);
    }
    // cerr << ans << ' ' << cnt[0] << ' ' << cnt[1] << '\n';
    cout << ans + cnt[0] + cnt[1] << '\n';
  }
  return 0;
}

B. 无向稀疏图上更快的最长路径

可以快速发现有答案的点对最多大概在 \(O(m\log m)\) 左右,并且最长的路径顶多只有 \(\log m\) 长。但是暴力 dfs 的话枚举出边是非常容易爆炸的,考虑排序边权,如果已经乘炸了就退出。这个玄学东西竟然是对的,说明该优化的还是得优化,万一就过了呢

Code
#include <algorithm>
#include <cassert>
#include <iostream>
#include <map>
#include <queue>
#include <vector>

using namespace std;
using pii = pair<int, int>;

const int kN = 1e6 + 1;

int n, m, q;
vector<pii> e[kN];
map<int, int> mp[kN];

void Dfs(int t, int x, int s) {
  mp[t][x] = max(mp[t][x], s);
  for (pii i : e[x]) {
    if (1ll * s * i.second > m)
      break;
    Dfs(t, i.first, s * i.second);
  }
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m >> q;
  for (int i = 2, x, y; i <= m; i++)
    cin >> x >> y, e[x].push_back({y, i}), e[y].push_back({x, i});
  for (int i = 1; i <= n; i++)
    sort(e[i].begin(), e[i].end(), [](pii x, pii y) { return x.second < y.second; });
  for (int i = 1; i <= n; i++)
    Dfs(i, i, 1);
  for (int s, t; q--;) {
    cin >> s >> t;
    if (mp[s].find(t) == mp[s].end())
      cout << "-1\n";
    else
      cout << mp[s][t] << '\n';
  }
  return 0;
}

2025/11/14

P5505 [JSOI2011] 分特产

对至少有 \(i\) 个同学没有分得特产容斥。那么对于每种特产,都有 \(n - i\) 个同学分特产。于是对于每一个 \(i\),都有方案数

\[\tbinom{n}{i}\prod\limits_{j = 1}^{m}\tbinom{a_j + n - i - 1}{n - i - 1} \]

最后配一个容斥系数即可

Code
#include <iostream>

using namespace std;

const int kN = 1e4 + 1, kM = 1e9 + 7;

int n, m, ans, a[kN], fac[kN], ifac[kN];

int Pow(int x, int y) {
  int res = 1;
  for (; y; y >>= 1, x = 1ll * x * x % kM)
    (y & 1) && (res = 1ll * res * x % kM);
  return res;
}
int C(int n, int m) {
  return 1ll * fac[n] * ifac[m] % kM * ifac[n - m] % kM;
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  fac[0] = ifac[0] = 1;
  for (int i = 1; i < kN; i++)
    fac[i] = 1ll * fac[i - 1] * i % kM, ifac[i] = Pow(fac[i], kM - 2);
  cin >> n >> m;
  for (int i = 1; i <= m; i++)
    cin >> a[i];
  for (int i = 0; i <= n; i++) {
    int res = 1;
    for (int j = 1; j <= m; j++)
      res = 1ll * res * C(n + a[j] - i - 1, n - i - 1) % kM;
    ans = (ans + (i & 1 ? -1ll : 1ll) * C(n, i) * res % kM + kM) % kM;
  }
  cout << ans;
  return 0;
}

P6076 [JSOI2015] 染色问题

只有限制最多可以选择的颜色数是好算的,于是考虑计算最多选择 \(i\) 个颜色的方案数。然后这道题就变成了 CF1228E Another Filling the Grid

Code
#include <iostream>

using namespace std;

const int kN = 1e5 + 1, kM = 1e9 + 7;

int n, m, c, ans, fac[kN], ifac[kN];

int Pow(int x, int y) {
  int res = 1;
  for (; y; y >>= 1, x = 1ll * x * x % kM)
    (y & 1) && (res = 1ll * res * x % kM);
  return res;
}
int C(int n, int m) {
  return 1ll * fac[n] * ifac[m] % kM * ifac[n - m] % kM;
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  fac[0] = ifac[0] = 1;
  for (int i = 1; i < kN; i++)
    fac[i] = 1ll * fac[i - 1] * i % kM, ifac[i] = Pow(fac[i], kM - 2);
  cin >> n >> m >> c;
  for (int i = 0; i <= c; i++) {
    int res = 0;
    for (int j = 0; j <= n; j++)
      res = (res + (j & 1 ? -1ll : 1ll) * C(n, j) * Pow((Pow(i + 1, n - j) - 1 + kM) % kM, m) % kM + kM) % kM;
    ans = (ans + ((c - i) & 1 ? -1ll : 1ll) * C(c, i) * res % kM + kM) % kM;
  }
  cout << ans;
  return 0;
}

2025/11/15

A. 诅咒

糖丸了真是。有一个显而易见的贪心是选择最小的数然后乘 \(k\)。等到最小的数乘 \(k\) 大于最大的数的时候就可以把剩余的次数均摊到每一个数字身上了。打比赛的时候没想明白这个东西是对的,害得我又想了好久其他的东西

Code
#include <cmath>
#include <iostream>
#include <set>

using namespace std;
using i28 = __int128_t;

const int kN = 2e5 + 1, kM = 1e9 + 7;

int n, m, k, fl, ans, v[kN];
multiset<i28> st;

int Pow(int x, int y) {
  int res = 1;
  for (; y; y >>= 1, x = 1ll * x * x % kM)
    (y & 1) && (res = 1ll * res * x % kM);
  return res;
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m >> k;
  for (int i = 1, x; i <= n; i++)
    cin >> x, st.insert(x);
  if (n == 1) {
    cout << int(*st.begin() * Pow(k, m) % kM);
    return 0;
  }
  if (*st.begin() == 0 || k == 1) {
    for (auto it : st)
      ans = (ans + it) % kM;
    cout << ans;
  return 0;
  }
  for (; *st.begin() * k <= *st.rbegin() && m; m--) {
    i28 t = *st.begin();
    st.erase(st.begin());
    st.insert(t * k);
  }
  int cnt = 0;
  for (auto it : st) {
    cnt++;
    // cerr << (int)it << ' ';
    ans = (ans + it % kM * Pow(k, m / n + (cnt <= m % n)) % kM) % kM;
  }
  cout << ans;
  return 0;
}

B. 食物

dp 状态是显然的,设 \(f_{i, j}\) 表以 \(i\) 为起点,最多走 \(j\) 步能获得的最大权值。考虑 dp 转移,首先全部向左和全部向右是简单的,前缀和即可。考虑怎么处理往左走再拐(往右走再拐同理)的方案。注意到相当于 \(f_{i - 1, j}\),因为它包含了可能为最大区间的答案,并且恰好多走一步。滚动数组优化空间即可

Code
#include <iostream>

using namespace std;
using ll = long long;

const int kN = 8001;

int n;
ll ans, s[kN], fl[kN], fr[kN], res[kN];

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> n;
  for (int i = 1; i <= n; i++)
    cin >> s[i], s[i] += s[i - 1];
  auto Calc = [](int l, int r) { return s[r] - s[l - 1]; };
  for (int j = 1; j <= 2 * n; j++) {
    for (int i = n; i >= 1; i--)
      fr[i] = max(fr[i - 1], Calc(i, min(n, i + j)));
    for (int i = 1; i <= n; i++)
      fl[i] = max(fl[i + 1], Calc(max(1, i - j), i));
    for (int i = 1; i <= n; i++)
      res[i] ^= j * max(fl[i], fr[i]);
  }
  for (int i = 1; i <= n; i++)
    ans ^= i + res[i];
  cout << ans;
  return 0;
}
posted @ 2025-11-10 18:36  sb-yyds  阅读(8)  评论(0)    收藏  举报