ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

2021牛客多校第五场 题解

2021-08-06 15:31:48  阅读:297  来源: 互联网

标签:limits 牛客 int 第五场 inv leq 题解 sum LL


比赛链接

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)\),且满足:

  1. \(|s|=|t|=n\),\(n\) 是一个正整数
  2. \(\exists i \in [1,n],s_i<t_i\)
  3. \(\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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有