ABC448 A-F 题解

ABC448 A-F 题解

A

按题意模拟。

B

不要开翻译。

按题意模拟即可。

C

使用 std::multiset 模拟即可。

D

使用一个 std::map 作桶,对全树遍历一遍即可。

E

唐了。居然一直在想倍增,实际上写两个快速幂即可。

将整数 \(X\) 改写成 \(X=K·M+Y\) 的形式,用 std::pair 维护 \((K,Y)\)。

这样一来我们就会写这种形式下的两个数相乘和两个数相加了。

我们现在需要支持乘上 \(10^L\) 和加上一个 \(ccc...c\),考虑怎么算这两个东西。

对于 \(10^L\),我们可以写一个以 \(10\) 为底的快速幂,反正我们会乘法了,思路略。

对于 \(ccc...c\),我们继续试图使用快速幂处理,先把他变成 \(c·111...1\),发现让一个数 \(X\) 后面接 \(L\) 个 \(1\) 只需要先让 \(X\) 乘上 \(10^L\) 再加上 \(L\) 个 \(1\) 就好,这是废话,但有了这句话我们就可以用快速幂拼出任意长度的 \(111...1\) 了。

计算 \(ccc...c\) 的过程里涉及到了计算 \(L\) 为 \(2\) 的整次幂时的 \(10^L\),直接套第一个快速幂最后的复杂度就是 \(O(K\log^2L)\) 的,能过。把这些整次幂的结果存起来就能压到 \(O(K\log L)\)。

using ll = long long;
using pll = pair<ll, ll>;
const ll mod=10007,maxn=10005;
ll n,m;
pll mul(pll x,pll y){
	pll res(0,0);
	res.fi=x.fi*y.fi*m+x.se*y.fi+x.fi*y.se;
	res.se=x.se*y.se%m;
	res.fi+=x.se*y.se/m;
	res.fi%=mod;
	return res;
}
pll add(pll x,pll y){
	pll res(x.fi+y.fi,x.se+y.se);
	res.fi+=res.se/m;
	res.fi%=mod;
	res.se%=m;
	return res;
}
pll ksm1(ll k){
	pll res;
	res.fi=1/m,res.se=1%m;
	pll x;
	x.fi=10/m,x.se=10%m;
	while(k){
		if(k&1)res=mul(res,x);
		x=mul(x,x);
		k>>=1;
	}
	return res;
}

pll lst[50];

pll ksm2(ll k){
	pll res(0,0);
	pll x;
	x.fi=1/m,x.se=1%m;
	ll cur=0;
	while(k){
		if(k&1){
			res=mul(res,lst[cur]);
			res=add(res,x);
		}
		pll pre=x;
		x=mul(x,lst[cur]);
		x=add(x,pre);
		cur++;
		k>>=1;
	}
	return res;
}

void solve(){
	cin>>n>>m;
	pll x(0,0);
	rep(i,0,31){
		lst[i]=ksm1(1ll<<i);
	}
	rep(i,1,n){
		ll c,l;
		cin>>c>>l;
		x=mul(x,ksm1(l));
		pll cur(c/m,c%m);
		cur=mul(cur,ksm2(l));
		x=add(x,cur);
	}
	cout<<x.fi<<endl;
	return ;
}

有没有 \(O(K)\) 的做法呢?

有的兄弟有的,使用光速幂可以通过根号时间预处理的方式做到固定底数的 \(O(1)\) 快速幂。再写一下就能百尺竿头更进一步,总时间复杂度 \(O({\sqrt L}+K)\)。

using ll = long long;
using pll = pair<ll, ll>;
const ll mod=10007,maxn=32005,block=32000;
ll n,m;
pll mul(pll x,pll y){
	pll res(0,0);
	res.fi=x.fi*y.fi*m+x.se*y.fi+x.fi*y.se;
	res.se=x.se*y.se%m;
	res.fi+=x.se*y.se/m;
	res.fi%=mod;
	return res;
}
pll add(pll x,pll y){
	pll res(x.fi+y.fi,x.se+y.se);
	res.fi+=res.se/m;
	res.fi%=mod;
	res.se%=m;
	return res;
}

pll A[maxn],B[maxn],C[maxn],D[maxn];

pll ksm1(ll k){
	return mul(B[k/block],A[k%block]);
}

pll ksm2(ll k){
	return add(mul(D[k/block],ksm1(k%block)),C[k%block]);
}

void solve(){
	cin>>n>>m;
	pll x(0,0);
	
	A[0].fi=1/m,A[0].se=1%m;
	B[0]=A[0];
	A[1].fi=10/m,A[1].se=10%m;
	rep(i,2,block)A[i]=mul(A[i-1],A[1]);
	B[1]=A[block];
	rep(i,2,block)B[i]=mul(B[i-1],B[1]);
	C[1]=A[0];
	rep(i,2,block){
		C[i]=mul(C[i-1],A[1]);
		C[i]=add(C[i],C[1]);
	}
	D[1]=C[block];
	rep(i,2,block){
		D[i]=mul(D[i-1],A[block]);
		D[i]=add(D[i],D[1]);
	}
	
	rep(i,1,n){
		ll c,l;
		cin>>c>>l;
		x=mul(x,ksm1(l));
		pll cur(c/m,c%m);
		cur=mul(cur,ksm2(l));
		x=add(x,cur);
	}
	cout<<x.fi<<endl;
	return ;
}

实测 \(O(K\log^2L)\) 写法 494ms,\(O(K\log L)\) 写法 67ms,\(O(\sqrt L+K)\) 写法 16ms。

F

【模板】莫队

如果把一个点 \((X,Y)\) 看成数轴上的一段区间 \([X,Y]\),我们十分惊讶地发现一个点到另一个点的距离,就是莫队算法中某个区间挪到另一个区间所需要的挪动指针次数。

直接套用莫队算法,将除了 \(1\) 号点之外的所有点按照莫队的规则排序,具体是 \(X\) 在同一块内的就按照 \(Y\) 排序,不在同一块内的按照块编号排序。这样我们就可以尝试平衡操作次数了。

假设块长为 \(B\) 且使用了奇偶优化,令 \(L=2×10^7\),那么操作次数也就是距离上界预计为 \(nB+\frac{L^2}{B}+L\),加上我们没有让 \(1\) 号点参与排序,可以被额外卡出大概 \(3L\) 的开销。稍微解下方程得到大约在 \(B=8.16×10^4\) 时最优,算出来总距离上界约为 \(9.86×10^9\),足够通过本题。

Fun Fact:块长在 \(B=10^5\) 时只有两个测试点没有通过,而在 \(B=1.5×10^5\) 时居然直接通过了,非常神奇。

看来本题保留了玄学调块长的部分,这样你才知道你写的是莫队。

using ll = long long;
using pll = pair<ll, ll>;
using vl = vector<ll>;
void solve(){
	ll n;
	cin>>n;
	vl p(n+1);
	vector<pll>a(n+1);
	rep(i,1,n){
		p[i]=i;
		cin>>a[i].fi>>a[i].se;
	}
	if(n==1){
		cout<<1<<endl;
		return ;
	}
	sort(p.begin()+2,p.end(),[&](ll X,ll Y){
		pll x=a[X],y=a[Y];
		ll belx=x.fi/81649,bely=y.fi/81649;
		if(belx!=bely)return belx<bely;
		if(belx&1)return x.se>y.se;
		return x.se<y.se;
	});
	rep(i,1,n)cout<<p[i]<<" ";
	return ;
}
posted @ 2026-03-08 01:25  Rnfmabj  阅读(59)  评论(0)    收藏  举报