[PA 2019] Desant & 饺子(zongzi)

感觉还是过于牛了,连着两次用意想不到的方式 A 题。

没事干就记一下。可能主要记下赛时怎么想的。


[PA 2019] Desant

这场

做这题的时候感觉冥冥之中自有天意。

模拟赛是 n35 版,所以不卡常。

有简单的 O(n2n) DP,设 fi,S 是前 i 个数选择的状态是 S 的最小逆序对个数,及数量。可以滚。

不优化第二维,这个 DP 就没前途了。一般做到这我可能就停了,但当时我没啥思考过程的就直接想到怎么设计状态了,不过赛时没仔细思考复杂度,只是单纯想多过几个包,没想到是正解。

这里属于马后炮,因为我根本就不是这么想的。 考虑平常求逆序对个数是怎么做的,大概就是枚举一维,另一位偏序,可能会用一个关于值域的数据结构维护。启发我们关于值域做文章。发现我们只在乎和之后元素的大小关系,那就设计出了状态:设 fi,T1,,Tk 表示在前 i 个数中的选择情况是 T 的最小逆序对个数。

解释下这个 T 是啥意思:把 ai,ai+1,,an 排个序,设排好序后是 b 数组。那就把值域分成若干段了,具体:(0,b1),(b1,b2),,(bk,n+1)Ti 表示在第 i 个段中已经选了的元素个数。

转移:设 ai 对应 bk,那么转移会把 (bk1,bk),(bk,bk+1) 两段合并起来。选这个元素,Tk 就等于 Tk+Tk+1+1;不选,Tk=Tk+Tk+1。因为关于值域的信息都记录了,所以新增的逆序对个数对 T 跑下后缀和就行了。

实现的时候可以把这个 T 数组哈希掉,然后就好做了。

分析下赛时不会的复杂度:前 i 个数都是固定的,只有选或不选的区别,Ti[0,Timax]。那状态数就是 (Ti+1),其中 Ti+1n。为 O(3n3)

证明:

  • 积最大的时候这些数相等,这个是小学数学。设这个数为 x,那么积 f(x)=xnxn 取任意值都不影响最值的位置,取对数 ln(x1x)=1xln(x)。对这个式子求导,算出 x=e 的时候有最大值。于是 f(x)=ene

赛时的代码实现比较劣,当时用 gp_hash_table 测了下 n=35 要跑 4 秒,于是换了手写哈希表,n=35 轻松过。n=40 过不去,卡常。

总复杂度 O(n23n3)

// 赛时代码
#include <bits/stdc++.h>

using namespace std;

#define IL inline
#define vec vector
#define eb emplace_back
#define bg begin
#define emp emplace
#define fi first
#define se second
#define bg begin
#define al(v) v.bg(), v.end()
#define sz size
#define pp pop_back
#define lb lower_bound
#define ub upper_bound
#define mkp make_pair
#define exg exchange
using ubt = long long;
using uubt = unsigned long long;
using dub = double;
using pii = pair<int, ubt>;

auto ckx = [](auto &x, const auto &y) { (x < y) && (x = y); };
auto ckm = [](auto &x, const auto &y) { (x > y) && (x = y); };

template <typename T = int>
IL T _R() {
  T s = 0, w = 1;
  char c = getchar();
  while (!isdigit(c)) w = c == '-' ? -1 : 1, c = getchar();
  while (isdigit(c)) s = s * 10 + c - 48, c = getchar();
  return s * w;
}

const int inf = 1e9;

const int N = 35;
const int maxN = N + 3;

int n, a[maxN];

int b[maxN][maxN];

int cnt[2];
vec<vec<int>> t[2];
vec<pii> f[2];
auto upd = [](auto &A, const auto &B) {
  if (A.fi == B.fi) A.se += B.se;
  else if (A.fi > B.fi) A = B;
};

const int mod = 1e7 + 73317;
const ubt B = 1231;
const uubt BB = 13331;
auto Tr = [](const auto &A) {
  int S = 0;
  uubt SS = 0;
  for (auto i : A) {
    S = S * B % mod + i;
    if (S >= mod) S -= mod;
    SS = SS * BB + i;
  }
  return mkp(S, SS);
};

struct HASH {
  vec<int> T;
  int head[mod], tot;
  struct edge {
    int ls;
    uubt v;
    int hs;
  } e[1000000];
  IL int find(int A, uubt B) {
    for (int i = head[A]; i; i = e[i].ls)
      if (e[i].v == B) return e[i].hs;
    return -1;
  }
  IL void add(int A, uubt B, int V) {
    if (head[A] == 0) T.eb(A);
    e[++tot] = {head[A], B, V};
    head[A] = tot;
  }
  IL void clear() {
    for (int i : T) head[i] = 0;
    T.clear();
    tot = 0;
  }
} mp[2];

auto id = [](int i, const auto &A) {
  auto tmp = Tr(A);
  auto v = mp[i].find(tmp.fi, tmp.se);
  if (v != -1) return v;
  v = cnt[i]++;
  mp[i].add(tmp.fi, tmp.se, v);
  f[i].eb(inf, 0), t[i].eb(A);
  return v;
};

pii ans[maxN];

