2023.10.28

A

类 Hanoi 问题,三个塔,移动顺序必须为 \(1\rightarrow 2\rightarrow 3\rightarrow 1\).

初始有 \(n\) 的圆盘到柱子 \(1\),问将它们全部操作到 \(2,3\) 上分别需要的步数 \(a,b\).

答案模 \(998244353\),多测。分别输出所有 \(a\)\(b\) 的异或和即可(模 \(998244353\) 后异或)。

\(T\le 1.2\times 10^6\)\(n\le 10^{12}\),TL=1s.


不会推,直接猜结论。

\(n=1,2,3\) 时的答案为 \((1,2),(5,7),(15,21)\).

一开始以为 \(a\)\(b\) 的差值为 \(\operatorname{fac}(n)\),然后一推发现完全不可做。

然后知道了 \(n=4\) 的答案是 \((43,59)\),所以换一种。

容易发现有

\[a_i=\begin{cases}1&i=1\\2b_{i-1}+1&i>1\end{cases} \]

\[b_i=a_{i-1}+a_i+1 \]

直接捏矩阵即可,考虑

\[\begin{bmatrix}a_i&b_i&1\end{bmatrix}\times A=\begin{bmatrix}a_{i+1}&b_{i+1}&1\end{bmatrix} \]

\[=\begin{bmatrix}2b_i+1&a_i+2b_i+2&1\end{bmatrix} \]

构造 \(A=\begin{bmatrix}0&1&0\\2&2&0\\1&2&1\end{bmatrix}\) 即可。

复杂度 \(O(27\cdot T\log n)\).


写几个优化。

  • 离线做。

  • 对于 \(i\in[0,40]\),预处理 \(A^{2^i}\),快速幂时 \(O(k^3)\rightarrow O(k^2)\).

  • 循环展开,取模优化。在 GJOJ 上使用指令集。

另外地,考虑优化矩乘的渐进复杂度,这意味着常数是可以去掉的。考虑构造

\[\begin{bmatrix}a+1&b+1\end{bmatrix}\times\begin{bmatrix}0&1\\2&2\end{bmatrix}=\begin{bmatrix}(2b+1)+1&(a+2b+2)+1\end{bmatrix} \]

\(k=3\) 的代码。

