2022牛客多校(二)

2022牛客多校(二)

一、比赛小结

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

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

C、Link with Nim Game

题目链接:C-Link with Nim Game

题意:

有 n 堆石头,第 i 堆石头有 ai 个。Alice 和 Bob 轮流进行操作,Alice 先。每次操作可以从某一堆石头中取出正整数个石头,无法完成操作者输。在双方都采取最优策略的情况下,必败的一方希望尽可能慢的结束游戏,必胜的一方希望尽可能快的结束游戏。

求“游戏结束时进行的操作数量”和“第一轮可能进行的操作种类数”

题解:

两个问题都不是特别好处理,我们先用 nim 推些结论。若当前局面的石头数异或和为 0 ,则为必败局面,若当前局面的石头数异或和不为 0 ,则为必胜局面。仔细分析的话,他们的选取石头还是限制性挺高的,必胜者一方面需要让当前局面变成异或和为 0 的情况,一方面想快速将石子拿完。必败者则一方面需要让当前局面变成异或和不是 0 ,一方面想慢点拿完石头。

事实上,对于必败者,他可以通过选取一堆并拿走一颗石头,进而让必胜者也只能那走一块石头,可以考虑 lowbit 来思考。这样来看,问题就明了了。

若初始局面为必败态,则 ans1 为所有石头的和, ans2 约为石头堆数(需要用 lowbit 的限制筛掉一些)。若初始局面为必胜态,则 ans1 为所有石头和 - 先手第一次拿走的,ans2 可由相应情况来计数。

代码:

对于集合 s 的元素 e 和对应的a[i] 有:a[i] -= 1 等价于 a[i] ^= e

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 1e5 + 5;
int n;
int a[maxn];
int sum, xsum;
// lose
void lose() {
  set<int> s, t;
  for (int i = 1; i <= n; i++) s.insert(a[i] ^ (a[i] - 1));
  for (int x : s)
    for (int i = 1; i <= n; i++)
      if (a[i] - (a[i] ^ x) > 1) {
        t.insert(x);
        break;
      }
  for (int x : t) s.erase(x);
  int ans = 0;
  for (int i = 1; i <= n; i++)
    if (s.count(a[i] ^ (a[i] - 1))) ans++;
  cout << sum << " " << ans << "\n";
}
// win
void win() {
  int mx = 0;
  int ans = 0;
  for (int i = 1; i <= n; i++) {
    int tmp = (a[i] ^ xsum);
    if (tmp < a[i]) {
      a[i] = a[i] - tmp;
      if (a[i] > mx) {
        mx = a[i];
        ans = 0;
      }
      if (a[i] == mx) ans++;
    }
  }
  cout << sum - mx + 1 << " " << ans << "\n";
}
signed 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 (_--) {
    cin >> n;
    sum = 0, xsum = 0;
    for (int i = 1; i <= n; i++) {
      cin >> a[i];
      sum += a[i];
      xsum ^= a[i];
    }
    if (xsum == 0)
      lose();
    else
      win();
  }
  return 0;
}

D、Link with Game Glitch

题目链接:D-Link with Game Glitch

题意:

k ∗ a i k*a_i kai 个物品 b i b_i bi 可以换取 k ∗ ω ∗ c i k*\omega *c_i kωci 个物品 d i d_i di ,求最大的 ω   ( 0 ≤ ω ≤ 1 ) \omega \ (0 \le \omega \le 1) ω (0ω1) 使得,不存在一种换取方式,取出无限种每类物品。

题解:

