逐月信息学 2024 提高组 #5

\(\color{black}\texttt{A. 党同伐异}\)

题目描述

\(N\) 个候选人,每个候选人都有一个不同的政治倾向 \(c_i\),进行 \(N-1\) 次选举。每轮选举中,所有未被淘汰的候选人给另一个没被淘汰的候选人。每一个候选人会将票投给 \(c_i\) 与自己差的绝对值最大的候选人。如果有多个这样的候选人,选择 \(c_i\) 更大的那个。每一轮中获得票最多的人淘汰,如果有多个这样的候选人,选择 \(c_i\) 更大的那个。问最后剩下哪个候选人。

思路

首先对 \(c_i\) 排序,因为每次淘汰的都是 \(c_i\) 最小或最大的人,所以剩余的候选人都是一个区间 \([l,r]\)。对于每一轮,二分最后一个 \(c_i - c_l\le c_r-c_i\)\(i\),则 \([l,i]\) 内的人全部投票给 \(r\)\([i+1,r]\) 的人投票给 \(l\),比较一下即可。

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

代码

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

const int MAXN = 1000001;

struct Node {
  int x, id;
}a[MAXN];

int n, s = 1, t;

int Binary_Search() {
  int l = s, r = t;
  for(; l < r; ) {
    int mid = (l + r + 1) >> 1;
    (a[mid].x - a[s].x <= a[t].x - a[mid].x ? l = mid : r = mid - 1);
  }
  return l;
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n;
  t = n;
  for(int i = 1; i <= n; ++i) {
    cin >> a[i].x;
    a[i].id = i;
  }
  sort(a + 1, a + n + 1, [](const Node &a, const Node &b) { return a.x < b.x; });
  for(int i = 1; i < n; ++i) {
    int x = Binary_Search();
    (x - s + 1 < t - x ? s++ : t--);
  }
  cout << a[s].id;
  return 0;
}

总结

水题。

\(\color{black}\texttt{B. 混乱谜团}\)

题目描述

定义两个数字 \(x,y\) 在排列 \(A\) 中的距离为 \(|i-j|(A_i=x,A_j=y)\),一个排列 \(A\) 的混乱程度为 \(1,2\) 的距离、\(2,3\) 的距离、\(\dots\)\(n-1,n\) 的距离之和。给定 \(N,K\),请构造一个长度为 \(N\),混乱程度为 \(K\) 的排列。

思路

我们先看一个混乱度为 \(N-1\) 的序列:\([1,2,3,\dots,N]\)。怎么让它的混乱度增加呢?

如果反转 \([2,3,\dots,N]\),混乱度加 \(N-2\)

如果反转 \([3,4,\dots,N]\),混乱度加 \(N-3\);

\(\vdots\)

由于反转是相互独立的,所以我们可以枚举每种反转看是否需要。

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

代码

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

const int MAXN = 100001;

int n, a[MAXN];
ll k;

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> k;
  a[1] = 1, k -= n - 1;
  int l = 2, r = n;
  for(int i = 2; l != r; ++i) {
    if(n - i <= k) {
      k -= n - i;
      a[r] = i;
      if(r > l) {
        r--;
      }else {
        r++;
      }
      swap(l, r);
    }else {
      a[l] = i;
      if(r < l) {
        l--;
      }else {
        l++;
      }
    }
  }
  a[l] = n;
  for(int i = 1; i <= n; ++i) {
    cout << a[i] << " ";
  }
  return 0;
}

总结

比较简单,但要记得 long long,而且考场上没做出来。

\(\color{black} \texttt{C. 固若金汤}\)

题目描述

有一排士兵,每个士兵都有一个默契值 \(c_i\)。现在要从中挑出一个子序列,使得子序列任意相邻三项按位与均不为 \(0\)。求最多能选多少士兵。

思路

定义 \(dp_{i,j}\) 表示子序列末尾选择 \(\dots,j,i\) 的最长长度。但这样的状态是 \(O(N^2)\) 的,转移是 \(O(N)\) 的,时间爆掉了。

