https://atcoder.jp/contests/abc248
G
\[\sum_{i=1}^n\sum_{j=i+1}^n C(i,j)
\]
关于 gcd 有关的要不就莫反要不就欧拉反演。
\(\gcd(S)=\sum_{d|\gcd(S)}\varphi(d)=\sum_{d|x,x\in S}\varphi(d)\)
那么我们可以枚举 \(d\),抽离出 d 的倍数的图,那么这张图上的任意一条路径 d 都是可以贡献到的,总贡献系数是什么?显然就是所有路径经过的点的数量总和。这个东西可以树形 dp,钦定一个点求出。然后就做完了。
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int mod=998244353,N=(int)(1e5+5);
bool vis[N];
int n,a[N],phi[N],pri[N],NW,cntt;
void init() {
phi[1]=1;
for(int i=2;i<=N-5;i++) {
if(!vis[i]) pri[++cntt]=i,phi[i]=i-1;
for(int j=1;j<=cntt&&pri[j]*i<=N-5;j++) {
vis[i*pri[j]]=1;
if(i%pri[j]==0) {
phi[i*pri[j]]=phi[i]*pri[j]; break ;
}
phi[i*pri[j]]=phi[i]*phi[pri[j]];
}
}
}
vector<int>vec[N];
bool fl[N];
int tot,b[N];
void sol(int x,int id) {
for(int i=1;i*i<=x;i++) {
if(x%i==0) {
if(!fl[i]) fl[i]=1,b[++tot]=i;
vec[i].push_back(id);
if(i*i==x) continue ;
if(!fl[x/i]) fl[x/i]=1,b[++tot]=x/i;
vec[x/i].push_back(id);
}
}
}
struct edge {
int nex,to;
}e[N<<1];
int cnt,hea[N],sz[N],XGF,rtsz;
void add_edge(int x,int y) {
e[++cnt].nex=hea[x]; e[cnt].to=y; hea[x]=cnt;
}
void dfs(int x,int ff) {
fl[x]=1; sz[x]=1;
for(int i=hea[x];i;i=e[i].nex) {
int y=e[i].to; if(y==ff||a[y]%NW) continue ;
dfs(y,x); sz[x]+=sz[y];
}
}
void dfs2(int x,int ff) {
fl[x]=0; int qwq=rtsz-sz[x]+1;
XGF=(XGF+qwq-1)%mod;
for(int i=hea[x];i;i=e[i].nex) {
int y=e[i].to; if(y==ff||!fl[y]) continue ;
XGF=(XGF+qwq*sz[y]%mod)%mod;
qwq=qwq+sz[y];
}
for(int i=hea[x];i;i=e[i].nex) {
int y=e[i].to; if(y==ff||!fl[y]) continue ;
dfs2(y,x);
}
}
signed main() {
cin.tie(0); ios::sync_with_stdio(false);
init();
cin>>n; for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<n;i++) {
int x,y; cin>>x>>y; add_edge(x,y); add_edge(y,x);
}
for(int i=1;i<=n;i++) sol(a[i],i);
memset(fl,0,sizeof(fl));
int ans=0;
for(int i=1;i<=tot;i++) {
NW=b[i]; XGF=0;
for(int j=1;j<=n;j++) fl[i]=0;
for(int x:vec[NW]) {
if(!fl[x]) dfs(x,0);
}
for(int x:vec[NW]) {
if(fl[x]) rtsz=sz[x],dfs2(x,0);
}
ans=(ans+XGF*phi[NW]%mod)%mod;
}
ans=(ans%mod+mod)%mod;
cout<<ans;
return 0;
}
Ex
没啥好说的,扫描线扫 R,DS 下标的 L 维护 \([L,R]\) 区间的 \(\max+\min\),发现预处理单调栈然后遇到 i,那么它当最大值的区间的左端点范围应该是 \([Lmx_i+1,i]\),需要注意的是当扫到了右边第一个比 i 大的数时,就需要消去 i 的贡献。发现需要支持区间加,区间小于等于某个数的个数,分块即可。
#include <bits/stdc++.h>
#define pb push_back
#define il inline
using namespace std;
// Li 表示左边第一个 比它大的
// 那么扫 R,每次覆盖都是 [Li+1,i]
// Ri到了那么就要消去贡献了
const int N=(int)(2e+5),M=500;
vector<int>vec[N],vec2[N];
int st[N],top,B[M][M],sz[M];
int n,m,K,bl,id[N],L[M],R[M],a[N],Lmx[N],Rmx[N],Lmi[N],Rmi[N],sum[N];
int tag[M];
il void build(int x) {
sz[x]=0;
for(int i=L[x];i<=R[x];i++) B[x][++sz[x]]=sum[i];
}
il void rebuild(int x) {
sz[x]=0;
for(int i=L[x];i<=R[x];i++) B[x][++sz[x]]=sum[i];
sort(B[x]+1,B[x]+1+sz[x]);
}
il void upt(int x,int y,int v) {
if(id[x]==id[y]) {
for(int i=x;i<=y;i++) sum[i]+=v;
rebuild(id[x]);
} else {
for(int i=x;i<=R[id[x]];i++) sum[i]+=v;
for(int i=L[id[y]];i<=y;i++) sum[i]+=v;
rebuild(id[x]); rebuild(id[y]);
for(int i=id[x]+1;i<id[y];i++) tag[i]+=v;
}
}
il int qry(int x,int y,int Lim) {
int res=0;
if(id[x]==id[y]) {
for(int i=x;i<=y;i++) if(sum[i]+tag[id[x]]<=Lim) ++res;
} else {
for(int i=x;i<=R[id[x]];i++) if(sum[i]+tag[id[x]]<=Lim) ++res;
for(int i=L[id[y]];i<=y;i++) if(sum[i]+tag[id[y]]<=Lim) ++res;
for(int i=id[x]+1;i<id[y];i++) {
int p=upper_bound(B[i]+1,B[i]+1+sz[i],Lim-tag[i])-B[i]-1;
res+=p;
}
}
return res;
}
signed main() {
cin.tie(0); ios::sync_with_stdio(false);
cin>>n>>K; m=3*n;
for(int i=1;i<=n;i++) cin>>a[i];
top=0;
for(int i=1;i<=n;i++) {
while(top&&a[st[top]]<a[i]) Rmx[st[top]]=i,--top;
st[++top]=i;
}
top=0;
for(int i=n;i>=1;i--) {
while(top&&a[st[top]]<a[i]) Lmx[st[top]]=i,--top;
st[++top]=i;
}
top=0;
for(int i=1;i<=n;i++) {
while(top&&a[st[top]]>a[i]) Rmi[st[top]]=i,--top;
st[++top]=i;
}
top=0;
for(int i=n;i>=1;i--) {
while(top&&a[st[top]]>a[i]) Lmi[st[top]]=i,--top;
st[++top]=i;
}
for(int i=1;i<=n;i++) {
if(Rmx[i]) vec[Rmx[i]].pb(i);
if(Rmi[i]) vec2[Rmi[i]].pb(i);
}
long long ans=0; bl=sqrt(n);
for(int i=1;i<=n;i++) sum[i]=i;
for(int i=1;i<=n;i++) id[i]=(i-1)/bl+1;
for(int i=1;i<=id[n];i++) L[i]=(i-1)*bl+1,R[i]=i*bl; R[id[n]]=n;
for(int i=1;i<=id[n];i++) build(i);
for(int i=1;i<=n;i++) {
upt(Lmx[i]+1,i,a[i]); upt(Lmi[i]+1,i,-a[i]);
for(int x:vec[i]) upt(Lmx[x]+1,x,-a[x]);
for(int x:vec2[i]) upt(Lmi[x]+1,x,a[x]);
ans+=qry(1,i,i+K);
}
cout<<ans;
return 0;
}