很简单的一道图论题,从 b i b_i bi d i d_i di 连一条权为 ω c i a i \displaystyle \omega \frac{c_i}{a_i} ωaici 的边,再对权取 − log ⁡ -\log log ,于是问题转化为二分+判负环

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 5;
const double eps = 1e-10;
int n, m;
double dis[maxn];
int cnt[maxn];
bool vis[maxn];
struct edge {
  int v;
  double w;
  edge(int _v = 0, double _w = 0) : v(_v), w(_w) {}
};
vector<edge> e[maxn];
// 有负环返回 false
bool spfa(double mid) {
  memset(dis, 0x3f, sizeof dis);
  memset(vis, 0, sizeof vis);
  memset(cnt, 0, sizeof cnt);
  queue<int> q;
  for (int i = 1; i <= n; i++) q.push(i), vis[i] = true;
  while (!q.empty()) {
    int now = q.front();
    q.pop();
    vis[now] = 0;
    for (int i = 0; i < e[now].size(); i++) {
      int v = e[now][i].v;
      double w = e[now][i].w;
      if (dis[v] > dis[now] + w + mid) {
        dis[v] = dis[now] + w + mid;
        cnt[v]++;
        if (cnt[v] == n) return false;
        if (!vis[v]) {
          q.push(v);
          vis[v] = true;
        }
      }
    }
  }
  return true;
}
int 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;
  for (int i = 1; i <= m; i++) {
    int b, d;
    double a, c;
    cin >> a >> b >> c >> d;
    e[b].push_back({d, -log(c / a)});
  }
  double l = 0, r = 1;
  while (abs(r - l) >= eps) {
    double mid = (l + r) / 2;
    if (spfa(-log(mid)))
      l = mid;
    else
      r = mid;
  }
  cout << fixed << setprecision(10) << r << endl;
  return 0;
}

E、Falfa with Substring

题目链接:E-Falfa with Substring

题意:

定义字符串 S 的 bit-value 是 S 中 bit 的子串数量,定义 F n , k F_{n,k} Fn,k 为 bit-value 为 k 且长度为 n 的字符串数量(字符集为 ‘a’ 到‘z’)

F n , 0 , F n , 1 , F n , 2 , … , F n , n F_{n,0}, F_{n,1}, F_{n,2},\dots, F_{n,n} Fn,0,Fn,1,Fn,2,,Fn,n ,(n ≤ 1 0 6 10^6 106, 模 998244353)

题解:

这道题目问的是恰好的数量,但我们会的是至少的数量,所以可以考虑容斥。我们可以考虑先构造 k 个 “bit” ,然后让剩下的去进行排列组合,那么有 f k = 2 6 n − 3 k ( n − 2 k k ) f_{k}=26^{n-3k} {{n-2k}\choose{k}} fk=26n3k(kn2k) ,实际上我们至考虑 k ≤ n 3 k\le \frac{n}{3} k3n 即可,设 g k g_k gk 为恰好 k k k 个的数量,则有:

f ( k ) = ∑ k ≤ j ≤ n ( j k ) g ( j ) \displaystyle f(k)=\sum_{k\le j\le n}{j \choose k}g(j) f(k)=kjn(kj)g(j)

g ( k ) = ∑ k ≤ j ≤ n ( − 1 ) j − k ( j k ) f ( j ) \displaystyle g(k) = \sum_{k\le j\le n}(-1)^{j-k}{j \choose k}f(j) g(k)=kjn(1)jk(kj)f(j)

稍作变形,发现有卷积的形式:

k ! g ( k ) = ∑ k ≤ j ≤ n j ! f ( j ) × ( − 1 ) j − k ( j − k ) ! \displaystyle k!g(k)=\sum_{k\le j\le n}j!f(j)\times \frac{(-1)^{j-k}}{(j-k)!} k!g(k)=kjnj!f(j)×(jk)!(1)jk

P i = i ! F ( i ) \displaystyle P_i=i!F(i) Pi=i!F(i) Q i = ( − 1 ) n − i ( n − i ) ! \displaystyle Q_i=\frac{(-1)^{n-i}}{(n-i)!} Qi=(ni)!(1)ni R n + i = i ! G i R_{n+i}=i!G_i Rn+i=i!Gi ,则:

