XVIII Open Cup GP of Gomel(待更新)

A. Aftermath

题意简述

有一个正整数 \(n\),给出它的所有因数的算术平均数 \(a\) 和调和平均数 \(h\),它们恰好都是整数,请你还原出任意一个满足条件的 \(n\) 的值。

数据范围:保证存在一个合法的 \(n\) 使得 \(1 \le n \le {10}^{15}\)

AC 代码
#include <cstdio>

typedef long long LL;

LL A, H;

int main() {
	scanf("%lld%lld", &A, &H);
	printf("%lld\n", A * H);
	return 0;
}

$ \displaystyle \frac{1}{h} = \frac{\displaystyle \sum_{d \mathrel{|} n} d^{-1}}{\sigma_0 (n)} = \frac{\displaystyle \sum_{d \mathrel{|} n} \frac{n}{d}}{n \sigma_0 (n)} = \frac{1}{n} \frac{\displaystyle \sum_{d \mathrel{|} n} d}{\sigma_0 (n)} = \frac{a}{n} $,所以 \(n = a \times h\)

B. Believer

题意简述

定义一个正整数数列的权值为所有数在数列中的出现次数的 popcount 之和。

给定 \(n\),请求出数列中元素总和为 \(n\)正整数数列的权值的最大值。

\(T\) 组数据。

数据范围:\(1 \le T \le {10}^3\)\(1 \le n \le {10}^{18}\)

AC 代码
#include <cstdio>

typedef long long LL;

LL N;

int main() {
	int Tests;
	scanf("%d", &Tests);
	while (Tests--) {
		scanf("%lld", &N);
		LL l = 1, r = 1.5e9, ans = 0;
		while (l <= r) {
			LL mid = (l + r) / 2;
			LL x = mid, b = 1, sum = 0;
			while (x) {
				sum += (x * (x + 1) / 2) * b;
				x /= 2, b *= 2;
			}
			if (sum <= N) ans = mid, l = mid + 1;
			else r = mid - 1;
		}
		LL x = ans, b = 1, sum = 0, cnt = 0;
		while (x) {
			sum += (x * (x + 1) / 2) * b;
			cnt += x;
			x /= 2, b *= 2;
		}
		cnt += (N - sum) / (ans + 1);
		printf("%lld\n", cnt);
	}
	return 0;
}

我们注意到 popcount 取到极大值的时候,也就是 \(2^k - 1\) 的 popcount 为 \(k\)

也就是说,如果我们想要通过添加数 \(x\) 让数列权值逐渐增加,初始时没有 \(x\),第一次添加 \(1\)\(x\) 让数列权值增加 \(1\),第二次需要添加 \(2\)\(x\) 让数列权值再增加 \(1\),第 \(k\) 次增加的时候需要在之前的基础上添加 \(2^{k - 1}\)\(x\),也就是代价是逐渐增大的。

对于这种代价逐渐增大的问题,我们只需要贪心就行了。相当于现在有 \(\{1, 2, 3, 4, \ldots \}\) 可以进行添加,我们肯定先选代价最小的 \(1\) 添加进去,但是下一次添加 \(1\) 就必须一次添加 \(2\)\(1\) 了,所以我们再把 \(2\) 倍的 \(1\) 丢回去,也就是变成了 \(\{2, 2, 3, 4, \ldots \}\),此时序列的权值就为 \(1\)。想要再让序列权值增加 \(1\),我们只能选择 \(2\) 的代价,然后把 \(2 \times 2 = 4\) 丢回去,以此类推。

一直这样添加下去,并且保证代价和不超过 \(n\),能添加的最多次数就是答案(如果代价小于 \(n\),容易发现此时在数列中出现过的最大元素只出现了一次,将它增大即可保证数列总和正好为 \(n\))。

但是因为答案太大了不能直接模拟,因为每次代价增加量总是递增的,我们可以二分最后添加的代价是多少。

假设要把代价为 \(1 \sim x\) 的所有增加量都加满,此时总代价为 \(\displaystyle \sum_{i = 0}^{\log_2 x} \frac{\left\lfloor \frac{x}{2^i} \right\rfloor \left( \left\lfloor \frac{x}{2^i} \right\rfloor + 1 \right)}{2} \cdot 2^i\),根据这个值与 \(n\) 的大小关系二分即可。

