2022牛客多校(一)

2022牛客多校(一)

一、比赛小结

比赛链接:"蔚来杯"2022牛客暑期多校训练营1

二、题目分析及解法(基础题)

A、Villages: Landlines

签到题

题意:

给出一个发电站,给出n-1个村庄,要建立一些电塔使得发电站与村庄连通,发电站与村庄均有接收范围 [ x i − r i , x i + r i ] [x_i-r_i, x_i+r_i] [xiri,xi+ri] ,电塔与电塔连接成本为距离差,求使图形连通的最小成本

题解:

进行区间merge即可,将前后不相交的区间距离贡献至答案

代码:

#include <bits/stdc++.h>
#define int long long
#define pii pair<int, int>
using namespace std;
const int maxn = 2e5 + 5;
int n;
vector<pii> vec, res;
signed main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> n;
  int u, v;
  cin >> u >> v;
  vec.push_back({u - v, u + v});
  for (int i = 1; i < n; i++) {
    cin >> u >> v;
    vec.push_back({u - v, u + v});
  }
  sort(vec.begin(), vec.end());
  pii last = vec[0];
  res.push_back(last);
  for (auto e : vec) {
    if (e.first <= last.second) {
      last = {min(last.first, e.second), max(last.second, e.second)};
      res.pop_back();
    } else
      last = e;
    res.push_back(last);
  }
  int ans = 0;
  for (int i = 1; i < res.size(); i++)
    ans += (res[i].first - res[i - 1].second);
  cout << ans << endl;
  return 0;
}

C、Grab the Seats!

中档题

题意:

在影院里有座位,有屏幕,定义”遮挡“为屏幕两端点与该座位形成的射线的三角区域。现在给出 k k k 个点,要求线性时间内求遮挡范围

题解:

实际上我们可以先只考虑一个屏幕的一个端点,另一个端点同理。我们来分析一下关键变量,首先是每排的第一个人所在的位子,其次是之前排所挡住当前排的第一个位子。第一个变量可以线性扫出来了,第二个变量我们稍加分析,发现可以维护其”斜率“,这个值是单调递增且易于维护的,进而整个题就做完了

另外,这个题有一个 O ( m log ⁡ k ) O(m\log k) O(mlogk) 算法,就是利用李超树直接求解

代码:

#include <bits/stdc++.h>
#define int long long
#define pii pair<int, int>
using namespace std;
const int inf = 0x3f3f3f3f3f3f3f3f;
const int maxn = 2e5 + 5;
int n, m, k, q;
pii per[maxn];
int front[maxn], ft[maxn];
struct frac {
  int a, b;
  bool operator<(const frac &f) const { return a * f.b < f.a * b; }
};
void solve() {
  fill(ft + 1, ft + m + 1, n);
  fill(front + 1, front + m + 1, n + 1);
  for (int i = 1; i <= k; i++)
    front[per[i].second] = min(front[per[i].second], per[i].first);
  frac L = {inf, 1ll};
  for (int i = 2; i <= m; i++) {
    if (L.a != inf) ft[i] = min(ft[i], (L.a * (i - 1) - 1) / L.b);
    L = min(L, (frac){front[i], i - 1ll});
  }
  frac R = {inf, 1ll};
  for (int i = m - 1; i >= 1; i--) {
    if (R.a != inf) ft[i] = min(ft[i], (R.a * (m - i) - 1) / R.b);
    R = min(R, (frac){front[i], m - i});
  }
  int ans = 0;
  for (int i = 1; i <= m; i++) {
    ft[i] = min(ft[i], front[i] - 1ll);
    ans += ft[i];
  }
  cout << ans << endl;
}
signed main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> n >> m >> k >> q;
  for (int i = 1; i <= k; i++) {
    int x, y;
    cin >> x >> y;
    per[i] = {x, y};
  }
  for (int i = 1; i <= q; i++) {
    int p, x, y;
    cin >> p >> x >> y;
    per[p] = {x, y};
    solve();
  }
  return 0;
}

D、Mocha and Railgun

签到题

题意:

给出圆,以及圆内一点,以及攻击方式,求该点攻击范围(与圆相交部分)最大值

题解:

如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dsp253xb-1662047660537)(file:///C:/Users/lenovo/AppData/Roaming/marktext/images/2022-08-23-18-01-29-image.png?msec=1661248906630)]

代码:

#include <bits/stdc++.h>
using namespace std;
int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  int _;
  cin >> _;
  while (_--) {
    double d, r;
    double x, y;
    cin >> r;
    cin >> x >> y >> d;
    double dis = sqrt(x * x + y * y);
    double ang1 = acos((dis - d) / r), ang2 = acos((dis + d) / r);
    double ans = (ang1 - ang2) * r;
    cout << fixed << setprecision(10) << ans << endl;
  }
  return 0;
}

G、lexicographically maximum

签到题

题意:

给出n,求 1~n之间字典序最大的那个数

题解:

考虑长度为len-1的 “999…9” ,将之与n进行比较讨论即可

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
char s[maxn];
int len;
int main() {
 // freopen("in.txt", "r", stdin);
 // freopen("out.txt", "w", stdout);
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> s;
  len = strlen(s);
  int t = -1;
  for (int i = 0; i < len; i++) {
    if (s[i] != '9') break;
    t = i;
  }
  if (t >= len - 2)
    cout << s;
  else
    for (int i = 0; i < len - 1; i++) cout << 9;
  return 0;
}

H、Fly

dp+NTT,中档题

题意:

n n n 种物品每种有无数个,第 i 个物品的体积为 a i a_i ai ,求解选择物品总体积不超过 M M M 的方案数。此外,有 k k k 个限制,第 i i i 个限制形如第 b i b_i bi 个物品所选的数量二进制表示下从低到高第 c i c_i ci 位必须为 0。即