R k = ∑ i + j = k P i × Q j \displaystyle R_{k}=\sum_{i+j=k}P_i\times Q_j Rk=i+j=kPi×Qj

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;
namespace poly {
typedef long long ll;
const int mod = 998244353;
const int maxn = 1.2e6 + 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 + 1; len <<= 1)
    ;  // 注意此处 本应为 n + m + 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
const int maxn = 1e6 + 10;
const int mod = 998244353;

int n, k;
int fc[maxn], ifc[maxn];
int f[maxn], g[maxn];

void init(int n) {
  fc[0] = 1ll;
  for (int i = 1; i <= n; i++) fc[i] = fc[i - 1] * i % mod;
  ifc[n] = poly::quickpow(fc[n], mod - 2);
  for (int i = n - 1; i >= 0; i--) ifc[i] = ifc[i + 1] * (i + 1) % mod;
}

int C(int n, int m) {
  if (m < 0 || n - m < 0) return 0;
  return fc[n] * ifc[m] % mod * ifc[n - m] % mod;
}

void solve() {
  cin >> n;
  init(n);
  k = n / 3;
  for (int i = 0; i <= k; i++)
    f[i] = C(n - 2 * i, i) * poly::quickpow(26, n - 3 * i) % mod;
  for (int i = 0, sign = 1; i <= k; i++, sign = -sign) {
    f[i] = f[i] * fc[i] % mod;
    g[k - i] = sign * ifc[i];
  }
  poly::polymul(f, g, f, 2 * k, 2 * k);
  for (int i = 0; i <= k; i++) f[i + k] = f[i + k] * ifc[i] % mod;
  for (int i = 0; i <= k; i++) cout << f[k + i] << " \n"[n == i];
  for (int i = k + 1; i <= n; i++) cout << 0 << " \n"[n == i];
}

signed 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;
}

G、Link with Monotonic Subsequence

题目链接:G-Link with Monotonic Subsequence

题意:

给定 n n n ,现让你构造一个长为 n n n 的排列,使得排列 p p p 满足: max ⁡ { L I S ( p ) , L D S ( p ) } \max \{LIS(p),LDS(p)\} max{LIS(p),LDS(p)} 为所有排列中最小的

题解:

分块构造,设块大小为 l e n = n len=\sqrt n len=n 。每个块内均为连续上升的序列,让最大的序列块在最前面,较小的序列块放后面。

Dliworth 定理

lds§ = 最小上升子序列分划数,lis§ = 最小下降子序列分划数

这个定理是组合学三大存在性定理之一,另外两个是 Ramsey 定理和 Hall 定理

【组合数学】组合存在性定理 三个组合存在性定理

⇒ l i s ( p ) × l d s ( p ) ≥ n ⇒ max ⁡ { l i s ( p ) , l d s ( p ) } ≥ ⌈ n ⌉ \Rarr lis(p)\times lds(p)\ge n\Rarr \max \{ lis(p),lds(p)\}\ge \lceil \sqrt n \rceil lis(p)×lds(p)nmax{lis(p),lds(p)}n

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
int n;
int a[maxn];
int main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  int _;
  cin >> _;
  while (_--) {
    cin >> n;
    int num = ceil(sqrt(n));
    int head = num * num - (num - 1);
    int cnt = 0;
    for (int i = 0; i < num; i++) {
      for (int j = 0; j < num; j++) {
        if (head + j > n) continue;
        a[cnt++] = head + j;
      }
      head -= num;
    }
    for (int i = 0; i < cnt; i++) cout << a[i] << " ";
    cout << endl;
  }
}

H、Take the Elevator

题目链接:H-Take the Elevator

题意:

k 层楼,n 个人,第 i 个人想从 ai 楼到 bi 楼。电梯同一时间最多载 m

人,每一时间单位走一层,可以随时向下,但只能到 1 楼才能向上。求

最短时间使得所有人到达他/她想去的楼层。

题解:

