Codeforces Round #680


写在前面

比赛地址:https://codeforces.com/contest/1445

第二场 CF,第一场打一半吃饭去了(

没把 D 推出来太遗憾了= =


A

Problem - 1445A

\(t\) 组数据。
给定两长度为 \(n\) 的递增序列 \(a,b\),参数 \(x\)
求重新排列 \(b\) 后,是否可以满足 \(\forall 1\le i\le n, a_i + b_i \le x\)
\(1\le t\le 100\)\(1\le n\le 50\)\(1\le x\le 1000\)\(1\le a_i,b_i\le x\)
1S,512MB。

数组开小是傻逼。

贪心一下,对于 \(a_i\),应选择当前还没有固定位置的,满足条件的,最大的 \(b_i\) 与它对应。
找不到 \(b_i\) 则无解。

开个桶记录下 \(b\) 的值,正序枚举 \(a_i\) 贪心即可。
复杂度 \(O(tx)\)

//知识点:贪心,模拟
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 500 + 10;
const int kMaxw = 10100;
//=============================================================
int T, n, x, a[kMaxn], b[kMaxn], cnt[kMaxw];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
//=============================================================
int main() {
  T = read();
  while (T --) {
    n = read(), x = read();
    memset(cnt, 0, sizeof (cnt));
    for (int i = 1; i <= n; ++ i) a[i] = read();
    for (int i = 1; i <= n; ++ i) {
      b[i] = read();
      cnt[b[i]] ++;
    }
    
    bool ans = true;
    for (int i = 1; i <= n; ++ i) {
      int val = a[i];
      bool flag = true;
      for (int j = x - val; j >= 1; -- j) {
        if (cnt[j]) {
          -- cnt[j];
          flag = false;
          break;
        }
      }
      if (flag) {
        ans = false;
        break;
      }
    }
    printf("%s\n", ans ? "Yes" : "No");
  }
  
  return 0;
}

B

Problem - 1445B

无法简述的题面。

看样例猜结论,答案即 \(\max(a+b, c+d)\)
至少有 100 个人的得分是 \(a+b\),又至少有 100 个人的得分是 \(c+d\)

//知识点:乱搞
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
//=============================================================
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
//=============================================================
int main() {
  int T = read();
  while (T --) {
    int a = read(), b = read(), c = read(), d = read();
    printf("%d\n", std::max(a + b, c + d));
  }
  return 0;
}

C

Problem - 1444A

\(t\) 组数据。
给定参数 \(p,q\),求一个最大的 \(x\),满足 \((x|p) \land (q\not| {x})\)
\(1\le t\le 50\)\(1\le p\le 10^{18}\)\(2\le q\le 10^9\)
1S,512MB。

这是赛时的写法:

显然 \(p \bmod q \not= 0\),输出 \(p\)

\(p \bmod q = 0\) 时,有个想法。
想到让 \(p\) 除以某些 \(q\) 的质因子,直到 \(p\) 不能被 \(q\) 整除。

\(p\) 中比 \(q\) 大的质因子没有影响,可以仅考虑 \(q\) 的质因子。
并且仅削除一种质因子,肯定比削除多种质因子优。

于是枚举 \(q\) 的每一种质因子,令 \(p\) 除以该质因子,直到 \(p\) 中该质因子的次数比 \(q\) 中的次数
统计削除不同质因子时,答案的最大值即可。

复杂度 \(O(\sqrt{q})\)

//知识点:质因数分解 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <cmath>
#define LL long long
//=============================================================
//=============================================================
inline LL read() {
  LL f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
//=============================================================
int main() {
  int T = read();
  while (T --) {
    LL p = read(), q = read(), orip = p, x = 1;
    if (p < q) {
      printf("%lld\n", p);
      continue ;
    }
    if (p % q != 0) {
      printf("%lld\n", p);
      continue ;
    }
    
    for (LL i = 2, lim = q; i * i <= lim; ++ i) {
      if (q % i == 0ll) {
        int cntq = 0, cntp = 0;
        
        while (q % i == 0ll) {
          q /= i;
          ++ cntq;
        }
        while (p % i == 0ll) {
          p /= i;
          ++ cntp;
        }
        
        if (cntp  < cntq) continue ;
        LL tmp = orip;
        for (int lim = cntp - cntq + 1; lim; -- lim) {
          tmp /= i;
        }
        if (tmp > x) x = tmp;
      }
    }
    
    if (q != 1) {
      int cntp = 0;
      while (p % q == 0ll) {
        p /= q;
        ++ cntp;
      }
      if (cntp  >= 1) {
        LL tmp = orip;
        for (int lim = cntp - 1 + 1; lim; -- lim) {
          tmp /= q;
        }
        if (tmp > x) x = tmp;  
      }
    }
    

    printf("%lld\n", x);
  }
  return 0;
}

以下是赛时代码,可以发现在计算 q 的大于 \(\sqrt{q}\) 的质因子时用了一个 continue; 语句= =
如果这个语句执行,那么将会跳到下一个数据。
居然没有 fst,太神奇了。

//知识点:质因数分解 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <cmath>
#define LL long long
//=============================================================
//=============================================================
inline LL read() {
  LL f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
//=============================================================
int main() {
  int T = read();
  while (T --) {
    LL p = read(), q = read(), orip = p, x = 1;
    if (p < q) {
      printf("%lld\n", p);
      continue ;
    }
    if (p % q != 0) {
      printf("%lld\n", p);
      continue ;
    }
    
    for (LL i = 2, lim = q; i * i <= lim; ++ i) {
      if (q % i == 0ll) {
        int cntq = 0, cntp = 0;
        
        while (q % i == 0ll) {
          q /= i;
          ++ cntq;
        }
        while (p % i == 0ll) {
          p /= i;
          ++ cntp;
        }
        
        if (cntp  < cntq) continue ;
        LL tmp = orip;
        for (int lim = cntp - cntq + 1; lim; -- lim) {
          tmp /= i;
        }
        if (tmp > x) x = tmp;
      }
    }
    
    if (q != 1) {
      int cntp = 0;
      while (p % q == 0ll) {
        p /= q;
        ++ cntp;
      }
      if (cntp  < 1) continue ;
      LL tmp = orip;
      for (int lim = cntp - 1 + 1; lim; -- lim) {
        tmp /= q;
      }
      if (tmp > x) x = tmp;
    }
    
    printf("%lld\n", x);
  }
  return 0;
}

D

Problem - 1444B

给定一长度为 \(2\times n\) 的数列 \(a\),需要将其划分成两个长度为 \(n\) 的数列 \(p,q\)
定义 \(f(p,q)\) 表示,将 \(p\) 不降序 排列 得到数列 \(x\)\(q\) 不升序 排列后得到数列 \(y\)\(\sum |x_i-y_i|\) 的值。
求所有可能的划分方式的 \(f(p,q)\) 之和 \(\bmod 998244353\) 的值。
\(1\le n\le 1.5\times 10^5\)\(1\le a_i\le 10^9\)
2S,512MB。

有结论一:对于任何一种合法的划分方案 \((p,q)\)\(f(p,q)\) 均相等。

考虑证明。
先将 \(a\) 升序排序,得到数列 \(b\)
设数集 \(L = \{b_1, b_2,\cdots, b_n\}\)\(R = \{b_{n+1}, b_{n+2}\cdots, b_{2n}\}\)
显然数列中的数只能存在于其中一个集合中。

此时有结论二:对于任意一种划分方案 \((p,q)\),对于每一个位置 \(i\)\(x_i\)\(y_i\) 不在同一集合中。
证明考虑反证法,若存在 \(x_i\in L\)\(y_i\in L\)
\(x,y\) 的单调性,则存在 \(i\) 个不大于 \(x_i\) 的数在 \(L\) 中,同时存在 \(n-i+1\) 个不大于 \(y_i\) 的数在 \(L\)中,且这两部分数不重合。
得到 \(L\) 中至少存在 \(n+1\) 个数,与已知矛盾,反证原结论成立。

由结论二可知,对于任意划分方案 \((p,q)\)\(f(p,q) = \operatorname{sum}(R) - \operatorname{sum}(L)\)
得到结论一成立。

只需要计算一种划分方案的答案即可,乘上划分的方案数 \(\binom{2n}{n}\),最后的答案即为:

\[\left(\sum_{i=n+1}^{2n} b_i - \sum_{i=1}^{n} b_i\right) \times \dbinom{2n}{n} \]

//知识点:思维,排序,组合数
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long 
const int kMaxn = 3e5 + 10;
const int kMod = 998244353;
//=============================================================
int n, a[kMaxn];
LL ans, fac[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
LL QPow(LL x_, LL y_) {
  LL ret = 1;
  for (; y_; y_ >>= 1ll) {
    if (y_ & 1) ret = ret * x_ % kMod;
    x_ = x_ * x_ % kMod;
  }
  return ret;
}
LL C(int n_, int m_) {
  fac[1] = 1;
  for (int i = 2; i <= n_; ++ i) {
    fac[i] = 1ll * fac[i - 1] * i % kMod; 
  }
  return fac[n_] * QPow(fac[m_], kMod - 2) % kMod * QPow(fac[n_ - m_], kMod - 2) % kMod;
}
//=============================================================
int main() {
  n = read();
  for (int i = 1; i <= 2 * n; ++ i) a[i] = read();
  std::sort(a + 1, a + 2 * n + 1);
  for (int i = 1; i <= n; ++ i) {
    ans = (ans - a[i] + kMod) % kMod;
  }
  for (int i = n + 1; i <= 2 * n; ++ i) {
    ans = (ans + a[i] + kMod) % kMod;
  }
  printf("%lld\n", ans * C(2 * n, n) % kMod);
  return 0;
}

E

Problem - 1444C

给定一张 \(n\) 个节点 \(m\) 条边的无向图,没有自环重边。
每个节点都在一个组 (group) 中,共有 \(k\) 组,可能存在某组为空。
求选出两组点,是它们能构成二分图的 方案数。
\(1\le n\le 5\times 10^5\)\(0\le m\le 5\times 10^5\)\(2\le k\le 5\times 10^5\)
\(1\le c_i\le k\)
3S,512MB。

从某提交记录学到的

感谢 DrCold 的注释代码!
提交记录:https://codeforces.com/contest/1445/submission/97408824

一张图是二分图的充要条件是不存在奇环。
先考虑每一组是否为二分图,使用扩展域并查集维护。
将内部不为二分图的组删除,再反向转化一下,求剩下的 \(k'\) 组里,有多少对不能组成二分图,答案即 \(\frac{k'(k'-1)}{2} -\) 对数。

之后枚举所有点对检查?太慢啦!
发现两组点 不能构成二分图的必要条件,是两组点之间有至少一条边。
发现图很稀疏,考虑枚举所有的 连接不同组 的边检查。

先将所有连接不同组的边按照 它们两端的点的组号排序,使得连接的组相同的边 是 被连续枚举的。

在判断每组内是否为二分图后,得到的扩展域并查集的基础上,将枚举到的新的边加入图中。
若新边连接的两个点的组中出现奇环,则该边无用,直接跳过。
若加入后出现奇环,则说明连接的两组不能构成二分图,答案 -1。
当当前枚举的边 与 上一条边连接的组不同时,回退之前的连接操作。

因此需要一个可撤销并查集维护,则以上过程的复杂度为 \(O(m\log n)\)

官方题解

这个缩点有点 nb。

首先使用 DFS 黑白染色求每组内是否为二分图。

将一个内部为二分图的组压缩成两个节点一条边(黑白点各压成一个节点)。
将只有一个节点的组保留,删去内部不为二分图的组。

考虑不同组之间的点,若它们之间存在一条边,则在新图中它们对应的压缩节点之间连一条边。

检查时枚举连接不同组的边,对于连接的组相同的边放在一起,对连接的两组在加入上述边的情况下进行黑白染色。
压缩后上述过程最多只会枚举 4 个点,6 条边,所有黑白染色的总复杂度可看做 \(O(n)\)

算法总复杂度可看做 \(O(n+m)\)

//知识点:可撤销并查集,枚举 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <vector>
#define LL long long
const int kMaxn = 5e5 + 10;
//=============================================================
LL ans;
int n, m, k, cnt, col[kMaxn];
bool no[kMaxn];

int top, fa[kMaxn << 1], size[kMaxn << 1];
struct Stack {
  int u, v, fa, size;
} st[kMaxn << 1];

int e_num;
struct Edge {
  int u, v, colu, colv;
} e[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
bool CompareEdge(Edge f_, Edge s_) {
  if (f_.colu != s_.colu) return f_.colu< s_.colu;
  return f_.colv < s_.colv;
}
int Find(int x_) {
  while (x_ != fa[x_]) x_ = fa[x_];
  return x_;
}
void Union(int u_, int v_) {
  int fu = Find(u_), fv = Find(v_);
  if (size[fu] > size[fv]) std::swap(fu, fv); //没写按秩合并 
  st[++ top] = (Stack) {fu, fv, fa[fu], size[fu]};
  if (fu != fv) {
    fa[fu] = fv;
    size[fv] += size[fu];
    size[fu] = 0;
  }
}
void Restore() {
  Stack now = st[top];
  if (now.u != now.v) {
    fa[now.u] = now.fa;
    size[now.u] = now.size;
    size[now.v] -= now.size;
  }
  top --;
}
//=============================================================
int main() {
  n = read(), m = read(), cnt = k = read();
  for (int i = 1; i <= n; ++ i) col[i] = read();
  for (int i = 1; i <= 2 * n; ++ i) {
    fa[i] = i;
    size[i] = 1;
  }
  for (int i = 1; i <= m; ++ i) {
    int u_ = read(), v_ = read();
    if (col[u_] != col[v_]) {
      e[++ e_num] = (Edge) {u_, v_, col[u_], col[v_]};
      if (e[e_num].colu > e[e_num].colv) {
        std::swap(e[e_num].colu, e[e_num].colv); 
      }
      continue ;
    }
    if (no[col[u_]]) continue ; //特判,防止重复统计  
    if (Find(u_) == Find(v_)) {
      cnt --;
      no[col[u_]] = true;
      continue ;
    }
    Union(u_, v_ + n);
    Union(v_, u_ + n);
  }
  
  ans = 1ll * (cnt - 1ll) * cnt / 2ll;
  int last_top = top, flag = false;
  
  std::sort(e + 1, e + e_num + 1, CompareEdge);
  for (int i = 1; i <= e_num; ++ i) {
    Edge now = e[i];
    if (no[now.colu] || no[now.colv]) continue ;
    if (now.colu != e[i - 1].colu || now.colv != e[i - 1].colv) {
      while (top != last_top) Restore();
      flag = false;
    }
    if (flag) continue ; //特判,防止重复统计 
    if (Find(now.u) == Find(now.v)) {
      ans --;
      flag = true;
      continue ;
    }
    Union(now.u, now.v + n);
    Union(now.v, now.u + n);
  }
  printf("%lld\n", ans);
  return 0;
}
posted @ 2020-11-02 21:09  Luckyblock  阅读(204)  评论(1编辑  收藏  举报