Codeforces Round #551 (Div.2) 题解 (翻车记)

原文链接: http://www.cnblogs.com/Apocrypha/p/10707367.html

Codeforces Round #551 (Div.2) 题解

\(Out \ \ of \ \ Competition\) 选手翻车记……

可能这场比赛自己也没怎么认真打,然后……然后就翻车了……

\(C\)题之后的题写写题解吧:

C. Serval and Parenthesis Sequence

一句话题意:给出字符串包含'(', ')', '?',求出一种将'?'替换成'('或者')'的方案,满足这个字符串所有前缀都不是合法括号序列,整个字符串是合法括号序列。

比较显然的贪心吧……
考虑套路方法,把'('设成\(1\),')'设成\(-1\),那么做前缀和时,如果某一处的\(sum < 0\),说明这个字符串无法成为合法括号序列。否则对于每一个'?',如果可以填'(',那么就填左括号,否则填右括号。如果其中某个非结尾的地方\(sum == 0\),则说明这个前缀形成了合法的括号序,判断无解即可……

Code:

#include 
using namespace std;
const int N = 3e5 + 50;

int n, c1, c2;
char s[N];
int sum = 0;

int main() {
  scanf("%d", &n);
  if(n & 1) return puts(":("), 0;
  scanf("%s", s + 1);
  for(int i = 1; i <= n; i++) {
    if(s[i] == '(') c1++;
    else if(s[i] == ')' ) c2++;
  }
  for(int i = 1; i <= n; i++) {
    if(s[i] == '(') {
      sum ++;
    }
    else if(s[i] == ')') {
      sum --;
    }
    else {
      if(c1 == n / 2) sum--, c2++, s[i] = ')';
      else sum++, c1++, s[i] = '(';
    }
    if(i != n && sum <= 0) return puts(":("), 0;
    if(i == n && sum) return puts(":("), 0;
  }
  for(int i = 1; i <= n; i++) printf("%c", s[i]);
  puts("");
  return 0;
}

D. Serval and Rooted Tree

一句话题意:一个\(n\)个节点的树,每个节点的值是其所有孩子节点的权值的\(\min\)或者\(\max\)。假设有\(k\)个叶子节点,要求给每个叶子节点权值赋成\(1\)\(k\)中的某一个(叶子节点权值不能相同),最大化根节点权值。

又是一道水题……然而刚开始居然没有想法,看来自己真的智商下降了……写\(E\)的时候想出来了,但是\(E\)已经写到一半了,想着\(E\)分高,就打算先把\(E\)写出来,再写\(D\)。然后……就翻车了……

考虑\(dp[x]\)记录\(x\)节点能够取到的权值的排名,\(v\)\(x\)的子节点,如果\(f[x] = 1\),那么\(dp[x] = \min_v dp[v]\),否则\(dp[x] = \sum_v dp[v]\)。这个还是比较显然的,那么答案就是\(n - dp[1] + 1\)

啊啊啊自己真是菜爆了……

Code:

#include 
using namespace std;
const int N = 3e5 + 50;
const int INF = 1e7;
typedef vector Vec;

int n, c;
int f[N], dp[N];
Vec G[N];

void Dfs(int o) {
  if(!G[o].size()) return (void) ( dp[o] = 1, c++);
  for(int i = 0; i < (int) G[o].size(); i++) {
    int to = G[o][i];
    Dfs(to);
  }
  if(f[o] == 1) {
    dp[o] = INF;
    for(int i = 0; i < (int) G[o].size(); i++) {
      int to = G[o][i];
      dp[o] = min(dp[o], dp[to]);
    }
  }
  else {
    dp[o] = 0;
    for(int i = 0; i < (int) G[o].size(); i++) {
      int to = G[o][i];
      dp[o] += dp[to];
    }
  }
  return ;
}

int main() {
  scanf("%d", &n);
  for(int i = 1; i <= n; i++) scanf("%d", &f[i]);
  for(int i = 2, x; i <= n; i++) scanf("%d", &x), G[x].push_back(i);
  Dfs(1);
  int ans = c - dp[1] + 1; 
  printf("%d\n", ans);
  return 0;
}

E. Serval and Snake

一句话题意:emm……似乎有点难说明啊……将就着看一下题吧……