int main() {
  freopen("des.in", "r", stdin);
  freopen("des.out", "w", stdout);

  // dub ST = clock();

  n = _R();
  for (int i = 1; i <= n; i++) a[i] = _R();

  for (int i = n; i >= 1; i--) {
    for (int j = i; j <= n; j++)
      b[i][j] = a[j];
    sort(b[i] + i, b[i] + n + 1);
    b[i][i - 1] = 0, b[i][n + 1] = n + 1;
  }

  auto tmp = vec<int> (n + 1, 0);
  upd(f[0][id(0, tmp)], mkp(0, 1));

  int cur = 0;
  for (int i = 1; i <= n; i++) {
    cur ^= 1;
    for (int j = 0; j < cnt[cur ^ 1]; j++) {
      const auto &u = t[cur ^ 1][j];
      vec<int> v, h;
      int K = f[cur ^ 1][j].fi;
      for (int p = i, fl = 0; p <= n + 1; p++)
        if (a[i] == b[i][p]) {
          v.eb(u[p - i] + u[p - i + 1]);
          h.eb(u[p - i] + u[p - i + 1] + 1);
          K += u[p - i + 1];
          p++, fl = 1;
        } else {
          v.eb(u[p - i]), h.eb(u[p - i]);
          if (fl) K += u[p - i];
        }
      int i1 = id(cur, v), i2 = id(cur, h);
      upd(f[cur][i1], f[cur ^ 1][j]);
      upd(f[cur][i2], mkp(K, f[cur ^ 1][j].se));
    }
    f[cur ^ 1].clear();
    t[cur ^ 1].clear();
    mp[cur ^ 1].clear();
    cnt[cur ^ 1] = 0;
  }

  for (int i = 0; i < cnt[cur]; i++)
    ans[t[cur][i][0]] = f[cur][i];

  for (int i = 1; i <= n; i++)
    printf("%d %lld\n", ans[i].fi, ans[i].se);

  // dub ED = clock();
  // cerr << (ED - ST) / 1e6 << endl;
}

饺子(zongzi)

这场

非正解。

发现 0 有良好的性质:我们不必选 n 个数了。假设有 z0,那我们找到有大于等于 nz 个数加和为 n 的倍数就行了。

我们想让限制尽可能松,可以拿众数当 “0”,因为我们是选 n 个数,每个数加减一个 x,那 n 个数的和就是加减 nx,对 n 取模后就都没了。所以可以对所有数平移。

然后对后面的数找就行了。做集合是难的,做区间是容易的,人类的大脑告诉我们:从小到大排序后比较容易找到答案。找不到怎么办,多随几遍。。。

复杂度 O(Δn),忽略排序。

// 赛时代码。小改了下if的顺序
#include <bits/stdc++.h>

using namespace std;

#define int ubt

#define IL inline
#define vec vector
#define bstr basic_string
#define eb emplace_back
#define bg begin
#define emp emplace
#define fi first
#define se second
#define bg begin
#define al(v) v.bg(), v.end()
#define sz size
#define pp pop_back
#define mkp make_pair
#define lb lower_bound
#define ub upper_bound
#define exg exchange
using ubt = long long;
using uubt = unsigned long long;
using dub = double;
using pii = pair<int, int>;

auto ckx = [](auto &x, const auto &y) { (x < y) && (x = y); };
auto ckm = [](auto &x, const auto &y) { (x > y) && (x = y); };

template <typename T = int>
IL T _R() {
  T s = 0, w = 1;
  char c = getchar();
  while (!isdigit(c)) w = c == '-' ? -1 : 1, c = getchar();
  while (isdigit(c)) s = s * 10 + c - 48, c = getchar();
  return s * w;
}

const int N = 3e5;
const int maxN = N * 2 + 3;

int n;

int b[maxN];

int tp;
pii a[maxN], c[maxN];
vec<int> pos;

int cnt[maxN];

deque<int> ls[maxN];

mt19937 rd(762);

signed main() {
  freopen("zongzi.in", "r", stdin);
  freopen("zongzi.out", "w", stdout);

  n = _R();
  for (int i = 1; i <= n * 2 - 1; i++)
    cnt[b[i] = a[i].fi = _R()]++, a[i].se = i;
  int mx = 0;
  for (int i = 0; i < n; i++)
    if (cnt[i] > cnt[mx]) mx = i;
  for (int i = 1; i <= n * 2 - 1; i++)
    if (a[i].fi != mx) {
      c[++tp].fi = (a[i].fi - mx + n) % n;
      c[tp].se = a[i].se;
    } else pos.eb(a[i].se);
  sort(c + 1, c + tp + 1);

  if (pos.sz() >= n) {
    while (n--)
      printf("%lld ", pos.back()), pos.pp();
    return 0;
  }

  do {
    for (int i = 0; i < n; i++) ls[i].clear();
    auto tmp = pos;
    ls[0].eb(0);
    for (int i = 1, j = 0; i <= tp; i++) {
      j += c[i].fi, j %= n;
      if (ls[j].sz()) {
        while (ls[j].sz() && i - ls[j].front() > n) ls[j].pop_front();
        if (ls[j].sz() && i - ls[j].front() + pos.sz() >= n) {
            vec<int> ans;
            for (int u = ls[j].front() + 1; u <= i; u++)
            ans.eb(c[u].se);

            while (ans.sz() < n) ans.eb(tmp.back()), tmp.pp();

            for (auto u : ans) printf("%lld ", u);
            puts("");

            // j = 0;
            // for (auto u : ans) j += b[u];
            // cout << j % n << '\n';

            return 0;
        }
      }
      ls[j].eb(i);
    }
  } while (shuffle(c + 1, c + tp + 1, rd), 1);

  puts("-1");
}

懒,而且我是 shadow,所以直接挂正解图片。

作者:ccxswl

出处:https://www.cnblogs.com/ccxswl/p/18720655

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   ccxswl  阅读(29)  评论(4编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探
点击右上角即可分享
微信分享提示
more_horiz
keyboard_arrow_up light_mode palette
选择主题