Codeforces Round #672 (Div. 2) 题解

Codeforces Round #672 (Div. 2)

A. Cubes Sorting

题意:问是否用(frac{n(n-1)}{2}-1)以内次交换相邻元素的操作把序列排好序。

解析:可以发现这就是一个冒泡排序的过程,而(frac{n(n-1)}{2})是冒泡排序的最坏复杂度,所以当且仅当序列是递减序列的时候需要使用(frac{n(n-1)}{2})次操作,其它都能排好序。因此只要判断当前序列是否是递减序列。

const int maxn = 5e4+10;
int a[maxn];
void run() {
  int n = rd();
  for (int i = 1; i <= n; i++) a[i] = rd();
  bool ans = false;
  for (int i = 2; i <= n; i++) if (a[i] >= a[i-1]) ans = true;
  puts(ans ? "YES" : "NO");
}

B. Rock and Lever

题意:统计一个序列中按位与的值大于等于按位异或的值的元素对的个数。

解析:考虑最高位1,两个元素最高位1在相同位置则满足条件,否则不满足。计数可以for一遍统计之前最高位1的元素个数。

const int maxn = 1e5+10;
int n, a, t[maxn];
int get(int x) {
  int cnt = 0;
  while (x) {
    cnt ++;
    x >>= 1;
  }
  return cnt;
}
int cnt[maxn];
void run() {
  memset(t,0,sizeof t);
  n = rd();
  for (int i = 1; i <= n; i++) a = rd(), cnt[i] = get(a);
  int ans = 0;
  for (int i = 1; i <= n; i++) {
    ans += t[cnt[i]];
    t[cnt[i]]++;
  }
  pnt(ans);
}

C. Pokémon Army

题意:给一个序列,可以从序列中选择一些元素,这些元素从左到右的值要正负交替,问选择元素的和最大是多少。hard版本增加了q次交换两个元素的操作,每次操作结束后需要再输出一个交换后的序列的答案。

解析:本题当时做的时候可以说是各显神通,ddp、思维模拟、线段树、差分等方法都有人AC。官方题解中解释了一个重要的结论,取所有的峰值作为正数,所有谷值作为负数是最优的,所取的元素从正数开始波峰波谷交替总共包含奇数个元素是最优的。证明也比较简单,如果取1个数那必然是取最大的那个为正最优,这个最大的值必然是波峰,那么考虑这个值之后的1个波谷和1个波峰,波峰的值必然比波谷的值大,因此波峰-波谷>0,把这两个值累加到答案会使答案更优,按照这样取下去会把所有波峰和波谷交替取完。

做法:这题差分是比较好理解的一种做法。对原序列进行差分,差分序列中所有正数和就是答案。正确性证明:考虑第一个波峰开始,显然差分序列累加到第一个波峰的时候就是第一个波峰的值,然后考虑后一个波谷和波峰,在第一个波峰走向波谷的时候所有的差分序列为负,不计入贡献,在波谷走向下一个波峰的时候所有差分序列的值为正,并且所有差分序列的值的和显然就等于下一个到达的波峰减之前一个波谷的值,也就是之前模拟之前的证明那个过程,照这样取下去只需要统计所有正数就是答案。至于q次交换只要实时(O(1))维护差分序列的值对答案的影响即可。

const int maxn = 3e5+10;
int a[maxn], ret;
 
inline void solve(int l, int r) {
  if (a[l] > a[l-1]) ret -= a[l] - a[l-1];
  if (a[l+1] > a[l]) ret -= a[l+1] - a[l];
  if (a[r] > a[l-1]) ret += a[r] - a[l-1];
  if (a[l+1] > a[r]) ret += a[l+1] - a[r];
}
 
void run() {
  ret = 0;
  int n = rd(), q = rd();
  for (int i = 1; i <= n; i++) a[i] = rd();a[n+1] = a[0] = 0;
  for (int i = 1; i <= n; i++) if (a[i] > a[i-1]) ret += a[i]-a[i-1];
  pnt(ret);
  for (int i = 1; i <= q; i++) {
    int l = rd(), r = rd();
    if (l > r) swap(l,r);
    if (l+1==r){
      if (a[r] > a[l]) ret -= a[r]-a[l];
      else ret += a[l]-a[r];
      if (a[l] > a[l-1]) ret -= a[l] - a[l-1];
      if (a[r] > a[l-1]) ret += a[r] - a[l-1];
      if (a[r+1] > a[r]) ret -= a[r+1] - a[r];
      if (a[r+1] > a[l]) ret += a[r+1] - a[l];
    } else {
      solve(l,r);
      solve(r,l);
    }
    swap(a[l],a[r]);
    pnt(ret);
  }
}