D. Do I Wanna Know?

题意简述

有个 \(n\) 个点的竞赛图。并给出概率 \(p = a / b\),图中每条有向边的方向是这样确定的:

  • 结点 \(i, j\)\(1 \le i < j \le n\))之间的有向边的方向,有 \(p\) 的概率为 \(i \to j\),另外 \(1 - p\) 的概率为 \(j \to i\)

  • 并且每条边的方向是两两独立的随机变量。

定义 \(f_k\)\(1 \le k \le n - 1\))表示这个竞赛图中存在大小为 \(k\) 的点集 \(S\),满足「对于每对 \(S\) 中的点与不在 \(S\) 中的点,它们之间的有向边都是从 \(S\) 中的点出发的」的概率。

再定义 \(g_k = 1\),以及 \(g_k = {(g_{k - 1})}^2 + 2\)\(2 \le k \le n - 1\))。

请求出 \(\left( \sum_{k = 1}^{n - 1} f_k g_k \right) \bmod 998244353\)

数据范围:\(2 \le n \le 6 \times {10}^5\)\(1 \le a < b \le 100\)

AC 代码
#include <cstdio>

typedef long long LL;
const int Mod = 998244353;

inline int qPow(int b, int e) {
	int a = 1;
	for (; e; e >>= 1, b = (LL)b * b % Mod)
		if (e & 1) a = (LL)a * b % Mod;
	return a;
}
inline int gInv(int x) { return qPow(x, Mod - 2); }

int N, Pr, p;
int Ans;

int main() {
	int a, b;
	scanf("%d%d%d", &N, &a, &b);
	Pr = (LL)a * gInv(b) % Mod;
	p = (LL)(1 - Pr + Mod) * gInv(Pr) % Mod;
	int Val = 1, gVal = 1;
	for (int i = 1; i <= N - 1; ++i) {
		if (p == 1) Val = (LL)Val * (N - i + 1) % Mod * gInv(i) % Mod;
		else Val = (LL)Val * (1 - qPow(p, N - i + 1) + Mod) % Mod * gInv(1 - qPow(p, i) + Mod) % Mod;
		Ans = (Ans + (LL)gVal * Val % Mod * qPow(Pr, (LL)i * (N - i) % (Mod - 1))) % Mod;
		gVal = ((LL)gVal * gVal + 2) % Mod;
	}
	printf("%d\n", Ans);
	return 0;
}

看起来 \(g\) 没有什么性质,那么我们需要求出所有的 \(f_k\)

我们知道竞赛图的一个性质:强连通分量缩点后,竞赛图形成一条链的形式。
可以推出如果存在大小为 \(k\) 的集合,那么恰好存在一个。

根据期望的线性性,也就是要对每个 \(k\),询问每个大小为 \(k\) 的点集 \(S\)\(S\) 到其他点的有向边全都由 \(S\) 侧出发的概率。
这也就是,对于每对 \(u \in S\)\(v \in (V \setminus S)\),如果 \(u < v\) 则贡献 \(p\) 到乘积中,否则贡献 \(1 - p\) 到乘积中。
也就是说,对于每个 \(S\),概率都是 \(p^{k (n - k) - t} {(1 - p)}^{t}\) 的形式,改写为 \(\displaystyle p^{k (n - k)} \cdot {\left( \frac{1 - p}{p} \right)}^t\)

对每个 \(k\) 提出 \(p^{k (n - k)}\),再令 \(\displaystyle q = \frac{1 - p}{p}\),只需计算所有 \(S\) 的方案中的 \(q^t\) 之和即可。
这实际上等价于:枚举所有长度为 \(n\),且有 \(k\)\(1\)\(01\) 串,记它的逆序对个数为 \(t\),将所有 \(q^t\) 求和。

于是可以很简单地进行 DP:令 \(\mathrm{dp}[n][k]\) 表示上述取值,则有:
\(\mathrm{dp}[n][k] = q^k \mathrm{dp}[n - 1][k] + \mathrm{dp}[n - 1][k - 1]\),也就是枚举最后一个数是 \(0\) 还是 \(1\)
边界条件是 \(\mathrm{dp}[0][0] = 1\),且所有 \(n < 0\)\(k < 0\)\(k > n\) 的位置为 \(0\)

