T1 linkstart
Subtask 1
显然,答案为
此时暴力枚举 (i) 的取值,运用快速幂求 (b^i) 即可。
Subtask 2
观察到 (k)是质数,想到运用等比数列求和公式解决。
则有:
((2)-(1))可得:
即:
快速幂求出 (b^n-1) ,并求 (b-1) 的逆元即可。
Subtask 3
可能很多人会受到等比数列求和公式的误导,直接想到 (Subtask 2) 的做法,而没有发现陷阱在于 (k) 并非质数,无法用逆元解决分母的 (b-1)。
这里提供两种时间复杂度为 (mathcal{O}(log n)) 的方法。
法1:递推
设
显然
上式可以递推出 (p_i) 。
设
显然
上式可以递推出 (sum_i) 。
对于答案,我们可以对 (n) 进行二进制分解,进行分段求和。定义 (base) 为目前做到哪一位。将 (i) 从高位往低位枚举,若 (n) 在第 (i) 位非空,则将 (base * sum_i) 计入答案,并更新此时做到的位数 (base) ,即 (base := base * p_i) 。
下为xiong_6的代码。
#include<bits/stdc++.h>
using namespace std;
namespace IO {
#define _CCF_ 8388608
#define getchar() (p1==p2&&(p2=(p1=Ibuf)+fread(Ibuf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
#define putchar(c) (O-Obuf<_CCF_)?(*O++=c):(fwrite(Obuf,O-Obuf,1,stdout),O=Obuf,*O++=c)
char Ibuf[_CCF_],*p1=Ibuf,*p2=Ibuf,Obuf[_CCF_],*O=Obuf;
int f,ch,Onum[32],Ohd; long long k;
template<typename T>inline void read(T&x){
x=f=ch=0; while(!isdigit(ch)) f|=(ch=='-'),ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^'0'),ch=getchar();
f&&(x=-x);}
template<typename T>inline void write(T x){
if(x==0) return putchar('0'),void(); if(x<0) putchar('-'),x=-x;
while(x>0) k=x/10,Onum[++Ohd]=(x-(k<<1)-(k<<3))^'0',x=k;
while(Ohd>0) putchar(Onum[Ohd]),--Ohd;}
inline void _Exit0() {fwrite(Obuf,O-Obuf,1,stdout),exit(0);}
} using namespace IO;
long long n,b,K,t;
long long bpow[100];//b^(2^i)
long long bsum[100];//sigma(b^0,b^(2^i-1))
long long base=1,ans;
int main(){
read(t);
while(t--){
base=1,ans=0;
read(n);
read(b);
read(K);
b%=K;
bpow[0]=b;
for(long long i=1;i<=63;i++){
bpow[i]=bpow[i-1]*bpow[i-1];
bpow[i]%=K;
}
bsum[0]=1;
for(long long i=1;i<=63;i++){
bsum[i]=bsum[i-1]*(bpow[i-1]+1);
bsum[i]%=K;
}
for(long long i=63;i>=0;i--){
if((1ll<<i)&n){
ans+=base*bsum[i];
ans%=K;
base*=bpow[i];
base%=K;
}
}
write(ans);
putchar('
');
}
IO::_Exit0();
}
法2:矩阵快速幂
验题人 fz 的方法,本来由于常数过大而超时,我觉得这个方法很不错,说服了 zzy 将时限改成了 (1.5s) 。
令 (f_i) 为第 (i) 层节点数,(g_i) 为前 (i) 层节点数。
则有递推式:
由 ((1) (2)) 构造矩阵
所以由 (g_1) 与 (f_2) 可矩阵快速幂得到答案。
下为fz的代码。
#include<cstdio>
#include<cctype>
using namespace std;
typedef long long ll;
inline ll readint(){
ll x=0;
char c=getchar();
bool f=0;
while(!isdigit(c)&&c!='-') c=getchar();
if(c=='-'){
f=1;
c=getchar();
}
while(isdigit(c)){
x=x*10+c-'0';
c=getchar();
}
return f?-x:x;
}
ll p;
struct matrix{
ll a[2][2];
matrix operator *(matrix b){
matrix c;
for(int i=0;i<2;i++) for(int j=0;j<2;j++){
c.a[i][j]=0;
for(int k=0;k<2;k++)
c.a[i][j]=(c.a[i][j]+a[i][k]*b.a[k][j]%p)%p;
}
return c;
}
};
matrix ksm(matrix a,ll b){
matrix ans;
for(int i=0;i<2;i++) for(int j=0;j<2;j++) ans.a[i][j]=0;
for(int i=0;i<2;i++) ans.a[i][i]=1%p;
while(b>0){
if(b%2==1) ans=ans*a;
a=a*a;
b/=2;
}
return ans;
}
int main(){
int t=readint();
while(t--){
ll n,b;
n=readint();
b=readint();
p=readint();
matrix a;
a.a[0][0]=b%p;
a.a[0][1]=0;
a.a[1][0]=a.a[1][1]=1%p;
a=ksm(a,n);
printf("%lld
",a.a[1][0]);
}
return 0;
}
T2 dispute
Subtask 1
随便暴力。
Subtask 2
预处理出每个数的 (k) 次前驱(即迭代 “此数前一个出现的位置” (k) 次),然后查询时在 ([l,r]) 中枚举之。
Subtask 3
(last_i) 记录每个数上一次出现的位置。维护 (max_i) 表示 (last_1) 到 (last_i) 的最大值。注意到,当 (L leq max_R) 时,区间内一定存在两个相同的数,而当 (max_R<L) 时,一定不存在。可以据此判断答案。
Subtask 4
做法多样。这里给出两种出题人认为较为简单的做法。
1.可以选择优化 (Subtask 2) 的方法:
即:找到每个数的 (k) 次前驱后,就变成了 (RMQ) 问题,在线用 (ST) 表解决即可。
2.可以选择分块:
看到 (5e4) 应该想到良心出题人(xiong_6)想让你们用分块过这个子任务。区间众数,分块,没啥好讲的吧。
Subtask 5
有的人可能看到 Subtask 3 就已经理解了。没错这题就这么水。结合 Subtask 2 和 Subtask 3 的方法即可得到正解。即维护每个数的 (k) 次前驱 (pre_i)(即迭代 “此数前一个出现的位置” (k) 次),维护 (maxp_i) 表示 (pre_1) 到 (pre_i) 的最大值。然后查询时比较 (maxp_R) 和 (L) 的大小即可。线性。
下为xiong_6的代码。
#include<bits/stdc++.h>
using namespace std;
namespace IO {
#define _CCF_ 8388608
#define getchar() (p1==p2&&(p2=(p1=Ibuf)+fread(Ibuf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
#define putchar(c) (O-Obuf<_CCF_)?(*O++=c):(fwrite(Obuf,O-Obuf,1,stdout),O=Obuf,*O++=c)
char Ibuf[_CCF_],*p1=Ibuf,*p2=Ibuf,Obuf[_CCF_],*O=Obuf;
int f,ch,Onum[32],Ohd; long long k;
template<typename T>inline void read(T&x){
x=f=ch=0; while(!isdigit(ch)) f|=(ch=='-'),ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^'0'),ch=getchar();
f&&(x=-x);}
template<typename T>inline void write(T x){
if(x==0) return putchar('0'),void(); if(x<0) putchar('-'),x=-x;
while(x>0) k=x/10,Onum[++Ohd]=(x-(k<<1)-(k<<3))^'0',x=k;
while(Ohd>0) putchar(Onum[Ohd]),--Ohd;}
inline void _Exit0() {fwrite(Obuf,O-Obuf,1,stdout),exit(0);}
} using namespace IO;
long long n,q,K,l,r;
long long sumans;
long long a[2000011];
vector<long long>pla[2000011];
long long last[2000011];
long long max_last[2000011];
int main(){
read(n);
read(K);
read(q);
for(long long i=1;i<=n;i++){
read(a[i]);
pla[a[i]].push_back(i);
}
for(long long i=1;i<=n;i++){
if(pla[i].size()>=K){
for(long long j=K-1;j<pla[i].size();j++){
last[pla[i][j]]=pla[i][j-K+1];
}
}
}
for(long long i=1;i<=n;i++){
max_last[i]=max(max_last[i-1],last[i]);
}
while(q--){
read(l);
read(r);
l=(l+K*sumans-1)%n+1;
r=(r+K*sumans-1)%n+1;
sumans%=n;
if(max_last[r]>=l){
write(2);
putchar('
');
sumans+=2;
}
else{
write(1);
putchar('
');
sumans+=1;
}
}
IO::_Exit0();
}
T3 transport
Subtask 1
求 (LCA) , 暴力枚举。
Subtask 2
菊花图。分“从根到叶子”和“从叶子到叶子”的路径两种情况讨论。
Subtask 3
链。可以分别计算点和边的贡献:一个点会被其左边到右边的路径、从其自身出发的路径经过,而一个边只会被从其左边点到其右边点的路径经过。
Subtask 4
所有点权为0即可计算边的贡献:一条边会且只会被这样的两个点经过:一个在子树内,一个在子树外。这样就可以通过 子树内点数乘以子树外点数乘以边权 算出边的贡献。
记录出入子树时间戳,或者直接回溯过程中求子树大小皆可。
Subtask 5
思路一样,分别计算点和边对答案的贡献。边的贡献在 Subtask4 中。对于一个点,他的贡献分三种:
1.被从子树外到子树内的点经过
这种情况答案为 子树外点数*子树内点数*点权
2.此点为子树内两点的 (LCA)
显然,这种情况会且只会在这两个点分别在其不同的儿子的子树内出现。令点 (i) 的子树大小为 (siz_i) ,令点 (i) 的儿子集合为 (S_i) ,那么点 (x) 的贡献为:
由于需要线性算法,所以我们记录其每一个儿子子树大小的 和的平方 与 平方的和 然后利用这个公式得出答案:
3.由此点出发的路径
一共有 ((n-1)) 条。
这样就求出了答案。
下为imzzy的代码。
#include<bits/stdc++.h>
#define rgi register int
#define ll long long
namespace IO {
#define _CCF_ 8388608
#define getchar() (p1==p2&&(p2=(p1=Ibuf)+fread(Ibuf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
#define putchar(c) (O-Obuf<_CCF_)?(*O++=c):(fwrite(Obuf,O-Obuf,1,stdout),O=Obuf,*O++=c)
char Ibuf[_CCF_],*p1=Ibuf,*p2=Ibuf,Obuf[_CCF_],*O=Obuf;
int f,ch,Onum[32],Ohd; ll k;
template<typename T>inline void read(T&x){
x=f=ch=0; while(!isdigit(ch)) f|=(ch=='-'),ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^'0'),ch=getchar();
f&&(x=-x);}
template<typename T>inline void write(T x){
if(x==0) return putchar('0'),void(); if(x<0) putchar('-'),x=-x;
while(x>0) k=x/10,Onum[++Ohd]=(x-(k<<1)-(k<<3))^'0',x=k;
while(Ohd>0) putchar(Onum[Ohd]),--Ohd;}
inline void _Exit0() {fwrite(Obuf,O-Obuf,1,stdout),exit(0);}
} using namespace IO;
const int mod=998244353,maxn=2000004;
int n,a[maxn];
struct EDGE{int v,w,nxt;}e[maxn<<1];
int first[maxn],cnte;
inline void addedge(int u,int v,int w) {e[++cnte]=(EDGE){v,w,first[u]},first[u]=cnte;}
int ans,siz[maxn],sum[maxn];
void dfs(int p,int f,int w) {int tmp=0;
for(rgi i=first[p];i;i=e[i].nxt) if(e[i].v!=f)
dfs(e[i].v,p,e[i].w),siz[p]+=siz[e[i].v],tmp=(tmp+(ll)siz[e[i].v]*siz[e[i].v])%mod;
tmp=((ll)siz[p]*siz[p]-tmp+mod)%mod*499122177%mod;
tmp=(tmp+(ll)siz[p]*(n-siz[p]-1)+n-1)%mod;
ans=(ans+(ll)tmp*a[p])%mod;
++siz[p];
ans=(ans+(ll)w*siz[p]%mod*(n-siz[p]))%mod;
}
signed main(){
int u,v,w; read(n);
for(rgi i=1;i<=n;++i) read(a[i]);
for(rgi i=1;i<n;++i) read(u),read(v),read(w),addedge(u,v,w),addedge(v,u,w);
dfs(1,0,0),write(ans);
_Exit0();
}