{ ∑ i = 1 n a i x i ≤ M x b i & 2 c i = 0 , i = 1 , 2 , … , k \begin{cases}\displaystyle\sum_{i=1}^na_ix_i\le M \\ x_{b_i}\&2^{c_i}=0, i=1, 2, \dots, k \end{cases} i=1naixiMxbi&2ci=0,i=1,2,,k

题解:

从第一个约束式其实可以看出,这题可能是个完全背包问题。再分析一下第二个条件的话,发现 x i x_i xi 最多128位。因此,我们可以通过适当变形,把 a i x i a_ix_i aixi 拆成 128 个 0-1 背包,最后来处理这个总的 0-1 背包即可。最后,我们发现这个整体的 0-1 背包规模似乎有点大,通常的 0-1 背包是 O ( n 2 ) O(n^2) O(n2) 的,因此我们需要进行一些优化,这里,我们采用了 NTT 来进行优化。

具体地,利用类似二进制分组的方法将完全背包转化为连续做 m = log ⁡ M m=\log M m=logM 个 01 背包,其中第 i i i 个 01 背包由 n n n 个物品组成,其中第 j 个物品的体积为 2 i × a j 2^i\times a_j 2i×aj i = 0 , 1 , … , m − 1 i=0, 1, \dots ,m-1 i=0,1,,m1 。并将 k k k 个限制所对应的物品从背包中剔去。化成数学表达式为:

f = ∏ i = 1 m ∏ j ( 1 + x 2 i × a j ) = ∑ i t i x i ,   a n s = ∑ i ≤ M t i \displaystyle f= \prod_{i=1}^m\prod_j(1+x^{2^i\times a_j})=\sum_{i}t_ix^i,\ ans=\sum_{i\le M}t_i f=i=1mj(1+x2i×aj)=itixi, ans=iMti

f ( i , j , b ) f(i, j, b) f(i,j,b) 为第 i i i 个 01 背包进行完毕,选择物品总体积为 V V V 的方案数,实际上,我们在选择第 i i i 个背包的时候,关键变量有三个,一个是当前所选的物品体积,一个是之前物品所选体积,还有一个是目前物品是否有超过 M M M 的风险。对于前两个,我们发现,我们只需要维护 [ V 2 i ] \left[\displaystyle \frac{V}{2^i}\right] [2iV] 即可,而对于第三项,可以类比数位dp里的limit参数,我们新设一个变量 b b b 来表示 [ V ( m o d 2 ) i > M ( m o d 2 ) i ] \left[ V \pmod 2^i>M \pmod 2^i \right] [V(mod2)i>M(mod2)i] 即可,进而最终答案为 f ( m , 0 , 0 ) f(m,0,0) f(m,0,0) ,而 j < ∑ l = 0 i n 2 l < 2 n \displaystyle j<\sum_{l=0}^i\frac{n}{2^l}<2n j<l=0i2ln<2n

g ( i , j ) g(i, j) g(i,j) 为第 i i i 个背包选择物品总体积为 j × 2 i j\times 2^i j×2i 的方案数,那么有:

g ( i , j ) = [ x j ] ∏ ( l , i ) ∉ L i m i t ( 1 + x a l ) = [ x j ] ∏ l = 1 n ( 1 + x a l ) ∏ ( l , i ) ∈ L i m i t ( 1 + x a l ) \displaystyle g(i,j)=[x^j]\prod_{(l, i)\not \in Limit}(1+x^{a_l})=[x^j]\frac{\prod_{l=1}^n(1+x^{a_l})}{\prod_{(l, i) \in Limit}(1+x^{a_l})} g(i,j)=[xj](l,i)Limit(1+xal)=[xj](l,i)Limit(1+xal)l=1n(1+xal)

于是有递推式:

f ( i , j , b ) × g ( i + 1 , l ) → f ( i + 1 , [ j + l 2 ] , B ( ( j + l ) ( m o d 2 ) , M i , b ) \displaystyle f(i,j,b)\times g(i+1,l) \rarr f(i+1,\left[\frac{j+l}{2} \right],B((j+l)\pmod 2, M_i,b) f(i,j,b)×g(i+1,l)f(i+1,[2j+l],B((j+l)(mod2),Mi,b)

B ( x , y , z ) = [ x > y ∨ ( ( x = y ) ∧ z ] B(x,y,z)=[x>y\lor((x=y)\land z ] B(x,y,z)=[x>y((x=y)z]

代码:

#include <bits/stdc++.h>
using namespace std;
namespace poly {
typedef long long ll;
const int mod = 998244353;
const int maxn = 1e6 + 10;
ll invi[maxn];
// 记得先init()!!!!!!!!!
void init() {
  invi[1] = 1;
  for (int i = 2; i < maxn; i++) {
    invi[i] = (mod - mod / i) * invi[mod % i] % mod;
  }
}
// 快速幂
ll quickpow(ll x, ll y) {
  ll ans = 1;
  while (y) {
    if (y & 1) ans = ans * x % mod;
    y >>= 1;
    x = x * x % mod;
  }
  return ans;
}
// 二次剩余
ll modsqrt(ll x) {
  if (mod == 2) return x % mod;
  if (quickpow(x, mod - 1 >> 1) != 1) return -1;
  ll ans = 0;
  if (mod % 4 == 3)
    ans = quickpow(x, mod + 1 >> 2);
  else {
    ll b;
    for (b = rand() % mod; quickpow(b, mod - 1 >> 1) == 1; b = rand() % mod)
      ;
    ll i = mod - 1 >> 1, k = 0;
    do {
      i >>= 1;
      k >>= 1;
      if ((quickpow(x, i) * quickpow(b, k) + 1) % mod == 0) k += (mod - 1 >> 1);
    } while (i % 2 == 0);
    ans = quickpow(x, i + 1 >> 1) * quickpow(b, k >> 1) % mod;
  }
  if (ans * 2 > mod) ans = mod - ans;
  return ans;
}
// 蝴蝶变换
void change(ll* a, int len) {
  static int rev[maxn];
  rev[0] = 0;
  for (int i = 0; i < len; i++) {
    rev[i] = (rev[i >> 1] >> 1);
    if (i & 1) rev[i] |= len >> 1;
  }
  for (int i = 0; i < len; i++)
    if (i < rev[i]) swap(a[i], a[rev[i]]);
}
// flag=1 -> NTT , flag=-1 -> INTT , O(nlogn)
void NTT(ll* a, int len, int flag) {
  change(a, len);
  for (int h = 2; h <= len; h <<= 1) {
    ll uni = quickpow(3, (mod - 1) / h);  // 3 为 mod = 998244353 的原根
    if (flag == -1) uni = quickpow(uni, mod - 2);
    for (int i = 0; i < len; i += h) {
      ll w = 1;
      for (int j = i; j < i + h / 2; j++) {
        ll u = a[j] % mod;
        ll v = w * a[j + h / 2] % mod;
        a[j] = (u + v) % mod;
        a[j + h / 2] = (u - v + mod) % mod;
        w = w * uni % mod;
      }
    }
  }
  if (flag == -1) {
    ll inv = quickpow(len, mod - 2);
    for (int i = 0; i < len; i++) a[i] = a[i] * inv % mod;
  }
}
// 多项式乘法,n m 分别为 a b 最高次数
void polymul(ll* a, ll* b, ll* ans, int n, int m) {
  static ll tpa[maxn], tpb[maxn];
  memcpy(tpa, a, sizeof(ll) * (n + 1));
  memcpy(tpb, b, sizeof(ll) * (m + 1));
  int len = 1;
  for (; len < n + m + 1; len <<= 1)
    ;
  memset(tpa + n + 1, 0, sizeof(ll) * (len - n));
  memset(tpb + m + 1, 0, sizeof(ll) * (len - m));
  NTT(tpa, len, 1);
  NTT(tpb, len, 1);
  for (int i = 0; i <= len; i++) ans[i] = tpa[i] * tpb[i] % mod;
  NTT(ans, len, -1);
}

// // 多项式求逆,ans为最终答案数组, O(nlogn)
// void polyinv(ll* a, ll* ans, int len) {
//   static ll tp[maxn];
//   memset(tp, 0, sizeof(ll) * (len << 1));
//   memset(ans, 0, sizeof(ll) * (len << 1));
//   ans[0] = quickpow(a[0], mod - 2);
//   for (int h = 2; h <= len; h <<= 1) {
//     memcpy(tp, a, sizeof(ll) * h);
//     NTT(tp, h << 1, 1);
//     NTT(ans, h << 1, 1);
//     for (int i = 0; i < (h << 1); i++)
//       ans[i] = ans[i] * ((2 + mod - tp[i] * ans[i] % mod) % mod) % mod;
//     NTT(ans, h << 1, -1);
//     memset(ans + h, 0, sizeof(ll) * h);
//   }
// }
// // 多项式除法,a / b = ans1, a % b = ans2
// void polydiv(ll* a, ll* b, ll* ans1, ll* ans2, int n, int m) {
//   static ll tp0[maxn];
//   int len = 1;
//   for (; len < n - m + 1; len <<= 1)
//     ;
//   memset(tp0, 0, sizeof(ll) * len);
//   for (int i = 0; i <= n; i++) ans1[n - i] = a[i];
//   for (int i = 0; i <= m; i++) ans2[m - i] = b[i];
//   for (int i = n - m + 1; i <= m; i++) ans2[i] = 0;
//   polyinv(ans2, tp0, len);
//   polymul(ans1, tp0, tp0, n, n - m);
//   for (int i = 0; i <= n - m; i++) ans1[i] = tp0[n - m - i];
//   polymul(b, ans1, ans2, m, n - m);
//   for (int i = 0; i < m; i++) ans2[i] = (a[i] - ans2[i] + mod) % mod;
// }
// // 多项式求导
// void qiudao(ll* a, ll* ans, int len) {
//   for (int i = 1; i < len; i++) ans[i - 1] = a[i] * i % mod;
//   ans[len - 1] = 0;
// }
// // 多项式积分
// void jifen(ll* a, ll* ans, int len) {
//   for (int i = len - 1; i; i--) ans[i] = a[i - 1] * invi[i] % mod;
//   ans[0] = 0;  // 积分常数C
// }
// // 多项式ln,a[0]!=0,O(nlogn)
// void polyln(ll* a, ll* ans, int len) {
//   static ll tp1[maxn];
//   qiudao(a, tp1, len);
//   memset(tp1 + len, 0, sizeof(ll) * len);
//   polyinv(a, ans, len);
//   NTT(ans, len << 1, 1);
//   NTT(tp1, len << 1, 1);
//   for (int i = 0; i < (len << 1); i++) tp1[i] = tp1[i] * ans[i] % mod;
//   NTT(tp1, len << 1, -1);
//   jifen(tp1, ans, len);
// }
// // 多项式exp,a[0]==0,O(nlogn)
// void polyexp(ll* a, ll* ans, int len) {
//   static ll tp2[maxn];
//   memset(tp2, 0, sizeof(ll) * (len << 1));
//   memset(ans, 0, sizeof(ll) * (len << 1));
//   ans[0] = 1;
//   for (int h = 2; h <= len; h <<= 1) {
//     polyln(ans, tp2, h);
//     tp2[0] = ((a[0] + 1) % mod - tp2[0] + mod) % mod;
//     for (int i = 1; i < h; i++) tp2[i] = (a[i] - tp2[i] + mod) % mod;
//     memset(tp2 + h, 0, sizeof(ll) * h);
//     NTT(ans, h << 1, 1);
//     NTT(tp2, h << 1, 1);
//     for (int i = 0; i < (h << 1); i++) ans[i] = ans[i] * tp2[i] % mod;
//     NTT(ans, h << 1, -1);
//     memset(ans + h, 0, sizeof(ll) * h);
//   }
// }
// // 多项式开根,O(nlogn)
// void polysqrt(ll* a, ll* ans, int len) {
//   static ll tp3[maxn], tp4[maxn];
//   memset(tp3, 0, sizeof(ll) * (len << 1));
//   memset(tp4, 0, sizeof(ll) * (len << 1));
//   memset(ans, 0, sizeof(ll) * (len << 1));
//   ans[0] = modsqrt(a[0]);  // 二次剩余
//   ll inv2 = quickpow(2, mod - 2);
//   for (int h = 2; h <= len; h <<= 1) {
//     polyinv(ans, tp3, h);
//     memcpy(tp4, a, sizeof(ll) * h);
//     NTT(tp3, h << 1, 1);
//     NTT(tp4, h << 1, 1);
//     NTT(ans, h << 1, 1);
//     for (int j = 0; j < (h << 1); j++)
//       ans[j] =
//           (ans[j] * ans[j] % mod + tp4[j]) % mod * inv2 % mod * tp3[j] % mod;
//     NTT(ans, h << 1, -1);
//     memset(ans + h, 0, sizeof(ll) * h);
//   }
// }

}  // namespace poly
typedef long long ll;
const int maxn = 1e6 + 10;
const int mod = 998244353;
int a[maxn];
vector<int> v[60];
ll dp[maxn];
ll f[2][maxn], tp[2][maxn];

void solve() {
  int n, k;
  ll m;
  cin >> n >> m >> k;
  int sum = 0;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
    sum += a[i];
  }
  for (int i = 1; i <= k; i++) {
    int b, c;
    cin >> b >> c;
    v[c].push_back(b);
  }
  dp[0] = 1;
  for (int i = 1; i <= n; i++)
    for (int j = sum; j >= a[i]; j--) dp[j] = (dp[j] + dp[j - a[i]]) % mod;
  f[0][0] = 1;
  for (int i = 0; i < 60; i++) {
    sort(v[i].begin(), v[i].end());
    v[i].erase(unique(v[i].begin(), v[i].end()), v[i].end());
    for (int j : v[i])
      for (int k = a[j]; k <= sum; k++)
        dp[k] = (dp[k] - dp[k - a[j]] + mod) % mod;
    poly::polymul(f[0], dp, f[0], sum, sum);
    poly::polymul(f[1], dp, f[1], sum, sum);
    for (int j = 0; j <= 2 * sum; j++) {
      if ((j & 1) > ((m >> i) & 1)) {
        tp[1][j >> 1] = (tp[1][j >> 1] + f[0][j] + f[1][j]) % mod;
      } else if ((j & 1) == ((m >> i) & 1)) {
        tp[1][j >> 1] = (tp[1][j >> 1] + f[1][j]) % mod;
        tp[0][j >> 1] = (tp[0][j >> 1] + f[0][j]) % mod;
      } else {
        tp[0][j >> 1] = (tp[0][j >> 1] + f[0][j] + f[1][j]) % mod;
      }
    }
    for (int i = 0; i <= 2 * sum + 1; i++) {
      f[0][i] = tp[0][i];
      f[1][i] = tp[1][i];
      tp[0][i] = tp[1][i] = 0;
    }
    for (int j : v[i])
      for (int k = sum; k >= a[j]; k--) dp[k] = (dp[k] + dp[k - a[j]]) % mod;
  }
  cout << f[0][0];
}

int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  poly::init();
  solve();
  return 0;
}

还有一种做法,是标答给出的,大体思路是一样的,但使用了 分治NTT

#include <bits/stdc++.h>
#define maxn 270086

using namespace std;

typedef long long ll;

const int p = 998244353;

inline int fpow(int x, int y) {
  int ans = 1;
  while (y) {
    if (y & 1) ans = 1ll * ans * x % p;
    x = 1ll * x * x % p, y >>= 1;
  }
  return ans;
}

const int G = 3, invG = fpow(G, p - 2);
int pos[maxn], prt[2][30];

inline void init() {
  for (int i = 1, j = 2; i <= 23; i++, j <<= 1)
    prt[0][i] = fpow(G, (p - 1) / j);
  for (int i = 1, j = 2; i <= 23; i++, j <<= 1)
    prt[1][i] = fpow(invG, (p - 1) / j);
}

inline void NTT(int *a, int lim, int type) {
  for (int i = 0; i < lim; i++)
    if (i < pos[i]) swap(a[i], a[pos[i]]);
  for (int mid = 1, pw = 1; mid < lim; mid <<= 1, pw++) {
    int wn = prt[type == -1][pw];
    for (int i = mid << 1, j = 0; j < lim; j += i) {
      int w = 1;
      for (int k = 0; k < mid; k++, w = 1ll * w * wn % p) {
        int x = a[j + k], y = 1ll * w * a[j + k + mid] % p;
        a[j + k] = x + y;
        if (a[j + k] >= p) a[j + k] -= p;
        a[j + k + mid] = x - y + p;
        if (a[j + k + mid] >= p) a[j + k + mid] -= p;
      }
    }
  }
  if (type == -1) {
    int invlim = fpow(lim, p - 2);
    for (int i = 0; i < lim; i++) a[i] = 1ll * a[i] * invlim % p;
  }
}

#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)

int a[maxn], L[maxn << 2], R[maxn << 2], st[maxn], cnt, len;
int A[maxn], B[maxn];

void build(int x, int l, int r) {
  if (l == r) {
    L[x] = st[l], R[x] = st[l + 1] - 1;
    return;
  }
  int mid = l + r >> 1;
  build(ls(x), l, mid), build(rs(x), mid + 1, r);
  int deg = R[ls(x)] - L[ls(x)] + R[rs(x)] - L[rs(x)], lim = 1;
  while (lim <= deg) lim <<= 1;
  for (int i = 0; i < lim; i++)
    A[i] = B[i] = 0, pos[i] = (pos[i >> 1] >> 1) | ((i & 1) ? (lim >> 1) : 0);
  for (int i = L[ls(x)]; i <= R[ls(x)]; i++) A[i - L[ls(x)]] = a[i];
  for (int i = L[rs(x)]; i <= R[rs(x)]; i++) B[i - L[rs(x)]] = a[i];
  NTT(A, lim, 1), NTT(B, lim, 1);
  for (int i = 0; i < lim; i++) A[i] = 1ll * A[i] * B[i] % p;
  NTT(A, lim, -1);
  L[x] = L[ls(x)], R[x] = L[x] + deg;
  for (int i = L[x]; i <= R[x]; i++) a[i] = A[i - L[x]];
}

inline void add(int &x, int y) {
  x += y;
  if (x >= p) x -= p;
}

int n, k, x, y;
ll m;
int siz[maxn];
vector<int> v[maxn];
int f[2][maxn], F[2][maxn], g[maxn];

int main() {
  init();
  scanf("%d%lld%d", &n, &m, &k);
  int sum = 0;
  for (int i = 1; i <= n; i++) {
    scanf("%d", &siz[i]), sum += siz[i];
    st[++cnt] = len;
    for (int j = 0; j <= siz[i]; j++, len++) a[len] = j == 0 || j == siz[i];
  }
  st[cnt + 1] = len;
  build(1, 1, n);
  while (k--) {
    scanf("%d%d", &x, &y);
    v[y].push_back(x);
  }
  int lim = 1;
  while (lim <= 3 * sum) lim <<= 1;
  for (int i = 0; i < lim; i++)
    pos[i] = (pos[i >> 1] >> 1) | ((i & 1) ? (lim >> 1) : 0);
  f[0][0] = 1;
  for (int i = 0; m >> i; i++) {
    for (int j = 0; j <= R[1]; j++) g[j] = a[j];
    for (int j = R[1] + 1; j < lim; j++) g[j] = 0;
    sort(v[i].begin(), v[i].end());
    v[i].erase(unique(v[i].begin(), v[i].end()), v[i].end());
    for (int j = 0; j < v[i].size(); j++) {
      int x = siz[v[i][j]];
      for (int k = x; k <= R[1]; k++) add(g[k], p - g[k - x]);
    }
    NTT(f[0], lim, 1), NTT(f[1], lim, 1), NTT(g, lim, 1);
    for (int j = 0; j < lim; j++)
      f[0][j] = 1ll * f[0][j] * g[j] % p, f[1][j] = 1ll * f[1][j] * g[j] % p;
    NTT(f[0], lim, -1), NTT(f[1], lim, -1);
    for (int j = 0; j < lim; j++) {
      if ((j & 1) < ((m >> i) & 1)) {
        add(F[0][j >> 1], f[0][j]);
        add(F[0][j >> 1], f[1][j]);
      } else if ((j & 1) == ((m >> i) & 1)) {
        add(F[0][j >> 1], f[0][j]);
        add(F[1][j >> 1], f[1][j]);
      } else {
        add(F[1][j >> 1], f[0][j]);
        add(F[1][j >> 1], f[1][j]);
      }
    }
    for (int j = 0; j < lim; j++)
      f[0][j] = F[0][j], f[1][j] = F[1][j], F[0][j] = F[1][j] = 0;
  }
  printf("%d", f[0][0]);
}

I、Chiitoitsu

中档dp题

题意:

题意挺复杂的,简单说就是,麻将里有34*4张牌,这34种牌可以进一步分成4大类(Manzu, Pinzu, Souzu, Jihai)。并且,这34类牌都对应一个字符串标号。

Serval 想在这些牌中组成 Chiitoitsu ,即由7种不同类型的手牌以及每种类型的两个组成,具体游戏规则为:

初始时,Serval 有其中的13张手牌,有且仅有2个相同类型的手牌,每个回合为:先从余堆中随机拿走一张牌,如果此时达到了 Chiitoitsu ,那么游戏结束,负责就再从手牌中丢弃一张牌到棋盘上,即不到余堆中。求成功的期望数。

题解:

其实他的策略很简单,就是在回合中,保留配对的牌,丢弃不配对的牌,而且答案显然只有7种,考试时用了暴力打表,把这7种给打出来了。用的是基础的容斥原理,没有意识到这其实是一道dp题。

回过头来看的话,其实转移方程还是挺明显的,设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示余堆还剩 i i i 张牌,手上还有 j j j 张单牌,则有:

d p [ i ] [ j ] = { 1 + i − 3 i d p [ i ] [ j ] ( j = 1 ) 1 + 3 j i d p [ i − 1 ] [ j − 2 ] + i − 3 j i d p [ i − 1 ] [ j ] ( j ≥ 2 ) dp[i][j]=\begin{cases}1+\frac{i-3}{i}dp[i][j] &(j=1) \\ 1+ \frac{3j}{i}dp[i-1][j-2]+\frac{i-3j}{i} dp[i-1][j] &(j\ge2)\end{cases} dp[i][j]={1+ii3dp[i][j]1+i3jdp[i1][j2]+ii3jdp[i1][j](j=1)(j2)

代码:

// dp 做法
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
int dp[150][15];
int inv[150];
string s;
unordered_map<string, int> mp;
int quickpow(int a, int x) {
  int res = 1ll;
  while (x) {
    if (x & 1ll) res = res * a % mod;
    a = a * a % mod;
    x >>= 1;
  }
  return res;
}
int getinv(int x) { return quickpow(x, mod - 2); }
void init() {
  for (int i = 1; i < 150; i++) inv[i] = getinv(i);
  for (int i = 1; i < 150; i++) {
    dp[i][1] = (1ll + (i - 3) * inv[i] % mod * dp[i - 1][1]) % mod;
    for (int j = 2; j < 15; j++) {
      dp[i][j] = (1ll + 3ll * j * inv[i] % mod * dp[i - 1][j - 2] +
                  (i - 3ll * j) * inv[i] % mod * dp[i - 1][j]);
      dp[i][j] = (dp[i][j] + mod) % mod;
    }
  }
}
signed main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  int _, cas = 1;
  cin >> _;
  init();
  while (_--) {
    mp.clear();
    cin >> s;
    int cnt = 0;
    int len = s.size();
    for (int i = 0; i < len; i += 2) {
      string tmp = s.substr(i, 2);
      if (!mp[tmp])
        mp[tmp]++;
      else
        cnt++;
    }
    cout << "Case #" << cas++ << ": " << dp[136 - 13][13 - 2 * cnt] << endl;
  }
  return 0;
}
// 打表做法
#include <bits/stdc++.h>
using namespace std;
string s;
int ans[] = {927105416, 745749140, 707741534, 882102328,
             781250051, 100000041, 31};
unordered_map<string, int> mp;
int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  int _, cas = 1;
  cin >> _;
  while (_--) {
    mp.clear();
    cin >> s;
    int len = s.size();
    int res = 0;
    for (int i = 0; i < len; i += 2) {
      string tmp = s.substr(i, 2);
      if (!mp[tmp])
        mp[tmp]++;
      else
        res++;
    }
    cout << "Case #" << cas++ << ": " << ans[res] << endl;
  }
  return 0;
}

J、Serval and Essay

金牌题,启发式合并裸题

题意:

给定一张 n n n 个点 m m m 条边的无重边无自环的有向图,初始时可以选择一个点染黑,若某个点的起点均为黑色,则该点可以被染黑,最大化图中黑点数量

题解:

我们首先从题目中挖掘出一些简单性质,记 S u S_u Su 为起点为 u u u 所能染黑的点的集合,不难发现有如下性质:

  1. v ∈ S u v\in S_u vSu ,则 S v ∈ S u S_v \in S_u SvSu

  2. v ∈ S u v\in S_u vSu ,则必存在一条从 u u u 指向 v v v 的单向路径

  3. S v ∪ S u ≠ ∅ S_v \cup S_u \not= \varnothing SvSu= ,则 S v ⊂ S u   o r   S u ⊂ S v S_v \subset S_u \ or \ S_u \sub S_v SvSu or SuSv

其实可以直接类比 SAM 的 endpos 来思考,于是我们可以看到 S u S_u Su S v S_v Sv 的单向关系,更深刻地,其实是 S u S_u Su 及其连边组成了一棵有向树

抽象地,我们将 S u S_u Su 看作为树的节点, ∣ S u ∣ |S_u| Su 为我们的待求量, S u S_u Su 唯一地由其子树所确定,因此,我们可以采用树上启发式搜索来完成这道题目

与常规树上启发式搜索不同的是,这里的建树并未给出,并且 S u S_u Su 本身即为我们的待求量,因此需要我们来自己建树

代码:

#include <bits/stdc++.h>
#define pii pair<int, int>
using namespace std;
const int maxn = 2e5 + 10;
int n;
int pre[maxn], sz[maxn];
set<int> to[maxn], from[maxn];
void init() {  // maxn 会卡
  for (int i = 1; i <= n; i++) {
    to[i].clear();
    from[i].clear();
    pre[i] = i;
    sz[i] = 1;
  }
}
int find(int x) {
  if (pre[x] == x) return x;
  return pre[x] = find(pre[x]);
}
void merge(int x, int y) {
  x = find(x), y = find(y);
  if (x == y) return;
  if (from[x].size() > from[y].size()) swap(x, y);
  pre[x] = y;
  sz[y] += sz[x];
  vector<pii> v;
  for (auto i : from[x]) {
    from[y].insert(i);
    to[i].insert(y);
    to[i].erase(x);
    if (to[i].size() == 1) v.push_back({y, i});
  }
  for (auto e : v) merge(e.first, e.second);
}
int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  int _, cas = 1;
  cin >> _;
  while (_--) {
    cin >> n;
    init();
    for (int i = 1; i <= n; i++) {
      int k;
      cin >> k;
      for (int j = 1; j <= k; j++) {
        int x;
        cin >> x;
        to[i].insert(x);
        from[x].insert(i);
      }
    }
    for (int i = 1; i <= n; i++)
      if (to[i].size() == 1) merge(*to[i].begin(), i);
    int ans = 0;
    for (int i = 1; i <= n; i++) ans = max(ans, sz[i]);
    cout << "Case #" << cas++ << ": " << ans << endl;
  }
  return 0;
}

三、题目分析及解法(进阶题)

补完其他场再来更,咕咕咕

B、Spirit Circle Observation

E、LTCS

F、Cut

K、Villages: Landcircles

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值