这实际上就是 q-二项式系数(q-Binomial Coefficient)的递推公式。根据 \(\displaystyle {n \brack k}_q = \sum_{i = 0}^{k - 1} \frac{1 - q^{n - i}}{1 - q^{i + 1}}\) 递推计算每个 \(\displaystyle {n \brack k}_q\) 即可。

当然也有其他方法,如果令 \(\displaystyle F_k(z) = \sum_{n = 0}^{\infty} \mathrm{dp}[n][k] z^n\),也就是每一列的生成函数,我们有:
直接根据递推式有 \(F_k(z) = q^k z F_k(z) + z F_{k - 1}(z)\),也就是 \(\displaystyle F_k(z) = \frac{z F_{k - 1}(z)}{1 - q^k z}\)
根据边界条件 \(\displaystyle F_0(z) = \frac{1}{1 - z}\),可以得到 \(\displaystyle F_k(z) = z^k \prod_{i = 0}^{k} \frac{1}{1 - q^i z}\)。我们要求的是每个 \([z^n] F_k(z)\)

\[\begin{aligned} F_k(qz) &= q^k z^k \prod_{i = 0}^{k} \frac{1}{1 - q^{i + 1} z} \\ &= q^k \frac{1 - z}{1 - q^{k + 1} z} F_k(z) \\ (1 - q^{k + 1} z) F_k(qz) &= q^k (1 - z) F_k(z) \\ [z^n] (1 - q^{k + 1} z) F_k(qz) &= [z^n] q^k (1 - z) F_k(z) \\ q^n \mathrm{dp}[n][k] - q^{k + n} \mathrm{dp}[n - 1][k] &= q^k \mathrm{dp}[n][k] - q^k \mathrm{dp}[n - 1][k] \\ (q^n - q^k) \mathrm{dp}[n][k] &= (q^{k + n} - q^k) \mathrm{dp}[n - 1][k] \\ \mathrm{dp}[n][k] &= \frac{1 - q^n}{1 - q^{n - k}} \mathrm{dp}[n - 1][k] \\ &= \prod_{i = 1}^{n - k} \frac{1 - q^{i + k}}{1 - q^i} \\ \end{aligned} \]

好吧,这实际上和上面的式子等价,也就是我们证了一遍 q-二项式系数的通项公式。

感谢 EntropyIncreaser 提供的帮助。

F. Forever and Always

G. Gate 21

题意简述

平面上有 \(n\) 条线段,第 \(i\) 条线段连接两点 \((i, l_i)\)\((i, r_i)\)\(l_i \le r_i\))。

有一个点的轨迹是一条直线,定义这条轨迹合法,当且仅当它在每一条线段上都经过了一个整点。

请你计算合法的轨迹的数量。

数据范围:\(1 \le n \le 2 \times {10}^5\)\(1 \le l_i \le r_i \le {10}^9\)

AC 代码
#include <cstdio>
#include <algorithm>

typedef long long LL;
const LL Inf = 0x0000003f3f3f3f3f;
const int MN = 200005;

LL Div(LL x, int y) {
	int z = (int)(x % y);
	if (z < 0) z += y;
	return (x - z) / y;
}

int N; LL A[MN], B[MN];
int sA[MN], cA;
LL lA[MN], rA[MN];
int sB[MN], cB;
LL lB[MN], rB[MN];
int stk[MN], tp;

