2021牛客暑期多校训练营4
B(生成函数)
题目链接
⭐⭐⭐⭐
题目:
给出一个随机数生成器,按指定权重随机生成\(1\sim n\),如果生成的数大于等于已生成数的最大值,则继续生成,否则结束生成,并造成已生成数量平方的贡献,问该贡献的期望
解析:
设\(f(x)=\sum_{i=0}^\infin P(len> i)x^i\)为数列长度大于\(i\)对应的生成函数,\(g_i(x)=\sum_{j=0}^\infin p_i^jx^j\)为数列数字\(i\)生成\(j\)次概率对应的生成函数。
对于任意一个结果数列,出现这个结果中所有数字的概率为\(\prod_{i=1}^np_i^{cnt_i}\),所以根据生成函数的乘法意义(\(x\)的指数位就是数列的最小长度,也是各个数字已确定出现次数之和)可以得到\(f(x)=\prod_{i=1}^ng_i(x)=\prod_{i=1}^n\frac{1}{1-p_ix},f'(x)=\sum_{i=1}^n\frac{p_i}{1-p_ix}\)
最终所需结果为\(\sum_{i=0}^\infin P(len=i)i^2\),进行如下转化
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 105, mod = 998244353;
ll w[maxn];
ll ksm(ll a, ll b) {
ll ans = 1;
while (b) {
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
int main() {
int n;
ll sum = 0;
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%lld", &w[i]);
sum = (sum + w[i]) % mod;
}
ll isum = ksm(sum, mod - 2);
for (int i = 1; i <= n; ++i)
w[i] = w[i] * isum % mod;
ll a = 1, b = 0;
ll t;
for (int i = 1; i <= n; ++i)
t = ksm((1 - w[i] + mod) % mod, mod - 2) % mod, a = a * t % mod, b = (b + w[i] * t) % mod;
printf("%lld", (2 * a * b + a) % mod);
}
C(构造)
题目链接
⭐⭐
题目:
给出三个字符串的长度\(n\),以及彼此之间的LCS \(a,b,c\),构造出符合条件的字符串,如果不能构造则输出"NO!"
解析:
不妨设三个LCS为\(a\le b\le c\),则\(c\ge a+b-n\)。因为若\(a\)代表第一个与第二个字符串的LCS,\(b\)代表第二个和第三个字符串的LCS,则第一个字符串与第三个字符串LCS的最小情况,则是\(a,b\)中出现了第二个字符串中的所有字符,那多余的字符则一定是第一个与第三个字符串所共有的
一种构造方法是,第一个字符串为\(a\dots a\),第二个字符串为\(\underbrace{a\dots a}_{a}b\dots b\),第三个字符串为\(\underbrace{a\dots a}_c\underbrace{b\dots b}_{b-a}c\dots c\)
#include<bits/stdc++.h>
using namespace std;
int x, y, z;
int n;
char ss[3][1005];
char* s[3];
int main() {
s[0] = ss[0], s[1] = ss[1], s[2] = ss[2];
scanf("%d%d%d%d", &x, &y, &z, &n);
int mx = max({ x,y,z });
int mn = min({ x,y,z });
int mid = x + y + z - mx - mn;
if (mx + mid - mn > n) printf("NO");
else {
if (y < z) {
swap(y, z);
swap(s[0], s[1]);
if (y < x) {
swap(y, x);
swap(s[2], s[0]);
}
}
else if (y < x) {
swap(y, x);
swap(s[2], s[0]);
}
for (int i = 0; i < n; ++i)
s[0][i] = 'a';
for (int i = 0; i < x; ++i)
s[1][i] = 'a';
for (int i = x; i < n; ++i)
s[1][i] = 'b';
for (int i = 0; i < z; ++i)
s[2][i] = 'a';
for (int i = 0; i < y - min(x, z); ++i)
s[2][z + i] = 'b';
for (int i = z + y - min(x, z); i < n; ++i)
s[2][i] = 'c';
printf("%s\n%s\n%s", ss[0], ss[1], ss[2]);
}
}
E(区间异或)
题目链接
⭐⭐⭐
题目:
给出一个含有\(n\)个节点的树,每个节点的值不确定,但有对应的范围\([l_i,r_i]\),且每条边上相邻结点的异或值已知,求解树上节点的可能值的数量
解析:
假设第一个节点的值为0,可以计算出所有节点对应的值\(w_i\),且如果第一个节点的值更改为\(x\),其他的节点的值将要变为\(w_i\oplus x\),那题目等价为对于所有的节点\(w_i\oplus x\)在对应范围内时有多少种\(x\)
如果枚举第一个节点的范围,并且每次\(O(n)\)的检验,时间复杂度会爆,所以考虑根据每个节点的范围区间,找出其异或\(w_i\)后的范围区间,求交集,这也就是第一个节点的可取值范围区间
对于\([XXX\underbrace{0\dots0}_{len},XXX\underbrace{1\dots1}_{len}]\)异或任意一个\(w\)(对应的二进制长度为\(l\)),\(w\)中小于等于\(len\)的二进制部分与上述区间进行异或后仍然可以得到一个同样的区间,大于\(len\)的部分直接与\(XXX\)进行异或及可以得到区间端点值的前半部分,这样就可以借助类似线段树进行更新时区间划分的思想,获得每个节点对应的异或区间
求交集时,可以考虑对左右端点分别赋值\(1,-1\),从小到大遍历端点,当端点和为\(n\)时,下一个必定为\(-1\)也就是右端点,加上这部分区间的贡献。原因在于原区间对应的异或区间虽然是分散的,但不会有交集。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int l[maxn], r[maxn];
vector<pair<int, int>> e[maxn], seg;
int w[maxn];
void insert(int l, int r, int ql, int qr, int w) {
if (ql <= l && r <= qr) {
int t = l ^ (~(r - l - 1) & w);
seg.push_back({ t,1 });
seg.push_back({ t + r - l,-1 });
}
else if (l < qr && ql < r) {
insert(l, (l + r) / 2, ql, qr, w);
insert((l + r) / 2, r, ql, qr, w);
}
}
void dfs(int x, int last) {
insert(0, 1 << 30, l[x], r[x] + 1, w[x]);
for (auto& i : e[x]) {
if (i.first == last) continue;
w[i.first] = w[x] ^ i.second;
dfs(i.first, x);
}
}
int main() {
int n, u, v, t;
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d%d", l + i, r + i);
for (int i = 1; i < n; ++i) {
scanf("%d%d%d", &u, &v, &t);
e[u].push_back({ v,t });
e[v].push_back({ u,t });
}
dfs(1, 0);
sort(seg.begin(), seg.end());
int ans = 0, now = 0;
for (int i = 0; i < seg.size(); ++i) {
now += seg[i].second;
if (now == n) ans += seg[i + 1].first - seg[i].first;
}
printf("%d", ans);
}
F(水题)
题目链接
⭐⭐
题目:
有两个人玩游戏,每次可以删除图中的一条边,或者删除一个不成环的连通分量,当一方无法操作时,另一方获得胜利,给出图,求出胜利方
解析:
方法一:
对于第一种操作,会使得边数减1,第二种操作会使得点数减\(k\),边数减\(k-1\),每次减的都是奇数,所以只需要判断\(n+m\)的奇偶性
方法二:
还有一种想法是,对于每个环需要一次拆边操作才能变为非环,对于每个连通块也至少需要一次可以拆完所有的边,那么根据连通块加环数的奇偶性也可以判断胜方(胜利者在另一方未拆环或者拆连通块时也不进行对应操作,保持奇偶性不变)
#include<bits/stdc++.h>
using namespace std;
int main() {
int n, m;
int a, b;
scanf("%d%d", &n, &m);
printf("%s", n + m & 1 ? "Alice" : "Bob");
}
I(思维)
题目链接
⭐⭐⭐
题目:
给出\(1\sim n\)的排列,可以对每个位置的元素加一次0或者1,请求出操作后数列的最小逆序对数
解析:
首先求出当前排列对应的逆序对数,考虑+1操作,最多能影响到的也只有\((x+1,x)\)这样的逆序对,所以统计相差为1(连续数)的构成逆序对的数量,除以2向下取整即为最多减少的逆序对数量。除以2的原因在于,当某个逆序对减少时必须得保持一个不动,另一个加一的状态,例如\((3,2,1)\)只能变为\((3,2,2)\)或者\((3,3,1)\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 8005;
int du[maxn];
int edge[maxn][maxn];
namespace GenHelper
{
unsigned z1, z2, z3, z4, b, u;
unsigned get()
{
b = ((z1 << 6) ^ z1) >> 13;
z1 = ((z1 & 4294967294U) << 18) ^ b;
b = ((z2 << 2) ^ z2) >> 27;
z2 = ((z2 & 4294967288U) << 2) ^ b;
b = ((z3 << 13) ^ z3) >> 21;
z3 = ((z3 & 4294967280U) << 7) ^ b;
b = ((z4 << 3) ^ z4) >> 12;
z4 = ((z4 & 4294967168U) << 13) ^ b;
return (z1 ^ z2 ^ z3 ^ z4);
}
bool read()
{
while (!u)
u = get();
bool res = u & 1;
u >>= 1;
return res;
}
void srand(int x)
{
z1 = x;
z2 = (~x) ^ 0x233333333U;
z3 = x ^ 0x1234598766U;
z4 = (~x) + 51;
u = 0;
}
}
using namespace GenHelper;
int main()
{
int n, seed;
scanf("%d%d", &n, &seed);
srand(seed);
for (int i = 1; i <= n; i++)
{
for (int j = i + 1; j <= n; j++)
{
edge[j][i] = edge[i][j] = read();
if (edge[i][j] == 1)
{
du[i]++;
du[j]++;
}
}
}
ll ans = (1ll) * n * (n - 1) * (n - 2) / 6;
ll sum = 0;
for (int i = 1; i <= n; i++)
sum += 1ll * du[i] * (n - 1 - du[i]);
ans = ans - sum / 2;
printf("%lld\n", ans);
}
F(水题)
题目链接
⭐⭐
题目:
给出一个矩阵,其中\(W_{i,j}=a_i+b_j\),求出宽度至少为\(x\),长度至少为\(y\)的子矩阵的最大平均值
解析:
方法一:
根据矩阵值的计算公式,可以看到子矩阵的最大平均值其实等价于\(a\)中长度至少为\(x\)子连续序列的最大平均值加上\(b\)中长度至少为\(y\)子连续序列的最大平均值
方法二:
可以二分答案尺取验证是否存在,也可以找出长度为\(x\)的区间的平均值并记录起始位置,利用答案区间一定大于等于已有长度为\(x\)的区间平均值的特点,线性贪心的查找到答案区间
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
long long sum[2][maxn];
int main() {
int n, m, x, y;
scanf("%d%d%d%d", &n, &m, &x, &y);
for (int i = 1; i <= n; ++i)
scanf("%lld", &sum[0][i]), sum[0][i] += sum[0][i - 1];
for (int i = 1; i <= m; ++i)
scanf("%lld", &sum[1][i]), sum[1][i] += sum[1][i - 1];
int last = 0;
double t, ans[2] = { 0 };
for (int i = x; i <= n; ++i) {
t = 1.0 * (sum[0][i] - sum[0][last]) / (i - last);
if (1.0 * (sum[0][i] - sum[0][i - x]) / x > t) {
t = 1.0 * (sum[0][i] - sum[0][i - x]) / x;
last = i - x;
}
ans[0] = max(ans[0], t);
}
last = 0;
for (int i = y; i <= m; ++i) {
t = 1.0 * (sum[1][i] - sum[1][last]) / (i - last);
if (1.0 * (sum[1][i] - sum[1][i - y]) / y > t) {
t = 1.0 * (sum[1][i] - sum[1][i - y]) / y;
last = i - y;
}
ans[1] = max(ans[1], t);
}
printf("%.10f", ans[0] + ans[1]);
}

浙公网安备 33010602011771号