点击查看代码
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define ll long long
#define N 1200010
#define P 998244353
using namespace std;
ll read(){
	ll x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
int add(int x,int y){
	x+=y;
	return x>=P?x-P:x;
}
int md(ll x){
	return x>=P?x%P:x;
}
struct Mat{
	int a[3][3];
	Mat(){
		a[0][0]=a[0][1]=a[0][2]=0;
		a[1][0]=a[1][1]=a[1][2]=0;
		a[2][0]=a[2][1]=a[2][2]=0;
	}
	Mat operator*(const Mat &b)const{
		Mat c;
		c.a[0][0]=add(add(md(1ll*a[0][0]*b.a[0][0]),md(1ll*a[0][1]*b.a[1][0])),md(1ll*a[0][2]*b.a[2][0]));
		c.a[0][1]=add(add(md(1ll*a[0][0]*b.a[0][1]),md(1ll*a[0][1]*b.a[1][1])),md(1ll*a[0][2]*b.a[2][1]));
		c.a[0][2]=add(add(md(1ll*a[0][0]*b.a[0][2]),md(1ll*a[0][1]*b.a[1][2])),md(1ll*a[0][2]*b.a[2][2]));
		
		c.a[1][0]=add(add(md(1ll*a[1][0]*b.a[0][0]),md(1ll*a[1][1]*b.a[1][0])),md(1ll*a[1][2]*b.a[2][0]));
		c.a[1][1]=add(add(md(1ll*a[1][0]*b.a[0][1]),md(1ll*a[1][1]*b.a[1][1])),md(1ll*a[1][2]*b.a[2][1]));
		c.a[1][2]=add(add(md(1ll*a[1][0]*b.a[0][2]),md(1ll*a[1][1]*b.a[1][2])),md(1ll*a[1][2]*b.a[2][2]));
		
		c.a[2][0]=add(add(md(1ll*a[2][0]*b.a[0][0]),md(1ll*a[2][1]*b.a[1][0])),md(1ll*a[2][2]*b.a[2][0]));
		c.a[2][1]=add(add(md(1ll*a[2][0]*b.a[0][1]),md(1ll*a[2][1]*b.a[1][1])),md(1ll*a[2][2]*b.a[2][1]));
		c.a[2][2]=add(add(md(1ll*a[2][0]*b.a[0][2]),md(1ll*a[2][1]*b.a[1][2])),md(1ll*a[2][2]*b.a[2][2]));
		return c;
	}
}init,base,now,bs[50];
int m;ll q[N];
void qpow(ll b){
	int cnt=0;
	while(b){
		if(b&1)now=now*bs[cnt];
		cnt++,b>>=1;
	}
}
int ansa,ansb;
int main(){
	init.a[0][0]=init.a[0][2]=1,init.a[0][1]=2;
	base.a[2][0]=base.a[0][1]=base.a[2][2]=1,base.a[1][0]=base.a[1][1]=base.a[2][1]=2;
	bs[0]=base;
	for(int i=1;i<50;i++)bs[i]=bs[i-1]*bs[i-1];
	m=read();
	for(int i=1;i<=m;i++)q[i]=read();
	sort(q+1,q+1+m),q[0]=1;
	now=init;
	for(int i=1;i<=m;i++){
		qpow(q[i]-q[i-1]);
		ansa^=now.a[0][0],ansb^=now.a[0][1];
	}
	printf("%d %d\n",ansa,ansb);
	
	return 0;
}

B

给出长为 \(n\) 的序列 \(a_0,a_1,\dots,a_{n-1}\).

\(m\) 次询问,每次给出 \(k\),问将 \(a\) 重排后,\(\sum\limits_{i=0}^{n-1}a_i\cdot a_{(i+k)\bmod n}\) 的最大值。

\(2\le n\le 5000\)\(1\le m\le n\)\(0\le k<n\)\(1\le a_i\le 10^5\).


一眼贪,考虑从大到小添,如果找到左侧或右侧距离为 \(k\) 没有数的最大的 \(x\),把这个数放在 \(x\) 的左边或右边。

一共有 \(\gcd(n,k)\) 个循环,每次填完一个循环就从新的位置开始填。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define N 5010
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
int n,a[N];
int pos[N],gcd,now,res[N];
int gl(int x,int k){
	return (x-k+n)%n;
}
int gr(int x,int k){
	return (x+k)%n;
}
void solve(){
	int k=read();
	if(!k){
		ll ans=0;
		for(int i=0;i<n;i++)ans+=1ll*a[i]*a[i];
		printf("%lld\n",ans);
		return;
	}
	if(k*2==n){
		ll ans=0;
		for(int i=0;i<n/2;i++)
			ans+=2ll*a[i*2]*a[i*2+1];
		printf("%lld\n",ans);
		return;
	}
	memset(pos,-1,sizeof(pos)),memset(res,-1,sizeof(res));
	gcd=__gcd(k,n),now=n-1;
	for(int i=0,tp;i<gcd;i++){
		pos[now]=i,res[i]=now;
		tp=gl(pos[now],k),pos[now-1]=tp,res[tp]=now-1;
		now-=2;
		for(int j=3;j<=n/gcd;j++){
			tp=gl(pos[now+2],k);
			if(~res[tp])tp=gr(pos[now+2],k);
			pos[now]=tp,res[tp]=now--;
		}
	}
	ll ans=0;
	for(int i=0;i<n;i++)
		ans+=1ll*a[res[i]]*a[res[gr(i,k)]];
	printf("%lld\n",ans);
}
int main(){
	n=read();
	for(int i=0;i<n;i++)a[i]=read();
	sort(a,a+n);
	int T=read();
	while(T--)solve();
	
	return 0;
}

C

已知 \(i\in[1,n]\)\(a_i\)\([l_i,r_i]\) 间的一个实数。

对于每个 \(i\)\(a_i\) 的期望排名,排名具体指 \(a_j\ge a_i\)\(j\) 的个数,\(j\in[1,n]\).

\(n\le 10^5\)\(1\le l_i<r_i\le 10^5\).


就是对于 \(i\),对每个 \(j\not=i\)\(a_j\ge a_i\) 的概率。

\(O(n^2)\) 随便积积就好,但是把这个东西变成大分讨的形式就优化不了了。

所以根本不用在 \(\mathbb R\) 上做,对于每个 \(i\in[l,r)\),赋权 \(\large \frac{1}{r-l}\).

对于两个长为 \(1\) 的区间 \([x,x+1]\)\([y,y+1]\),当 \(x<y\)\(y\)\(x\) 贡献,当 \(x=y\)\(y\)\(x\) 贡献其权值的 \(\frac{1}{2}\).

然后发现两次后缀和就做完了,减去自己对自己产生的贡献。

点击查看代码
#include<bits/stdc++.h>
#define db double
#define N 100010
#define R 99999
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
int n,_l[N],_r[N];
db s[N],s2[N],s3[N];
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		_l[i]=read(),_r[i]=read()-1;
		s[_r[i]]+=1.0/(_r[i]-_l[i]+1),s[_l[i]-1]-=1.0/(_r[i]-_l[i]+1);
	}
	for(int i=R;~i;i--)s[i]+=s[i+1];
	for(int i=R;~i;i--)s2[i]=s2[i+1]+s[i];
	for(int i=R;~i;i--)s3[i]=s3[i+1]+s2[i];
	for(int i=1,l,r;i<=n;i++){
		l=_l[i],r=_r[i];
		db ans=(s3[l+1]-s3[r+2]+(s2[l]-s2[r+1])/2.0)/(r-l+1);
		ans-=((r-l)/2.0+0.5)/(r-l+1);
		printf("%.10lf\n",ans+1);
	}
	
	return 0;
}
posted @ 2023-10-29 20:58  SError  阅读(8)  评论(0编辑  收藏  举报