int main() {
	scanf("%d", &N);
	for (int i = 1; i <= N; ++i) scanf("%lld%lld", &A[i], &B[i]);
	stk[tp = 1] = N;
	for (int i = N - 1; i >= 1; --i) {
		while (tp >= 2 && (A[stk[tp - 1]] - A[stk[tp]]) * (stk[tp] - i) >= (A[stk[tp]] - A[i]) * (stk[tp - 1] - stk[tp])) --tp;
		stk[++tp] = i;
	}
	cA = tp;
	for (int i = 1; i <= cA; ++i) sA[i] = stk[i];
	lA[1] = -Inf, rA[cA] = Inf;
	for (int i = 2; i <= cA; ++i) {
		LL v = Div(A[sA[i - 1]] - A[sA[i]], sA[i - 1] - sA[i]);
		rA[i - 1] = v, lA[i] = v + 1;
	}
	stk[tp = 1] = 1;
	for (int i = 2; i <= N; ++i) {
		while (tp >= 2 && (B[stk[tp]] - B[stk[tp - 1]]) * (i - stk[tp]) >= (B[i] - B[stk[tp]]) * (stk[tp] - stk[tp - 1])) --tp;
		stk[++tp] = i;
	}
	cB = tp;
	for (int i = 1; i <= cB; ++i) sB[i] = stk[i];
	lB[1] = -Inf, rB[cB] = Inf;
	for (int i = 2; i <= cB; ++i) {
		LL v = Div(B[sB[i]] - B[sB[i - 1]], sB[i] - sB[i - 1]);
		rB[i - 1] = v, lB[i] = v + 1;
	}
	LL Ans = 0;
	int i = 1, j = 1;
	while (i <= cA && j <= cB) {
		if (lA[i] > rA[i]) { ++i; continue; }
		if (lB[j] > rB[j]) { ++j; continue; }
		if (rA[i] < lB[j]) { ++i; continue; }
		if (rB[j] < lA[i]) { ++j; continue; }
		LL lb = std::max(lA[i], lB[j]), rb = std::min(rA[i], rB[j]);
		if (sA[i] < sB[j]) rb = std::min(rb, Div(B[sB[j]] - A[sA[i]], sB[j] - sA[i]));
		if (sA[i] > sB[j]) lb = std::max(lb, Div(A[sA[i]] - B[sB[j]] - 1, sA[i] - sB[j]) + 1);
		if (lb <= rb) {
			LL lbs = (B[sB[j]] - lb * sB[j]) - (A[sA[i]] - lb * sA[i]) + 1;
			LL rbs = (B[sB[j]] - rb * sB[j]) - (A[sA[i]] - rb * sA[i]) + 1;
			Ans += (lbs + rbs) * (rb - lb + 1) / 2;
		}
		++(rA[i] <= rB[j] ? i : j);
	}
	printf("%lld\n", Ans);
	return 0;
}

轨迹是个一次函数,如果枚举轨迹的斜率(显然必须是个整数),那么可行的截距是一个区间,其左右端点如何确定呢?

以左端点为例,假设斜率为 \(k\),左端点应该被 \(\displaystyle i = \mathop{\arg\max}_{j = 1}^{n} (l_j - k \cdot j)\) 这个 \((i, l_i)\) 限制住。

这是一个经典的凸壳形式,求出所有 \((i, l_i)\) 的上凸壳即可。对于右端点,则是所有 \((i, r_i)\) 的下凸壳。

然后在凸壳上双指针即可,每一段左端点和右端点对应的截距是一个一次函数,等差数列求和即可。

注意实现细节。

K. Kids Aren’t Alright

题意简述

给定 \(m\),求出集合内元素的 \(\gcd\)\(1\)\(\operatorname{lcm}\)\(n\) 的非空集合的个数,对 \(998244353\) 取模。

数据范围:\(1 \le m \le {10}^{18}\)