很显然的一道贪心题,有两个最优子问题:一是谁需要上电梯,二是优先让谁上电梯。第一个问题好解决,电梯上行时让需要上行的人上电梯,电梯下行时让需要下行的人上电梯,上下其实本质类似。对于第二个问题,我们拿上行举例,我们发现电梯承载人数关于时间的积分是定值,所以说电梯同时承载的人越多,时间就越少,因此,应该让所上楼层较高的人优先上楼梯。

具体地,先考虑上行,设每个人的行程为一条线段 [ l i , r i ] [l_i,r_i] [li,ri] ,若在某个楼层,与之相交的线段不超过 m 个,那么都可以上电梯,若超过了 m 个,那么让 r r r 比较大的上电梯,下行同理。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 5e5 + 10;
int n, m;
int k, a[maxn], b[maxn], tmp[maxn];
int cnt, up[maxn], down[maxn], suf[maxn];
signed main() {
  cin >> n >> m >> k;
  tmp[++cnt] = k, tmp[++cnt] = 1;
  for (int i = 1; i <= n; i++) {
    cin >> a[i] >> b[i];
    tmp[++cnt] = a[i], tmp[++cnt] = b[i];
  }
  sort(tmp + 1, tmp + cnt + 1);
  cnt = unique(tmp + 1, tmp + cnt + 1) - tmp - 1;
  for (int i = 1; i <= n; i++) {
    a[i] = lower_bound(tmp + 1, tmp + cnt + 1, a[i]) - tmp;
    b[i] = lower_bound(tmp + 1, tmp + cnt + 1, b[i]) - tmp;
    if (a[i] < b[i])
      up[a[i]]++, up[b[i]]--;
    else
      down[b[i]]++, down[a[i]]--;
  }
  for (int i = 1; i <= cnt; i++) up[i] += up[i - 1], down[i] += down[i - 1];
  int ans = 0;
  for (int i = cnt - 1; i >= 1; i--) {
    suf[i] = (max(up[i], down[i]) + m - 1) / m;
    suf[i] = max(suf[i], suf[i + 1]);
    ans += 2ll * suf[i] * (tmp[i + 1] - tmp[i]);
  }
  cout << ans;
  return 0;
}
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 2e6 + 5;
int n, m, k;
int pre1[maxn], pre2[maxn];
map<int, int> up, down;
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;
  up.clear(), down.clear();
  for (int i = 1; i <= n; i++) {
    int a, b;
    cin >> a >> b;
    if (a < b) {
      up[b]++;
      up[a]--;
    } else {
      down[a]++;
      down[b]--;
    }
  }
  int cnt = 0, rest = 0;  //电梯还剩多少位置
  for (auto it = up.rbegin(); it != up.rend(); it++) {
    while (rest < it->second) {
      pre1[++cnt] = it->first;
      rest += m;
    }
    rest -= it->second;
  }
  cnt = 0, rest = 0;
  for (auto it = down.rbegin(); it != down.rend(); it++) {
    while (rest < it->second) {
      pre2[++cnt] = it->first;
      rest += m;
    }
    rest -= it->second;
  }
  int ans = 0;
  for (int i = 1; i <= n && (pre1[i] || pre2[i]); i++)
    ans += max(pre1[i], pre2[i]) - 1;
  cout << 2 * ans << endl;
  return 0;
}

I、let fat tension

题目链接:I-let fat tension

题意:

给定 X 1 , X 2 , . . . , X n ∈ R k ,   Y 1 , Y 2 , . . . , Y n ∈ R d X_1,X_2,...,X_n \in \R^k, \ Y_1,Y_2,...,Y_n \in \R^d X1,X2,...,XnRk, Y1,Y2,...,YnRd

定义: l e ( i , j ) = X i ⋅ X j ∣ X i ∣ ⋅ ∣ X j ∣ \displaystyle le(i,j)=\frac{X_i \cdot X_j}{|X_i|\cdot |X_j|} le(i,j)=XiXjXiXj Y i n e w = ∑ j = 1 n l e ( i , j ) Y j \displaystyle Y_i^{new}=\sum_{j=1}^nle(i,j)Y_j Yinew=j=1nle(i,j)Yj

