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\),进行如下转化

\[\begin{aligned} &\Leftrightarrow \sum_{i=1}^\infin \left(P(len> i-1)-P(len> i)\right)i^2 \\ &\Leftrightarrow \sum_{i=0}^\infin P(len> i)\left((i+1)^2-i^2\right) \\ &\Leftrightarrow \sum_{i=0}^\infin P(len> i)(2i+1)=2f'(1)+f(1) \end{aligned} \]

#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]);
}
posted @ 2021-08-30 16:48  DreamW1ngs  阅读(59)  评论(0)    收藏  举报