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 ;
}

浙公网安备 33010602011771号