CodeForces 997 简要题解

Convert to Ones

翻转操作实际就是合并两段 0 0 ,特判全 1 的情况,假设有 k k 0 ,答案是 (k1)min(x,y)+y ( k − 1 ) min ( x , y ) + y

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 300005;

int n, x, y;
char s[N];

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  scanf("%d %d %d %s", &n, &x, &y, s);
  int result = 0;
  for (int i = 0; i < n; ++i) {
    if (s[i] == '0') {
      while (i < n && s[i] == '0') {
        ++i;
      }
      ++result;
    }
  }
  if (!result) {
    puts("0");
  } else {
    printf("%lld\n", (ll)min(x, y) * (result - 1) + y);
  }
  return 0;
}

Roman Digits

原问题等价于求不同的 4a+9b+49c 4 a + 9 b + 49 c 个数,要求 a+b+cn a + b + c ≤ n 。注意到 n n 大于某个定值的时候答案是线性增长的,暴力较小的值就行了。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int answer[] = {0, 4, 10, 20, 35, 56, 83, 116, 155, 198, 244, 292};

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  int n;
  scanf("%d", &n);
  if (n < 12) {
    printf("%d\n", answer[n]);
  } else {
    printf("%lld\n", 49ll * (n - 11) + answer[11]);
  }
  return 0;
}

Sky Full of Stars

考虑计算每行每列都有至少 2 种颜色的方案数。如果不考虑行的要求,方案数就是 (3n3)n ( 3 n − 3 ) n ,否则对行容斥,枚举有 i i 行只有一种颜色,如果这些行都是同色的,那么每一列都不能全是这种颜色,方案数是 (ni)×3(3ni1)n ,否则方案数是 (ni)×(3i3)3n(ni) ( n i ) × ( 3 i − 3 ) 3 n ( n − i )

answer=3n2(3n3)nni=1(1)i(ni)(3(3ni1)n+(3i3)3n(ni)) a n s w e r = 3 n 2 − ( 3 n − 3 ) n − ∑ i = 1 n ( − 1 ) i ( n i ) ( 3 ( 3 n − i − 1 ) n + ( 3 i − 3 ) 3 n ( n − i ) )

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 1000005;
const int mod = 998244353;

int n, fac[N], ifac[N];

int add(int x, int y) {
  x += y;
  if (x >= mod) {
    x -= mod;
  }
  return x;
}

int sub(int x, int y) {
  x -= y;
  if (x < 0) {
    x += mod;
  }
  return x;
}

int mul(int x, int y) {
  return (ll)x * y % mod;
}

int power(int x, int y) {
  int result = 1;
  for (; y; y >>= 1, x = mul(x, x)) {
    if (y & 1) {
      result = mul(result, x);
    }
  }
  return result;
}

int binom(int x, int y) {
  return mul(fac[x], mul(ifac[y], ifac[x - y]));
}

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  scanf("%d", &n);
  fac[0] = fac[1] = ifac[0] = ifac[1] = 1;
  for (int i = 2; i <= n; ++i) {
    fac[i] = mul(fac[i - 1], i);
    ifac[i] = mul(mod - mod / i, ifac[mod % i]);
  }
  for (int i = 2; i <= n; ++i) {
    ifac[i] = mul(ifac[i], ifac[i - 1]);
  }
  int answer = power(power(3, n), n);
  answer = sub(answer, power(sub(power(3, n), 3), n));
  for (int i = 1; i <= n; ++i) {
    int coef = 0;
    coef = add(coef, mul(3, power(sub(power(3, n - i), 1), n)));
    coef = add(coef, mul(sub(power(3, i), 3), power(power(3, n - i), n)));
    coef = mul(coef, binom(n, i));
    if (i & 1) {
      answer = add(answer, coef);
    } else {
      answer = sub(answer, coef);
    }
  }
  printf("%d\n", answer);
  return 0;
}

Cycles in product