i = 1 , 2 , . . . , n i = 1, 2, ..., n i=1,2,...,n,求 Y i Y_i Yi (n ≤ 10000; k, d ≤ 50)

题解:

朴素做法的话,应该是 O ( n 2 k + n 2 d ) O(n^2k+n^2d) O(n2k+n2d) 会超时,因此我们想一下优化,计算单个 l e ( i , j ) le(i,j) le(i,j) 的值是 O ( k ) O(k) O(k) ,计算单个 l e ( i , j ) Y j le(i,j)Y_j le(i,j)Yj 的值是 O ( d ) O(d) O(d) ,因此我们思考一下它这个新定义的函数的实际涵义,发现: X ∈ R n × k ,   Y ∈ R n × d X \in \R^{n\times k}, \ Y \in \R^{n\times d} XRn×k, YRn×d ,而答案所求为 X X T Y XX^TY XXTY 于是我们发现,利用矩阵乘法的结合律时间复杂度为 O ( n k d + n k d ) O(nkd+nkd) O(nkd+nkd)

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 10005;
const int maxk = 55;
double a[maxn][maxk], b[maxk][maxn], c[maxn][maxk];
double e[maxk][maxk], f[maxn][maxk];
int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  int n, k, d;
  cin >> n >> k >> d;
  for (int i = 1; i <= n; i++) {
    double sum = 0;
    for (int j = 1; j <= k; j++) {
      cin >> a[i][j];
      sum = sum + a[i][j] * a[i][j];
    }
    sum = 1.0 / sqrt(sum);
    for (int j = 1; j <= k; j++) {
      a[i][j] = a[i][j] * sum;
      b[j][i] = a[i][j];
    }
  }
  for (int i = 1; i <= n; i++)
    for (int j = 1; j <= d; j++) cin >> c[i][j];
  for (int i = 1; i <= k; i++)
    for (int j = 1; j <= d; j++) {
      double sum = 0;
      for (int t = 1; t <= n; t++) sum = sum + b[i][t] * c[t][j];
      e[i][j] = sum;
    }
  for (int i = 1; i <= n; i++)
    for (int j = 1; j <= d; j++) {
      double sum = 0;
      for (int t = 1; t <= k; t++) sum = sum + a[i][t] * e[t][j];
      f[i][j] = sum;
    }
  for (int i = 1; i <= n; i++)
    for (int j = 1; j <= d; j++) cout << f[i][j] << " \n"[j == d];
  return 0;
}

J、Link with Arithmetic Progression

题目链接:J-Link with Arithmetic Progression

题意:

给出一个序列,求其线性回归方程

题解:

已知 ( x i , y i )   i = 1 , 2 , … , n (x_i,y_i) \ i=1,2,\dots,n (xi,yi) i=1,2,,n ,求其线性回归方程 y ^ = A x ^ + B \hat y=A\hat x+B y^=Ax^+B

x ˉ = ∑ i = 1 n x i n ,   y ˉ = ∑ i = 1 n y i n \displaystyle \=x = \frac{\sum_{i=1}^nx_i}{n}, \ \=y = \frac{\sum_{i=1}^ny_i}{n} xˉ=ni=1nxi, yˉ=ni=1nyi ,则有:

A = ∑ i = 1 n x i y i − n x ˉ y ˉ ∑ i = 1 n x i 2 − n x ˉ x ˉ \displaystyle A = \frac{\sum_{i=1}^n x_iy_i - n\=x\=y}{\sum_{i=1}^n x_i^2 - n\=x\=x} A=i=1nxi2nxˉxˉi=1nxiyinxˉyˉ B = y ˉ − A x ˉ B=\=y -A\=x B=yˉAxˉ

代码:

#include <bits/stdc++.h>
#define double long double
using namespace std;
const int maxn = 1e5 + 5;
int n;
double a[maxn];
double sx, sy, sxx, sxy, A, B;
double res;
namespace GTI {
char gc(void) {
  const int S = 1 << 16;
  static char buf[S], *s = buf, *t = buf;
  if (s == t) t = buf + fread(s = buf, 1, S, stdin);
  if (s == t) return EOF;
  return *s++;
}
int gti(void) {
  int a = 0, b = 1, c = gc();
  for (; !isdigit(c); c = gc()) b ^= (c == '-');
  for (; isdigit(c); c = gc()) a = a * 10 + c - '0';
  return b ? a : -a;
}
}  // namespace GTI
using GTI::gti;
int main() {
  int _;
  _ = gti();
  while (_--) {
    sx = sy = sxx = sxy = B = A = res = 0;
    n = gti();
    for (int i = 1; i <= n; i++) {
      a[i] = gti();
      sy += a[i];
      sx += (double)i;
      sxy += (double)i * a[i];
      sxx += (double)i * (double)i;
    }
    B = ((double)n * sxy - sx * sy) / ((double)n * sxx - sx * sx);
    A = (sy * sxx - sx * sxy) / (n * sxx - sx * sx);
    for (int i = 1; i <= n; i++)
      res += (a[i] - A - B * (double)i) * (a[i] - B * (double)i - A);
    cout << fixed << setprecision(15) << res << endl;
  }
}

K、Link with Bracket Sequence I

题目链接:K-Link with Bracket Sequence I

题意:

link 有一个括号序列 a ,它是一个有效序列的 b 的子序列,其长度分别为 n 和 m 。现在给出 a,n,m (n<=m<=200),求 b 可能的方案数

题解:

其实跟括号序列相关的题目绝大多数都是 dp 题,有时可以用组合数学里的知识对其进行优化,这道题也比较显然是个 dp 题。那么我们来提取一下有关变量,并推导其递推式。分析后我们可以得到:子序列长度 i ,原序列长度 j ,以及子序列所匹配到原序列的对应位置 k 是三个相关变量,且这三个变量足以完成此题。

具体地,设 d p i , j , k dp_{i,j,k} dpi,j,k 为 b 的前 i i i 个匹配至 a 的前 j j j 个,并且这 i i i 个序列中未能匹配的左括号有 k k k 个。然后我们发现有两种转移,

a [ j + 1 ] = ′ ( ′ a[j+1]='(' a[j+1]=( 时,可由 d p i , j , k → d p i + 1 , j + 1 , k + 1 dp_{i,j,k} \rarr dp_{i+1,j+1,k+1} dpi,j,kdpi+1,j+1,k+1

a [ j + 1 ] = ′ ) ′ a[j+1]=' )' a[j+1]=) 时,可由 d p i , j , k → d p i + 1 , j + 1 , k − 1 dp_{i,j,k} \rarr dp_{i+1,j+1,k-1} dpi,j,kdpi+1,j+1,k1

另外,当无法匹配时有如下转移:

d p i , j , k → d p i + 1 , j , k + 1 / d p i + 1 , j , k − 1 dp_{i,j,k} \rarr dp_{i+1,j,k+1} / dp_{i+1,j,k-1} dpi,j,kdpi+1,j,k+1/dpi+1,j,k1

代码:

#include <bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
const int maxn = 220;
int n, m;
char s[maxn];
int dp[maxn][maxn][maxn];
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 (_--) {
    cin >> n >> m;
    cin >> s + 1;
    memset(dp, 0, sizeof dp);
    dp[0][0][0] = 1;
    for (int i = 0; i < m; i++) {
      for (int j = 0; j <= i; j++) {
        for (int k = 0; k <= i; k++) {
          if (k > 0) {
            if (s[j + 1] == ')') {
              dp[i + 1][j + 1][k - 1] += dp[i][j][k];
              dp[i + 1][j + 1][k - 1] %= mod;
            } else {
              dp[i + 1][j][k - 1] += dp[i][j][k];
              dp[i + 1][j][k - 1] %= mod;
            }
          }
          if (s[j + 1] == '(') {
            dp[i + 1][j + 1][k + 1] += dp[i][j][k];
            dp[i + 1][j + 1][k + 1] %= mod;
          } else {
            dp[i + 1][j][k + 1] += dp[i][j][k];
            dp[i + 1][j][k + 1] %= mod;
          }
        }
      }
    }
    cout << dp[m][n][0] << endl;
  }
  return 0;
}

