2024初秋集训——提高组 #31

C. 特殊区间

题目描述

给定一个数列 \(A_1,A_2,\dots,A_N\),我们定义一个区间 \([l,r](l<r)\) 的价值为:

\[\max \limits_{a,b,c,d\in [l,r],c\ne d}\{A_a-A_b-(A_c\oplus A_d)\} \]

给定 \(Q\) 次查询,每次查询有多少个区间的价值在 \([d,u]\) 之间。

思路

显然,我们会令 \(A_a\) 最大,\(A_b\) 最小,\(A_c\oplus A_d\) 最小。由于一个序列的异或最小值为将其排序后相邻两项的异或最小值,所以我们使用两个 multiset 分别维护排序后的数列和排序后数列相邻两项异或值。由于这里有单调性,所以我们可以通过双指针求出价值 \(\ge x\) 的区间数量,使用前缀和的方式相减即可求出答案。

空间复杂度 \(O(N)\),时间复杂度 \(O(QN\log N)\)

代码

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

const int MAXN = 50001, MAXV = 1000001;

int n, q;
ll a[MAXN];
multiset<ll> s, s2;

void Insert(ll x) {
  auto it = s.lower_bound(x);
  if(it != s.begin() && it != s.end()) {
    s2.erase(s2.find((*prev(it)) ^ (*it)));
  }
  if(it != s.begin()) {
    s2.insert((*prev(it)) ^ x);
  }
  if(it != s.end()) {
    s2.insert((*it) ^ x);
  }
  s.insert(x);
}

void Erase(ll x) {
  s.erase(s.find(x));
  auto it = s.lower_bound(x);
  if(it != s.begin() && it != s.end()) {
    s2.insert((*prev(it)) ^ (*it));
  }
  if(it != s.begin()) {
    s2.erase(s2.find((*prev(it)) ^ x));
  }
  if(it != s.end()) {
    s2.erase(s2.find((*it) ^ x));
  }
}

ll Calc(ll x) {
  s.clear(), s2.clear();
  Insert(a[1]);
  ll ret = 0;
  for(int i = 1, j = 1; i <= n; Erase(a[i]), ++i) {
    for(; j <= n && (s2.empty() || *prev(s.end()) - *s.begin() - *s2.begin() < x); ) {
      if(++j <= n) {
        Insert(a[j]);
      }
    }
    ret += (n - j + 1);
  }
  return ret;
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> q;
  for(int i = 1; i <= n; ++i) {
    cin >> a[i];
  }
  for(int i = 1; i <= q; ++i) {
    ll d, u;
    cin >> d >> u;
    cout << Calc(d) - Calc(u + 1) << "\n";
  }
  return 0;
}

D. 魔法阵

题目描述

你要将 \(N\) 个魔导器围成一圈,如果魔导器 \(i\)\(j\) 相邻,则会构成一个魔力值为 \(m_{i,j}\) 的魔术通路。只有任意两个魔术通路的魔力值之差 \(\le k\),这个排列才是稳定的。现在有一些位置已经确定了,求有多少种合法的放置魔导器的方法。

思路

为了方便实现,我们先让这个圈转到第一个魔导器固定。若没有确定的魔导器,则强制定义第一个为 \(1\),最后再让答案乘以 \(N\)

我们考虑枚举魔力值的最小值 \(x\)。令 \(dp_{0/1,S,i}\) 表示当前是/否出现了 \(x\),已经使用了魔导器集合 \(S\),最后一个魔导器为 \(x\)。这里有一维状态是/否出现 \(x\) 是为了防止算重。按此状态转移即可。

空间复杂度 \(O(N2^N)\),时间复杂度 \(O(N^3 2^N)\)

代码

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

const int MAXN = 14, MOD = 998244353, MAXV = (1 << 13);

int n, k, a[MAXN], pos, p[MAXN], g[MAXN][MAXN], ans, A[MAXN * MAXN], tot, dp[2][MAXV][MAXN];
bool flag;

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> k;
  for(int i = 1; i <= n; ++i) {
    cin >> a[i];
    if(a[i]) {
      pos = i;
    }
  }
  if(pos) {
    for(int i = 1; i <= n; ++i) {
      p[(i >= pos ? i - pos + 1 : n - pos + 1 + i)] = a[i];
    }
  }else {
    flag = 1, p[1] = 1;
  }
  for(int i = 1; i <= n; ++i) {
    for(int j = 1; j <= n; ++j) {
      cin >> g[i][j];
      if(j < i) {
        A[++tot] = g[i][j];
      }
    }
  }
  for(int i = 1; i <= tot; ++i) {
    for(bool op : {0, 1}) {
      for(int s = 1; s < (1 << n); ++s) {
        for(int j = 1; j <= n; ++j) {
          dp[op][s][j] = 0;
        }
      }
    }
    dp[0][1 << (p[1] - 1)][p[1]] = 1;
    for(int s = 1; s < (1 << n); ++s) {
      for(bool op : {0, 1}) {
        int b = __builtin_popcount(s);
        for(int j = 1; j <= n; ++j) {
          if(!dp[op][s][j]) {
            continue;
          }
          for(int x = 1; x <= n; ++x) {
            if(!((s >> (x - 1)) & 1) && g[j][x] >= A[i] && g[j][x] - A[i] <= k && (!p[b + 1] || p[b + 1] == x)) {
              dp[op | (g[j][x] == A[i])][s | (1 << (x - 1))][x] = (dp[op | (g[j][x] == A[i])][s | (1 << (x - 1))][x] + dp[op][s][j]) % MOD;
            }
          }
        }
      }
    }
    for(int j = 1; j <= n; ++j) {
      if(g[j][p[1]] >= A[i] && g[j][p[1]] - A[i] <= k) {
        ans = (ans + dp[1][(1 << n) - 1][j]) % MOD;
        if(g[j][p[1]] == A[i]) {
          ans = (ans + dp[0][(1 << n) - 1][j]) % MOD;
        }
      }
    }
  }
  cout << 1ll * (flag ? n : 1) * ans % MOD;
  return 0;
}
posted @ 2024-10-06 21:29  Yaosicheng124  阅读(4)  评论(0编辑  收藏  举报