f(i,j) f ( i , j ) 表示从第一棵树中 i i 出发走 j 步回到自己的方案数, g(i,j) g ( i , j ) 表示第二棵树中 i i 出发走 j 步回到自己的方案数,那么 answer=mi=0(mi)xf(x,i)yf(y,mi) a n s w e r = ∑ i = 0 m ( m i ) ∑ x f ( x , i ) ∑ y f ( y , m − i )

考虑求 f(i,j) f ( i , j ) ,枚举第一次走向哪棵子树,在子树内走了多少步转移。从下往上,从上往下分别DP一次就能求答案了。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 4005;
const int M = 80;
const int mod = 998244353;

int binom[M][M];

int add(int x, int y) {
  x += y;
  if (x >= mod) {
    x -= mod;
  }
  return x;
}

int sub(int x, int y) {
  x -= y;
  if (x < 0) {
    x += mod;
  }
  return x;
}

int mul(int x, int y) {
  return (ll)x * y % mod;
}

struct tree_t {
  int n, m, dp[N][M], up[N][M], sum[N][M], down[N][M];
  vector<int> adj[N];

  void dpu(int x, int parent) {
    for (auto y : adj[x]) {
      if (y != parent) {
        dpu(y, x);
        for (int i = 0; i <= m; i += 2) {
          sum[x][i] = add(sum[x][i], up[y][i]);
        }
      }
    }
    up[x][0] = 1;
    for (int i = 2; i <= m; i += 2) {
      for (int j = 0; j <= i - 2; j += 2) {
        up[x][i] = add(up[x][i], mul(sum[x][j], up[x][i - j - 2]));
      }
    }
  }

  void dpd(int x, int parent) {
    for (int i = 0; i <= m; i += 2) {
      sum[x][i] = add(sum[x][i], down[x][i]);
    }
    dp[x][0] = 1;
    for (int i = 2; i <= m; i += 2) {
      for (int j = 0; j <= i - 2; j += 2) {
        dp[x][i] = add(dp[x][i], mul(sum[x][j], dp[x][i - j - 2]));
      }
    }
    for (auto y : adj[x]) {
      if (y != parent) {
        for (int i = 0; i <= m; i += 2) {
          sum[x][i] = sub(sum[x][i], up[y][i]);
        }
        down[y][0] = 1;
        for (int i = 2; i <= m; i += 2) {
          for (int j = 0; j <= i - 2; j += 2) {
            down[y][i] = add(down[y][i], mul(sum[x][j], down[y][i - j - 2]));
          }
        }
        for (int i = 0; i <= m; i += 2) {
          sum[x][i] = add(sum[x][i], up[y][i]);
        }
        dpd(y, x);
      }
    }
  }

  void init() {
    for (int i = 1; i < n; ++i) {
      int x, y;
      scanf("%d %d", &x, &y);
      adj[x].push_back(y);
      adj[y].push_back(x);
    }
    dpu(1, 0);
    dpd(1, 0);
  }
} p, q;

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  int m;
  scanf("%d %d %d", &p.n, &q.n, &m);
  p.m = q.m = m;
  if (m & 1) {
    puts("0");
    return 0;
  }
  p.init();
  q.init();
  for (int i = 0; i <= m; ++i) {
    binom[i][0] = 1;
    for (int j = 1; j <= i; ++j) {
      binom[i][j] = add(binom[i - 1][j], binom[i - 1][j - 1]);
    }
  }
  int answer = 0;
  for (int i = 0; i <= m; i += 2) {
    int x = 0, y = 0;
    for (int j = 1; j <= p.n; ++j) {
      x = add(x, p.dp[j][i]);
    }
    for (int j = 1; j <= q.n; ++j) {
      y = add(y, q.dp[j][m - i]);
    }
    answer = add(answer, mul(binom[m][i], mul(x, y)));
  }
  printf("%d\n", answer);
  return 0;
}

Good Subsegments

如果不要求子区间的话,考虑 (maxmin)(rl) ( m a x − m i n ) − ( r − l ) ,不难发现只需要用线段树维护区间最小值和最小值个数就行了。那么这里就是求区间历史最小值个数之和,线段树每个节点维护区间最小值,最小值个数,上次更新的时间,答案,以及对应的标记就可以了。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 120005;