L、Link with Level Editor I

题目链接:L-Link with Level Editor I

题意:

有 n 个世界,每个世界有一个 m 个点 l 条有向边的图。从第一个世界的编号为 1 的点出发,每个世界可以不动或可以走一条边,到达下一个点 u,然后跳到下一个世界的点 u,如果跳到点 m 则胜利。 n ≤ 1 0 4 ,   m ≤ 2 × 1 0 3 ,   ∑ l ≤ 1 0 6 n\le 10^4, \ m\le 2\times 10^3, \ \sum l \le 10^6 n104, m2×103, l106

求一个最短的子段,使得其可胜利。

题解:

这个题一时分析不出是 dp 题还是图论题。但题里面有个条件,就是 ∑ l ≤ 1 0 6 \sum l \le 10^6 l106 ,这个信息很关键。假如我们不考虑最短,仅考虑是否连通,例如判断 ( l , r ) (l,r) (l,r) 子段是否连通 ,那么将会产生一个 O ( ∑ l ) O(\sum l) O(l) 的朴素算法,再去枚举的话,朴素算法就是 O ( n 2 ∑ l ) O(n^2\sum l) O(n2l) 十分巨大,因此我们考虑优化,有些量经过了累次计算。

我们发现其实这些节点是满足最优子结构、无后效性的,那么我们考虑如何 dp ,选取哪些量进行 dp 。先从朴素情况入手,假设第 i i i 个世界的 n o d e 1 node_1 node1 跳到第 j j j 个世界后所覆盖的点集为 s e t i , j set_{i, j} seti,j ,那么从 s e t i , j → s e t i , j + 1 set_{i,j}\rarr set_{i,j+1} seti,jseti,j+1 时,只需考虑 j → j + 1 j\rarr j+1 jj+1 世界对应的连边,我们建立一个映射表: d p i , j dp_{i,j} dpi,j 表示第 i i i 个世界的节点 j j j 最近可由哪个世界跳转而来,并将这个最小步数存至此变量中。那么有: d p i + 1 , j = min ⁡ { d p i , j , d p i , k + 1 } ,   ( i , k ) → ( i + 1 , j ) ∈ E dp_{i+1,j}=\min \{dp_{i,j},dp_{i,k}+1\}, \ (i,k)\rarr (i+1,j) \in E dpi+1,j=min{dpi,j,dpi,k+1}, (i,k)(i+1,j)E ,那么此时再利用滚动数组优化第一维即可

不过说实话,这个复杂度比较迷,不过数据比较弱,刚好跑过了。

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 5;
const int inf = 0x3f3f3f3f;
int n, m;
int dp[2][maxn];
int main() {
  //   freopen("in.txt", "r", stdin);
  //   freopen("out.txt", "w", stdout);
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  int ans = inf;
  memset(dp, 0x3f, sizeof dp);
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    dp[(i & 1) ^ 1][1] = 0;
    for (int j = 2; j <= m; j++) dp[i & 1][j] = dp[(i & 1) ^ 1][j] + 1;
    int l;
    cin >> l;
    while (l--) {
      int u, v;
      cin >> u >> v;
      dp[i & 1][v] = min(dp[i & 1][v], dp[(i & 1) ^ 1][u] + 1);
    }
    ans = min(ans, dp[i & 1][m]);
  }
  if (ans == inf)
    cout << -1 << endl;
  else
    cout << ans << endl;
  return 0;
}

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

不会做X

A、Falfa with Polygon

B、light

F、NIO with String Game

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值