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=\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 上使用指令集。
另外地,考虑优化矩乘的渐进复杂度,这意味着常数是可以去掉的。考虑构造
贴 \(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;
}