struct node_t {
  int tag, time, value, number;
  ll answer;
} tree[N << 2];

int n, m, top_max, top_min, a[N], stack_max[N], stack_min[N];
vector<pair<int, int>> queries[N];
ll answer[N];

void add_value(int x, int value) {
  tree[x].value += value;
  tree[x].tag += value;
}

void add_time(int x, int value) {
  tree[x].time += value;
  tree[x].answer += (ll)value * tree[x].number;
}

void push_up(int x) {
  tree[x].value = min(tree[x << 1].value, tree[x << 1 | 1].value);
  tree[x].answer = tree[x << 1].answer + tree[x << 1 | 1].answer;
  tree[x].number = 0;
  if (tree[x].value == tree[x << 1].value) {
    tree[x].number += tree[x << 1].number;
  }
  if (tree[x].value == tree[x << 1 | 1].value) {
    tree[x].number += tree[x << 1 | 1].number;
  }
}

void push_down(int x) {
  if (tree[x].tag) {
    add_value(x << 1, tree[x].tag);
    add_value(x << 1 | 1, tree[x].tag);
    tree[x].tag = 0;
  }
  if (tree[x].time) {
    if (tree[x].value == tree[x << 1].value) {
      add_time(x << 1, tree[x].time);
    }
    if (tree[x].value == tree[x << 1 | 1].value) {
      add_time(x << 1 | 1, tree[x].time);
    }
    tree[x].time = 0;
  }
}

void build(int x, int l, int r) {
  tree[x].value = l;
  tree[x].number = 1;
  if (l == r) {
    return;
  }
  int mid = l + r >> 1;
  build(x << 1, l, mid);
  build(x << 1 | 1, mid + 1, r);
}

void modify(int x, int l, int r, int ql, int qr, int value) {
  if (l == ql && r == qr) {
    add_value(x, value);
    return;
  }
  int mid = l + r >> 1;
  push_down(x);
  if (qr <= mid) {
    modify(x << 1, l, mid, ql, qr, value);
  } else if (ql > mid) {
    modify(x << 1 | 1, mid + 1, r, ql, qr, value);
  } else {
    modify(x << 1, l, mid, ql, mid, value);
    modify(x << 1 | 1, mid + 1, r, mid + 1, qr, value);
  }
  push_up(x);
}

ll query(int x, int l, int r, int ql, int qr) {
  if (l == ql && r == qr) {
    return tree[x].answer;
  }
  int mid = l + r >> 1;
  push_down(x);
  if (qr <= mid) {
    return query(x << 1, l, mid, ql, qr);
  } else if (ql > mid) {
    return query(x << 1 | 1, mid + 1, r, ql, qr);
  } else {
    return query(x << 1, l, mid, ql, mid) + query(x << 1 | 1, mid + 1, r, mid + 1, qr);
  }
}

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  scanf("%d", &n);
  for (int i = 1; i <= n; ++i) {
    scanf("%d", &a[i]);
  }
  scanf("%d", &m);
  for (int i = 1; i <= m; ++i) {
    int l, r;
    scanf("%d %d", &l, &r);
    queries[r].push_back(make_pair(l, i));
  }
  build(1, 1, n);
  for (int i = 1; i <= n; ++i) {
    add_value(1, -1);
    while (top_max && a[stack_max[top_max]] < a[i]) {
      modify(1, 1, n, stack_max[top_max - 1] + 1, stack_max[top_max], a[i] - a[stack_max[top_max]]);
      --top_max;
    }
    stack_max[++top_max] = i;
    while (top_min && a[stack_min[top_min]] > a[i]) {
      modify(1, 1, n, stack_min[top_min - 1] + 1, stack_min[top_min], a[stack_min[top_min]] - a[i]);
      --top_min;
    }
    stack_min[++top_min] = i;
    add_time(1, 1);
    for (auto q : queries[i]) {
      answer[q.second] = query(1, 1, n, q.first, i);
    }
  }
  for (int i = 1; i <= m; ++i) {
    printf("%lld\n", answer[i]);
  }
  return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值