AC 代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define RI int
#define RL LL
#define Con const
#define CI Con int&
#define CL Con LL&
#define I inline
#define W while
#define LL long long
#define Gmax(x,y) (x<(y)&&(x=(y)))
#define abs(x) ((x)<0?-(x):(x))
#define hl_AK_NOI true
using namespace std;
I LL Qmul(CL x,CL y,CL X)//快速乘
{
	RL k=(LL)((1.0L*x*y)/(1.0L*X)),t=x*y-k*X;
	t-=X;W(t<0) t+=X;return t;
}
class MillerRabin//MillerRabin判素数板子
{
	private:
		#define Pcnt 12
		Con int P[Pcnt]={2,3,5,7,11,13,17,19,61,2333,4567,24251};
		I LL Qpow(RL x,RL y,CL X)
		{
			RL t=1;W(y) y&1&&(t=Qmul(t,x,X)),x=Qmul(x,x,X),y>>=1;
			return t;
		}
		I bool Check(CL x,CI p)
		{
			if(!(x%p)||Qpow(p%x,x-1,x)^1) return false;
			RL k=x-1,t;W(!(k&1))
			{
				if((t=Qpow(p%x,k>>=1,x))^1&&t^(x-1)) return false;
				if(!(t^(x-1))) return true;
			}return true;
		}
	public:
		bool IsPrime(CL x)
		{
			if(x<2) return false;
			for(RI i=0;i^Pcnt;++i) {if(!(x^P[i])) return true;if(!Check(x,P[i])) return false;}
			return true;
		}
};
const int MC=105;
int c;
LL ar[MC];
class PollardRho//PollardRho分解质因数
{
	private:
		#define Rand(x) (1LL*rand()*rand()*rand()*rand()%(x)+1)
		MillerRabin MR;
		I LL gcd(CL x,CL y) {return y?gcd(y,x%y):x;}//求gcd
		I LL Work(CL x,CI y)//分解
		{
			RI t=0,k=1;RL v0=Rand(x-1),v=v0,d,s=1;W(hl_AK_NOI)//初始化随机一个v0
			{
				if(v=(Qmul(v,v,x)+y)%x,s=Qmul(s,abs(v-v0),x),!(v^v0)||!s) return x;//计算当前v,统计乘积,判断是否分解失败
				if(++t==k) {if((d=gcd(s,x))^1) return d;v0=v,k<<=1;}//倍增
			}
		}
		I void Resolve(RL x,RI t)//分解
		{
			if(x==1)return;if(MR.IsPrime(x))return ar[++c]=x,void();
			RL y=x;W((y=Work(x,t--))==x);x/=y;Resolve(x,t),Resolve(y,t);//找到一个因数y,然后分解(注意除尽)
		}
	public:
		I PollardRho() {srand(20050521);}//初始化随机种子
		I void Solve(CL x) {Resolve(x,302627441);}//求答案
}P;
const int Mod=998244353;
inline int qPow(int b,LL e){
	int a=1;
	for(;e;e>>=1,b=(LL)b*b%Mod)
		if(e&1)a=(LL)a*b%Mod;
	return a;
}
int b[MC],t;
int ans;
void DFS(int i,LL x,LL v){
	if(i>t){
		v=(v%Mod+Mod)%Mod;
		ans=(ans+(LL)v*(qPow(2,x)-1))%Mod;
		return;
	}
	DFS(i+1,x*(b[i]+1),v);
	DFS(i+1,x*b[i],-2*v);
	if(b[i]>1)DFS(i+1,x*(b[i]-1),v);
}
int main()
{
	RL n;
	scanf("%lld",&n);
	P.Solve(n);
	sort(ar+1,ar+c+1);
	for(int i=1;i<=c;++i)
		if(ar[i]!=ar[i-1])b[++t]=1;
		else ++b[t];
//	for(int i=1;i<=t;++i)printf("%d,",b[i]);
	DFS(1,1,1);
	printf("%d\n",ans);
	return 0;
}

考察 \(\gcd\)\(\operatorname{lcm}\) 的性质,显然只需知道每个质因子的次数,而不需要知道质因子本身。

反正我连 Miller–Rabin 都不会,直接抄了个 Pollard–Rho 板子分解了质因数。

实际上可以先筛掉 \({10}^6\) 内的质因数,然后剩下的只可能是 \(p\) 或者 \(p^2\) 或者 \(p q\) 三种形式之一,Miller–Rabin 判掉 \(p\) 形式,然后开个根判掉 \(p^2\) 就只剩 \(p q\) 不用判了。

得到了每个幂次 \(\alpha_1, \alpha_2, \ldots , \alpha_k\) 后,只需对每个质数幂次的 \(\gcd\)\(\operatorname{lcm}\) 两个方向进行容斥。

也就是:\(\displaystyle \sum_{\forall 1 \le i \le k, (x_i, y_i) \in {(\{0, 1\})}^2} \prod_{j = 1}^{k} {(-1)}^{x_i} {(-1)}^{y_i} \left( 2^{\alpha_i + 1 - x_i - y_i} - 1 \right)\)

改成 \(\displaystyle \sum_{\forall 1 \le i \le k, x_i \in \{0, 1, 2\}} \prod_{j = 1}^{k} {(-1)}^{x_i} \binom{2}{x_i} \left( 2^{\alpha_i + 1 - x_i} - 1 \right)\) 可以在 \(\mathcal O \!\left( 3^{\omega(n)} \right)\) 的时间复杂度内计算。

posted @ 2021-02-22 20:36  粉兔  阅读(826)  评论(0编辑  收藏  举报