2021牛客多校第五场 题解
作者:互联网
H题 Holding Two(构造,找规律)
给定正整数 \(n,m\),要求构造一个 \(n\) 行 \(m\) 列的矩阵 \(A\),元素为
0
或者1
,保证同一行的连续三个元素,同一列的连续三个元素,对角线上的连续三个元素,都不可能相等。\(1\leq n,m \leq 1000\)
观察样例,推一推,不难构造出这样一种矩阵:
1100110011001100....
0011001100110011....
1100110011001100....
0011001100110011....
....
代码如下:
#include<bits/stdc++.h>
using namespace std;
int n, m;
const char output[2][4] = {{'0', '0', '1', '1'}, {'1', '1', '0', '0'}};
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < m; ++j)
putchar(output[i % 2][j % 4]);
puts("");
}
return 0;
}
K题 King of Range (ST表/单调队列,双指针)
给定一个长度为 \(n\) 的数列 \(\{a_n\}\),以及 \(m\) 次询问。
每次询问给出一个整数 \(k\),问一共存在多少组不同区间,使得区间的最大值减去最小值大于 \(k\)。
\(1\leq n \leq 10^5,1\leq m \leq 200, 1\leq a_i,k \leq 10^9\)
我们可以先 \(O(n \log n)\) 建一个 ST 表,这样就可以 \(O(1)\) 查询每个区间了(我第一遍用了一个黑心模板,每次查询是 \(O(\log n)\) 的,害得我超时一次,逆天)。
这题看起来好像没法离线,所以我们必须在线处理,考虑到 \(m\) 的范围,我们不难猜测正解里的每次询问复杂度是 \(O(n)\) 的,而序列上的 \(O(n)\) 算法,很难不想到双指针。
显然,倘若区间(l,r)
满足要求,那么任何包含该区间的区间同样符合要求,因为扩展区间只会使得最大值更大,最小值更小,两者之差必然更加大于 \(k\) 。那么我们不妨双指针进行处理:固定左端点L
后,将右端点不断向右移动,当 \(\operatorname{query}(L,R)>k\) 的时候,给答案加上 \(n-R+1\),随后将左端点向右移一位,重复该流程。这个方法是 \(O(n)\) 的,最终的总复杂度为 \(O(nm)\)。
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, m, a[N];
//ST表
int lg[N];
int ST1[N][25], ST2[N][25];
void init() {
//
for (int i = 1; i <= n; i++)
lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
for (int i = 1; i <= n; i++)
lg[i]--;
//
for (int i = 1; i <= n; ++i)
ST1[i][0] = ST2[i][0] = a[i];
for (int k = 1; k <= 20; ++k)
for (int i = 1; i + (1 << k) - 1 <= n; ++i) {
ST1[i][k] = max(ST1[i][k - 1], ST1[i + (1 << (k - 1))][k - 1]);
ST2[i][k] = min(ST2[i][k - 1], ST2[i + (1 << (k - 1))][k - 1]);
}
}
inline int queryMax(int l, int r) {
int k = lg[r - l + 1];
return max(ST1[l][k], ST1[r - (1 << k) + 1][k]);
}
inline int queryMin(int l, int r) {
int k = lg[r - l + 1];
return min(ST2[l][k], ST2[r - (1 << k) + 1][k]);
}
//solve
#define LL long long
LL solve(int k) {
LL ans = 0;
for (int l = 1, r = 1; l <= n; ++l) {
bool flag = (queryMax(l, r) - queryMin(l, r) > k);
while (!flag && r <= n) {
r++;
if (r == n + 1) break;
if (queryMax(l, r) - queryMin(l, r) > k) flag = true;
}
if (r == n + 1) break;
if (flag) ans += n - r + 1;
}
return ans;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
init();
while (m--) {
int k;
scanf("%d", &k);
printf("%lld\n", solve(k));
}
return 0;
}
此外,我们也可以用单调队列来维护区间极值,但是并不如 ST 表那么直观好写(主要是我不是很会写),而且这并不能降低本题的复杂度,所以就不贴代码了。
B题 Boxes (数学:概率)
给定 \(n\) 个盒子,每个盒子里面有一个白球或者黑球,概率均为 \(\dfrac{1}{2}\)。打开第 \(i\) 个盒子需要 \(w_i\) 的代价。
我们随时可以花费 \(C\) 的代价开挂,知道此时场上还没开的盒子里面有几个黑球。
问采取最优策略的情况下,知道每个盒子装着什么球所花最小代价的数学期望。
\(1\leq n\leq 10^5,0\leq C,w_i\leq 10^9\)
我们不难想到,采取最优策略,必然是先开代价小的后开代价大的,所以我们将所有盒子按照 \(w_i\) 从小到大进行排序,复杂度为 \(O(n\log n)\)。
我们记 \(P(k)\) 为前 \(k\) 位并非完全相同,但后 \(n-k\) 位完全相同的情况(注意,这里要发现第 \(k\) 位和第 \(k+1\) 位要不相同),推出正常状态下 \(P(k)=(\dfrac{1}{2})^{n-k}\)。特别的,\(P(0)=(\dfrac{1}{2})^{n-1}\)。
得到概率公式之后,我们推出答案公式:
\[ans=\min\{C+\sum\limits_{k=0}^{n-1}P(k)*sum(1,k), sum(1,n)\} \](\(sum(1,k)\) 是开启前 \(k\) 个盒子的代价)
做一次前缀和后线性递推即可,复杂度 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n;
double C, w[N], dp[N];
int main()
{
//read
scanf("%d%lf", &n, &C);
for (int i = 1; i <= n; ++i)
scanf("%lf", &w[i]);
//sort & pre-sum
sort(w + 1, w + n + 1);
for (int i = 1; i <= n; ++i) w[i] += w[i - 1];
//dp
dp[n] = 1.0;
for (int i = n - 1; i > 0; i--)
dp[i] = dp[i + 1] / 2;
dp[0] = dp[1];
//solve
double ans = 0;
for (int k = 0; k < n; ++k)
ans += dp[k] * w[k];
printf("%lf\n", min(ans + C, w[n]));
return 0;
}
D题 Double Strings (线性DP,组合数)
给定两个字符串 \(A,B\),要求我们从中各选出一个子串,分别记为 \(s,t(s\in A,t\in B)\),且满足:
- \(|s|=|t|=n\),\(n\) 是一个正整数
- \(\exists i \in [1,n],s_i<t_i\)
- \(\forall j \in[1,i),s_j=t_j\)
求出不同组合 \((s,t)\) 的总个数(这里的不同,是指字符对应的下标不同)。
\(1\leq|A|,|B|\leq 5000\),两个字符串均只包含小写字母
简单来说,这两个字符串存在某一位具有大小关系,前面部分完全相同,后面部分随便即可。
求公共前缀数量可以预处理,是一个典型的二维 DP,记 \(f_{i,j}\) 为 \(A\) 的前 \(i\) 位, \(B\) 的前 \(j\) 位所能构成的公共子序列的数量,有如下 DP 方程,边界条件为 \(f_{0,*}=f_{*,0}=0\),复杂度 \(O(n^2)\)。
\[f_{i,j}=(f_{i-1,j}+f_{i,j-1}-f_{i-1,j-1})+\begin{cases}f_{i-1,j-1}+1, & A_i=B_j \\ 0,& A_i\not=B_j\end{cases} \]值得一提的是,本题允许这个前缀是空的,所以统计答案的时候,还要对每个 \(f_{i,j}\) 加上一才对。
后面的就是排列组合算了,假设后面 \(A\) 还剩下 \(x\) 位,\(B\) 还剩下 \(y\) 位,那么后面的可能的后缀长度为
\[g=\sum\limits_{i=0}^{\min(x,y)}\C_x^i*\C_y^i \]我们假设 \(x\leq y\),根据组合的对称性,可得
\[g=\sum\limits_{i=0}^{x}\C_x^{x-i}*\C_y^i=\C_x^x\C_y^0+\C_x^{x-1}\C_y^1+\cdots+\C_x^0\C_y^x \]这东西如果在高中做,我感觉我想的可能会快一点:二项式定理!
记 \((1+a)^x=C_x^0a^0+C_x^1a^1+\cdots+C_x^xa^x,(1+a)^y=C_y^0a^0+C_y^1a^1+\cdots+C_y^ya^y\),那么便有
\[(1+a)^x(1+a)^y=\sum\limits_{i=0}^{x+y}\sum\limits_{j=0}^{i}\C_x^j\C_y^{i-j}a^i \](附:当 \(m>n\) 时,记 \(\C_n^m=0\))
也就是说,\(a^x\) 的系数恰好就是 \(g\)。又因为 \((1+a)^x(1+a)^y=(1+a)^{x+y}\),所以我们得到
\[g=\sum\limits_{i=0}^{x}\C_x^{x-i}*\C_y^i=\C_x^x\C_y^0+\C_x^{x-1}\C_y^1+\cdots+\C_x^0\C_y^x=\C_{x+y}^x \]我们也先 \(O(n^2)\) 的预处理出所有的组合数即可。
考虑到这个数据规模,不太好用杨辉三角来预处理组合数,所以我们必须用别的方法来 \(O(1)\) 的处理它。因为需要对分数取模,所以显然是要预处理所有阶乘的乘法逆元(\(\dfrac{a}{b}\mod p=a*\operatorname{inv}(b)\mod p\))。
根据费马小定理,可知 \(\operatorname{inv}(a)=a^{p-2}\mod p\),所以我们可以先算出 \(n!\) 的逆元。
随后,我们便可以线性递推出其他阶乘的逆元:
\[n!*inv(n!)\equiv1 \bmod p\\ (n-1)!*inv((n - 1)!)\equiv1 \bmod p\\ n!*inv((n - 1)!)\equiv n \bmod p\\ n!*inv(n!)*inv((n - 1)!)\equiv n*inv(n!) \bmod p\\ inv((n - 1)!)\equiv n*inv(n!) \bmod p \]然后我们便可以 \(O(1)\) 的计算组合数了:
\[\C_n^m=n!*inv(m!)*inv((n-m)!)\bmod p \]处理好了所有的前缀和后缀,我们便可以 \(O(n^2)\) 的枚举中间断点,随后 \(O(1)\) 的查询所有的前缀和后缀,并计入答案即可。本题虽然要算的东西贼多,但是最终的总复杂度还是 \(O(n^2)\) 的。
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 5010;
const LL mod = 1e9 + 7;
//逆元法快速求组合数
LL fact[N << 1], inv[N << 1];
LL quickpow(LL a, LL b) {
LL res = 1;
while (b) {
if (b & 1) res = res * a % mod;
b >>= 1;
a = a * a % mod;
}
return res;
}
void init(int n) {
fact[0] = 1;
for (int i = 1; i <= n * 2; ++i)
fact[i] = fact[i - 1] * i % mod;
inv[n] = quickpow(fact[n], mod - 2);
for (int i = 2 * n - 1; i >= 0; --i)
inv[i] = inv[i + 1] * (i + 1) % mod;
}
LL C(LL n, LL m) {
if (m > n) return 0;
return fact[n] * inv[m] % mod * inv[n - m] % mod;
}
//
int n, m;
char s[N], t[N];
LL f[N][N];
//
int main()
{
scanf("%s%s", s + 1, t + 1);
n = strlen(s + 1), m = strlen(t + 1);
init(max(n, m) << 1);
//DP
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) {
f[i][j] = (f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + mod) % mod;
if (s[i] == t[j])
f[i][j] = (f[i][j] + f[i - 1][j - 1] + 1) % mod;
}
//clac ans
LL ans = 0;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
if (s[i] < t[j])
ans = (ans + (1 + f[i-1][j-1]) * C(n - i + m - j, m - j)) % mod;
printf("%lld", ans);
return 0;
}
J题 Jewels (二分图带权匹配)
现在有 \(n\) 个气球,第 \(i\) 个气球的坐标为 \((x_i,y_i,z_i)\)。每个气球有一个速度 \(v_i\),也就是说,时刻 \(t\) 的时候,第 \(i\) 个气球的坐标为 \((x_i,y_i,z_i+tv_i)\)。
我们在点 \((0,0,0)\) 处。每个时刻(从
0
开始)都可以拿走一个气球,同时花费 \(d^2\) 的体力(若拿走的这个气球在 \((a,b,c)\) 处,那么 \(d^2=a^2+b^2+c^2\))。问我们应该采取怎样的顺序,才能花费最少的体力,拿走所有的气球,并输出花费体力的最小值。\(1\leq n\leq 300,0\leq |x_i|,|y_i|,z_i,v_i\leq 1000\),所有数据均为整数
显然,每个点的 \(x_i,y_i\) 坐标对于答案并无影响,我们只需要正常将其加上去就好了。那么,我们便可以将这个问题从三维空间系化到了一维的数轴上。
排完序之后,我们需要使 \(\sum\limits_{i=0}^{n-1}{(z_i+i*v_i)^2}\) 最大,也就是使 \(\sum\limits_{i=0}^{n-1}{{v_i}^2i^2+2z_iv_ii}\) 最大,......
以上没啥子用,下面直接看正解:
显然,用n
次便可以拿走所有的气球,也就是说,我们需要将这几个时间和对应拿走的气球一一对应,我考试的时候想到的是基于临项交换的贪心什么什么的,但是这题让我属实开眼界了:构建一个二分图,左边是各个时间点,右边是气球,两边之间的边权则是对应时间所花的体力(一种基于时间的建模方式)。显然,这是一个二分图的带权匹配问题。(但有一说一,我记得不知道在哪看过,当数据规模在100-300
这一级的时候,要么是 Floyd 或者各种 DP,要么就是网络流/二分图了)
因为这显然是一个完备匹配,而且图十分稠密,所以可以使用 KM 算法来完成。(这题数据贼逆天,费用流和基于 DFS 的 KM 算法全部都 TLE 了,必须得用 BFS 优化过的 KM 算法,或者用其他奇奇怪怪方法对普通KM进行优化,才能完成这题)
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 310;
#define INF ((1LL) << 62)
int n;
LL dis[N][N];
//KM
int vis[N], match[N], pre[N];
LL la[N], ra[N], slack[N];
void bfs(LL u) {
LL x, y = 0, yy = 0, delta;
memset(pre, 0, sizeof(pre));
for (int i = 1; i <= n; i++)
slack[i] = INF;
match[y] = u;
while (true) {
x = match[y], delta = INF, vis[y] = 1;
for (int i = 1; i <= n; i++) {
if (vis[i]) continue;
if (slack[i] > la[x] + ra[i] - dis[x][i]) {
slack[i] = la[x] + ra[i] - dis[x][i];
pre[i] = y;
}
if (slack[i] < delta)
delta = slack[i], yy = i;
}
for (int i = 0; i <= n; i++)
if (vis[i]) {
la[match[i]] -= delta;
ra[i] += delta;
}
else slack[i] -= delta;
y = yy;
if (match[y] == -1) break;
}
while (y) {
match[y] = match[pre[y]];
y = pre[y];
}
}
LL KM()
{
memset(match, -1, sizeof(match));
memset(la, 0, sizeof(la));
memset(ra, 0, sizeof(ra));
for (int i = 1; i <= n; i++) {
memset(vis, 0, sizeof(vis));
bfs(i);
}
LL ans = 0;
for (int i = 1; i <= n; i++)
ans += dis[match[i]][i];
return -ans;
}
int main()
{
//read & build
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
LL x, y, z, v;
scanf("%lld%lld%lld%lld", &x, &y, &z, &v);
for (int t = 0; t < n; ++t)
dis[t + 1][i] = -x * x - y * y - (z + t * v) * (z + t * v);
}
//solve
printf("%lld", KM());
return 0;
}
标签:limits,牛客,int,第五场,inv,leq,题解,sum,LL 来源: https://www.cnblogs.com/cyhforlight/p/15108767.html