AGC049E Increment Decrement

想了一个比较菜的做法。

认为操作是我们选择一个区间 \([l,r]\),区间加或减 \(1\),代价是 \(\min(r-l+1,C)\)。分析一下操作的性质,可以发现操作的区间只有包含或不交。因为如果操作了 \([a,c]\)\([b,d]\)\(a\le b\le c\le d\),如果一正一负那么公共的部分不用操作,两个正改成 \([a,d]\)\([b,c]\) 不劣。

考虑差分,记 \(d_i=a_i-a_{i-1}\),那么就相当于提供了 \(d_i\)\(+\) 或者 \(-d_i\)\(-\)。然后 \(+\)\(-\) 要匹配,对于 \(i\)\(j\) 位置的匹配代价是 \(\min(|j-i|,C)\)

根据 unr d1t2 的想法,分层,每层 \(+-\) 交替。这里的分层就是对 \(a\)\(01\),然后 \(0\to 1\) 就是 \(+\)\(1\to 0\)\(-\),其余是空位。考虑怎么算最小权匹配,把 \(|j-i|\)\(C\) 分开考虑,我们可以操作若干 \((i,j)\),剩下的全部用 \(C\) 匹配。因此可以看成选择 \((i,j)\) 的代价是 \(|j-i|-C\)

显然此时 \(i,j\) 是相邻的,于是可以 dp。记 \(f_i\) 为前 \(i\) 个点答案,那么有 \(f_i=\min(f_{i-1},f_{i-2}+dis_i-C)\)。这里 \(dis\) 为第 \(i\) 个点和上一个位置的距离。

实际上原题就可以做 \(\mathrm{poly}(n)\) 了,不过 \(f\)\(O(n^2)\) 的,然后还要转 \(01\),复杂度非常高。

考虑继续优化。先把 \(f\) 取相反数,于是有 \(f_i=\max(f_{i-1},f_{i-2}+C-dis_i)\)。可以变成 \(f_i-f_{i-1}=\max(0,f_{i-2}-f_{i-1}+C-dis_i)\),那么我们只要维护 \(f_i-f_{i-1}\) 即可,这是 \(O(n)\) 的。

那么对于原题,先转 \(01\),此时记 \(F_{i,j,d,0/1}\) 表示前 \(i\) 个位置,\(f\) 的差是 \(j\),当前 \(dis=d\),最后一个位置是 \(0/1\) 的方案数。每次如果更新了 \(j\) 就算一下贡献。这样复杂度 \(O(n^5)\)

但是可以发现同时记录 \(j,d\) 是没用的,我们只关心 \(C-j-d\),于是只要记录这个值转移即可 \(O(n^4)\)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const int mod = 1e9 + 7;
void Add(int &x, ll y) { x = (x + y) % mod; }

const int kN = 55;
int n, c, k;
int pwk[kN];
int b[kN][kN];
int t[kN * kN];
int cnt[kN][2];
int f[kN][kN][2];
int g[kN][kN][2];

int main() {
  // freopen("1.in", "r", stdin);
  // freopen("1.out", "w", stdout);
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n >> c >> k;
  for(int i = 1; i <= n; i++) {
    for(int j = 1; j <= k; j++) {
      cin >> b[i][j];
      t[(i - 1) * k + j] = b[i][j];
    }
  }

  pwk[0] = 1;
  for(int i = 1; i <= n; i++) {
    pwk[i] = (ll)pwk[i - 1] * k % mod;
  }

  int res = 0;
  sort(t + 1, t + n * k + 1);
  for(int L = 0; L < n * k; L++) {
    if(t[L] == t[L + 1]) continue;

    for(int i = 1; i <= n; i++) {
      cnt[i][0] = cnt[i][1] = 0;
      for(int j = 1; j <= k; j++) {
        cnt[i][b[i][j] > t[L]]++;
      }
    }
    cnt[n + 1][0] = 1;

    memset(f, 0, sizeof(f));
    for(int i = 1, prd = 1; i <= n; i++) {
      f[i][c - 1][1] = (ll)prd * cnt[i][1] % mod;
      prd = (ll)prd * cnt[i][0] % mod;
    }

    int sum = 0;
    for(int i = 1; i <= n; i++) {
      for(int j = 0; j < c; j++) {
        for(int v : {0, 1}) {
          int val = f[i][j][v];
          if(!val) continue;
          Add(f[i + 1][max(j - 1, 0)][v], (ll)val * cnt[i + 1][v]);
          Add(f[i + 1][c - 1 - j][v ^ 1], (ll)val * cnt[i + 1][v ^ 1]);

          int add = (ll)val * cnt[i + 1][v ^ 1] % mod;
          add = (ll)add * j % mod;
          add = (ll)add * pwk[max(n - i - 1, 0)] % mod;
          Add(sum, mod - add);
        }
      }
    }

    memset(g, 0, sizeof(g));
    g[0][0][0] = 1;
    for(int i = 0; i <= n; i++) {
      for(int c = 0; c <= i; c++) {
        for(int v : {0, 1}) {
          int val = g[i][c][v];
          if(!val) continue;
          Add(g[i + 1][c][v], (ll)val * cnt[i + 1][v]);
          Add(g[i + 1][c + 1][v ^ 1], (ll)val * cnt[i + 1][v ^ 1]);
        }
      }
    }

    for(int i = 0; i <= n + 1; i += 2) {
      Add(sum, (ll)g[n + 1][i][0] * (i / 2 * c));
    }

    Add(res, (ll)(t[L + 1] - t[L]) * sum);
  }
  cout << res << "\n";
  return 0;
}
posted @ 2026-04-24 16:45  CJzdc  阅读(30)  评论(0)    收藏  举报