题解:比较好想的一个二分 + 枚举吧……首先我们从左向右枚举答案端点的列\(i\),查询\((1, 1)\)\((n, i)\)的答案,如果某次询问的答案为奇数,说明一个端点的纵坐标为\(i\),另一个端点也类似。而如果所有的询问都是偶数,那么说明两个端点的纵坐标相同。

先考虑第一种情况,现在我们知道了两个端点的纵坐标\(y\),我们就可以在横坐标上二分,如果查询\((1, y)\)\((mid, y)\)的答案为奇数,说明端点在\([l, mid]\)里,否则在\([mid + 1, r]\)里。这样加上第一次的枚举,总的次数为\(n + log_2 n = 1010\)

然后是第二种情况,我们继续枚举行,查询\((1, 1)\)\((i, n)\)的答案,如果某次的答案为奇数,说明某个端点的横坐标为\(i\),另一个端点也类似。然后列仍然用二分即可。粗略的考虑一下这样的查询的个数最多似乎是\(2 * n + 2 * log_n = 2020\)次,超出了限制……但是实际上第一次枚举最多只会有\(n - 1\),第二次枚举最多只会有\(n - 2\),而两次二分实际上可以只需要一次,所以询问次数不会超过\(2007\)次。而且这个很难卡的……

Code:

#include 
using namespace std;
const int N = 1005;

struct Point {
  int x, y;
  void read() {
    scanf("%d%d", &x, &y);
  }
};

typedef pair P;
#define fi first
#define se second
#define mk make_pair

int n;

int Ask(Point a, Point b) {
  printf("? %d %d %d %d\n", a.x, a.y, b.x, b.y);
  fflush(stdout);
  int x;
  scanf("%d", &x);
  if(x == -1) assert(0);
  return x;
}

void Answer(Point a, Point b) {
  printf("! %d %d %d %d\n", a.x, a.y, b.x, b.y);
  exit(0);
}

int main() {
  scanf("%d", &n);
  Point Ansx, Ansy;
  int l = 1, r = n;
  int f = 0;
  for(int i = 1; i <= n; i++) {
    Point A, B;
    A.x = A.y = 1; B.x = n, B.y = i;
    int c = Ask(A, B);
    if(c & 1) {
      f = 1;
      Ansx.y = i;
      break;
    }
  }
  if(!f) {
    for(int i = 1; i <= n; i++) {
      Point A, B;
      A.x = A.y = 1; B.x = i, B.y = n;
      int c = Ask(A, B);
      if(c & 1) {
        Ansx.x = i;
        break;
      }
    }
    int l = 1, r = n;
    while(l < r) {
      if(l + 1 == r) {
        Point A, B;
        A.x = A.y = 1; B.x = Ansx.x, B.y = l;
        int c = Ask(A, B);
        if(c & 1) r--;
        else l++;
        break;
      }
      int mid = (l + r) >> 1;
      Point A, B;
      A.x = A.y = 1; B.x = Ansx.x, B.y = mid;
      int c = Ask(A, B);
      if(!(c & 1)) l = mid + 1;
      else r = mid;
    }
    Ansx.y = Ansy.y = l;
    l = Ansx.x + 1, r = n;
    while(l < r) {
      if(l + 1 == r) {
        Point A, B;
        A.x = r, A.y = 1; B.x = B.y = n;
        int c = Ask(A, B);
        if(c & 1) l++;
        else r--;
        break;
      }
      int mid = (l + r) >> 1;
      Point A, B;
      A.x = mid, A.y = 1; B.x = B.y = n;
      int c = Ask(A, B);
      if(!(c & 1)) r = mid - 1;
      else l = mid;
    }
    Ansy.x = l;
    Answer(Ansx, Ansy);
  }
  else {
    for(int i = n; i >= 1; i--) {
      Point A, B;
      A.x = 1, A.y = i; B.x = B.y = n;
      int c = Ask(A, B);
      if(c & 1) {
        Ansy.y = i;
        break;
      }
    }
    int l = 1, r = n;
    while(l < r) {
      if(l + 1 == r) {
        Point A, B;
        A.x = 1; A.y = Ansx.y; B.x = l, B.y = Ansx.y;
        int c = Ask(A, B);
        if(c & 1) r--;
        else l++;
        break;
      }
      int mid = (l + r) >> 1;
      Point A, B;
      A.x = 1, A.y = Ansx.y; B.x = mid, B.y = Ansx.y;
      int c = Ask(A, B);
      if(c & 1) r = mid;
      else l = mid + 1;
    }
    Ansx.x = l;
    l = 1, r = n;
    while(l < r) {
      if(l == r) {
        Point A, B;
        A.x = 1, A.y = Ansy.y; B.x = l; B.y = Ansy.y;
        int c = Ask(A, B);
        if(c & 1) r--;
        else l++;
        break;
      }
      int mid = (l + r) >> 1;
      Point A, B;
      A.x = 1, A.y = Ansy.y; B.x = mid, B.y = Ansy.y;
      int c = Ask(A, B);
      if(c & 1) r = mid;
      else l = mid + 1;
    }
    Ansy.x = l;
    Answer(Ansx, Ansy);
  }
  return 0;
}