由于状态不能优化,所以考虑优化转移:定义 \(f_{i,x}\) 表示以 \(i\) 结尾的末两个数按位且第 \(x\) 位为一的子序列长度最大值。

这个 \(f_{i,x}\) 在 dp 时处理就行了。这时,我们发现转移时就不需要 \(j\) 了,于是就 \(\texttt{AC}\) 了。

时空复杂度均为 \(O(N^2)\)

代码

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

const int MAXN = 2001;

int n, a[MAXN], dp[MAXN][MAXN], f[MAXN][32], ans = 1;

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n;
  for(int i = 1; i <= n; ++i) {
    cin >> a[i];
  }
  for(int i = 1; i <= n; ++i) {
    dp[i][0] = 1;
  }
  for(int i = 1; i <= n; ++i) {
    for(int j = 0; j < i; ++j) {
      for(int k = 0; k < 32; ++k) {
        if(!j || (((a[i] & a[j]) >> k) & 1)) {
          f[i][k] = max(f[i][k], dp[i][j]);
        }
      }
      ans = max(ans, dp[i][j]);
    }
    for(int j = i + 1; j <= n; ++j) {
      for(int k = 0; k < 32; ++k) {
        if((a[j] >> k) & 1) {
          dp[j][i] = max(dp[j][i], f[i][k] + 1);
        }
      }
    }
  }
  cout << ans;
  return 0;
}

总结

这道题需要一些思考,考场上未做出。

\(\color{black}\texttt{D. 替身使者}\)

题目描述

\(\texttt{IOI}\) 中,\(\texttt{HPY}\) 十分有信心已经 \(\texttt{AK}\),但为了保险,他准备雇佣 \(N\) 个替身中的一些。每个替身都需要 \(x_i\) 元的费用,并且要求电脑至少升级到 \(k_i\) 级。\(\texttt{HPY}\) 的电脑原来是 \(0\) 级,每升一级需要 \(b\) 元。第 \(i\) 个替身会 \(m_i\) 道题,分别是 \(A_{i,1},A_{i,2},\dots,A_{i.m_i}\),问 \(\texttt{HPY}\) 至少需要多少钱来保证每道题都至少有一个替身会写?

若不可能则输出 \(-1\)

思路

这道题如果去掉电脑升级就十分简单,所以我们来考虑如何去掉这个限制。

我们可以对每个人要求的级数进行排序,这样要求最大的一定是最后一个人。所以就可以省掉这个状态了。

dp:\(dp_{i,s}\) 表示在前 \(i\) 个中选,会的题目集合为 \(s\) 时的最少钱数。

加个降维优化就可以了。

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

代码

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

const int MAXN = 101, MAXV = (1 << 21);
const ll INF = (ll)(2e18);

struct Node {
  int x, k, res;
}s[MAXN];

int n, m, b;
ll dp[MAXV], ans = INF;

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> m >> b;
  for(int i = 1, k; i <= n; ++i) {
    cin >> s[i].x >> s[i].k >> k;
    for(int j = 1, x; j <= k; ++j) {
      cin >> x;
      s[i].res |= (1 << (x - 1));
    }
  }
  sort(s + 1, s + n + 1, [](const Node &a, const Node &b) { return a.k < b.k; });
  for(int i = 1; i < (1 << m); ++i) {
    dp[i] = INF;
  }
  dp[0] = 0;
  for(int i = 1; i <= n; ++i) {
    for(int j = (1 << m) - 1; j >= 0; --j) {
      dp[j | s[i].res] = min(dp[j | s[i].res], dp[j] + s[i].x);
    }
    ans = min(ans, dp[(1 << m) - 1] + 1ll * s[i].k * b);
  }
  cout << (ans == INF ? -1 : ans);
  return 0;
}

总结

需要注意 long long\(-1\) 等问题。

posted @ 2024-07-02 16:51  Yaosicheng124  阅读(33)  评论(1编辑  收藏  举报