线段树维护区间奇数/偶数子序列最大/最小值的写法:
(其中1到n区间奇数序列的最大值就是答案)

const int maxn = 3e5+10;
int a[maxn];
struct seg{
  int l, r, ex, en, ox, on;
  inline int mid() {return l + r >> 1;}
}node[maxn<<2];
 
inline void psup(int i) {
  node[i].ex = max({0ll,node[ls].ex,node[rs].ex});
  node[i].ex = max(node[i].ex, node[ls].ex + node[rs].ex);
  node[i].ex = max(node[i].ex, node[ls].ox - node[rs].on);
  node[i].ox = max(node[ls].ox, node[rs].ox);
  node[i].ox = max(node[i].ox, node[ls].ox - node[rs].en);
  node[i].ox = max(node[i].ox, node[ls].ex + node[rs].ox);
  node[i].en = min({0ll,node[ls].en,node[rs].en});
  node[i].en = min(node[i].en, node[ls].en + node[rs].en);
  node[i].en = min(node[i].en, node[ls].on - node[rs].ox);
  node[i].on = min(node[ls].on,node[rs].on);
  node[i].on = min(node[i].on, node[ls].on - node[rs].ex);
  node[i].on = min(node[i].on, node[ls].en + node[rs].on);
}
 
void build(int l, int r, int i) {
  node[i] = (seg){l,r,-inf,inf,-inf,inf};
  if (l == r){
    node[i].on = node[i].ox = a[l];
    node[i].en = node[i].ex = 0;
    return;
  }
  int mid = l + r >> 1;
  build(l,mid,ls);
  build(mid+1,r,rs);
  psup(i);
}
 
void modify(int p, int x, int i) {
  if (node[i].l == p && node[i].r == p) {
    node[i].on = node[i].ox = x;
    return;
  }
  int mid = node[i].mid();
  if (p <= mid) modify(p,x,ls);
  else modify(p,x,rs);
  psup(i);
}
 
void run() {
  int n = rd(), q = rd();
  for (int i = 1; i <= n; i++) a[i] = rd();
  build(1,n,1);
  pnt(node[1].ox);
  for (int i = 1; i <= q; i++) {
    int l = rd(), r = rd();
    modify(l,a[r],1);
    modify(r,a[l],1);
    swap(a[l],a[r]);
    pnt(node[1].ox);
  }
}

D. Rescue Nibel!

题意:给(n)个区间([l,r]),问从(n)个区间中选择(k)个区间使得他们都有交集的选择方案数。

解析:本题有一个非常直观的想法就是离散化+树状数组统计峰值C(n,k),但这个做法是错误的,会遗漏区间分布比较零散的那些情况。正确的做法是差分,对每个区间按左端点排序,左到右扫的时候把左端点加到贡献里,加入时统计该左端点所在区间对答案的贡献,计算方法就是在已经在贡献里的区间中再选择k-1个区间也就是把C(now,k-1)累加到ans中,当扫到右端点的时候把该区间从贡献中删除。具体实现方法就是维护一个now值,如果端点为l就把该区间加到贡献,也就是now++,如果为r+1就是把该区间删除,now--,注意排序的时候要先把之前的区间删除再累加新的区间,因为删除的区间已经不存在了,不能继续使用。可以发现,同C2一样,差分在解决一类贡献相关的问题上效果显著。

const int maxn = 1e6+10;
struct P{
  int a, b;
  bool friend operator <(const P &x, const P &y) {
    return x.a == y.a ? x.b < y.b : x.a < y.a;
  }
}p[maxn];
 
int cnt;
 
ll fac[maxn];
 
void init() {
  fac[0]=1;
  for(int i=1;i<maxn;i++) {
    fac[i]=fac[i-1]*i%mod;
  }
}
 
ll Cc(ll x, ll y){
  if (x<y) return 0;
  return fac[x]%mod*ksm(fac[y]%mod*fac[x-y]%mod,mod-2,mod)%mod;
}
 
void run() {
  init();
  int n = rd(), k = rd();
  for (int i = 1; i <= n; i++)  {
    int l = rd(), r = rd();
    p[++cnt].a = l, p[cnt].b = 1;
    p[++cnt].a = r+1, p[cnt].b = -1;
  }
  sort(p+1,p+cnt+1);
  int ans = 0, pos = 1, sum = 0;
  for (int i = 1; i <= cnt; i++) {
    if (p[pos].b == 1) ans += Cc(sum,k - 1),ans %= mod;
		sum += p[pos].b;
		pos++;
  }
  pnt(ans);
}
原文地址:https://www.cnblogs.com/hznudreamer/p/13732621.html