F. Serval and Bonus Problem

一句话题意:在一个长度为\(l\)的线段上随机\(n\)条线段,求长度为\(l\)的线段中至少被\(k\)个区间覆盖的线段长度之和……

考虑随机\(n\)条线段,就相当于随机\(2n\)个点,而我们就可以把\(l\)均等分\(2n + 1\)段,然后把随机点看成随机这些均等分的点,这样对于题目的答案是不变的。那么我们考虑用\(dp\),计算出有多少条线段被\(k\)个区间覆盖,那么最后单独计算一下贡献即可。

我们记\(dp[i][j]\)表示考虑了前\(i\)个端点,当前没有匹配上的左端点有\(j\)个,那么转移就很显然了,下一个要么是左端点,要么是右端点,注意边界条件判一下……

然后我们开始计算贡献,我们枚举每一个长度为\(\frac{l}{2n + 1}\)区间,那么这个区间被\(k\)个区间覆盖的方案数就是\(f[i][j] * f[n * 2 - i][j] * j!\)。最后由于求的是期望,再除以\(f[n * 2][0]\)即可。

Code:

#include 
using namespace std;
typedef long long ll;
const int N = 5005;
const int Md = 998244353;

inline int Add(const int &x, const int &y) { return (x + y >= Md) ? (x + y - Md) : (x + y); }
inline int Sub(const int &x, const int &y) { return (x - y < 0) ? (x - y + Md) : (x - y); }
inline int Mul(const int &x, const int &y) { return (ll)x * y % Md; }
int Powe(int x, int y = Md - 2) {
  int ans = 1;
  for(; y; y >>= 1, x = (ll)x * x % Md) if(y & 1) ans = 1ll * ans * x % Md;
  return ans;
}

int n, k, l;
int fac[N], Inv[N], f[N][N];

void Init() {
  fac[0] = Inv[0] = 1;
  for(int i = 1; i < N; i++) fac[i] = Mul(fac[i - 1], i);
  Inv[N - 1] = Powe(fac[N - 1]);
  for(int i = N - 2; i; i--) Inv[i] = Mul(Inv[i + 1], i + 1);
}

int main() {
  scanf("%d%d%d", &n, &k, &l);
  Init();
  f[0][0] = 1;
  for(int i = 0; i <= (n << 1); i++) {
    for(int j = 0; j <= min(n, i); j++) {
      f[i + 1][j + 1] = Add(f[i + 1][j + 1], f[i][j]);
      if(j) f[i + 1][j - 1] = Add(f[i + 1][j - 1], Mul(f[i][j], j));
    }
  }
  int ans = 0;
  int len = Mul(l, Powe((n << 1) + 1));
  for(int i = 1; i <= (n << 1) - 1; i++) {
    for(int j = k; j <= min(n, i); j++) ans = Add(ans, Mul(f[i][j], Mul(f[(n << 1) - i][j], fac[j])));
  }
  ans = Mul(ans, Powe(f[n << 1][0]));
  ans = Mul(ans, len);
  printf("%d\n", ans);
  return 0;
}

转载于:https://www.cnblogs.com/Apocrypha/p/10707367.html

你可能感兴趣的:(Codeforces Round #551 (Div.2) 题解 (翻车记))