zoukankan      html  css  js  c++  java
  • 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
    ", 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 imes 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
    ", 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 imes 2 = 4) 丢回去,以此类推。

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

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

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

    D. Do I Wanna Know?

    题意简述

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

    • 结点 (i, j)(1 le i < j le n))之间的有向边的方向,有 (p) 的概率为 (i o j),另外 (1 - p) 的概率为 (j o 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 ight) mod 998244353)

    数据范围:(2 le n le 6 imes {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
    ", 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} ight)}^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 rack k}_q = sum_{i = 0}^{k - 1} frac{1 - q^{n - i}}{1 - q^{i + 1}}) 递推计算每个 (displaystyle {n rack 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))

    [egin{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 imes {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
    ", Ans);
    	return 0;
    }
    

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

    以左端点为例,假设斜率为 (k),左端点应该被 (displaystyle i = mathop{argmax}_{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
    ",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 ight))

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

    来自 PinkRabbit 的博客园(https://www.cnblogs.com/PinkRabbit/)。未经允许,请勿转载。
  • 相关阅读:
    类 7.2访问控制与封装 笔记
    第七章 类(class)7.1 笔记
    10.4.3反向迭代器Reverse_iterator笔记
    10.4再探迭代器笔记
    10.3.4参数绑定 bind
    10.3lambda表达式笔记
    第10章 10.1-10.3笔记
    关于按下ctrl+z后,之后的cin失效的问题
    构造和改变一个string的其他方法
    BZOJ2527 & 洛谷3527:[Poi2011]Meteors——题解
  • 原文地址:https://www.cnblogs.com/PinkRabbit/p/OpenCup-XVIII-GP-of-Gomel.html
Copyright © 2011-2022 走看看