n ≤ 2 × 1 0 5 n\leq 2\times 10^5 n≤2×105
题意真的很难懂。。。
简而言之就是有两棵树,每次求对于其中一棵树 A A A的一条边 ( p , q ) (p,q) (p,q)(设 p p p为 q q q的父亲,即 q q q在 p p p子树内),在另一棵树 B B B上的边 ( x , y ) (x,y) (x,y)(设 x , y x,y x,y在 A A A中 d f s dfs dfs序满足 d f n [ x ] < d f n [ y ] dfn[x]<dfn[y] dfn[x]<dfn[y]),且满足 A A A中的两个点分别在 p p p的子树内和子树外。
实际上只有两种情况:
考虑对于两棵树分别建立两颗线段树。
第一颗以 d f n [ x ] dfn[x] dfn[x]为下标,按 d f n [ y ] dfn[y] dfn[y]递增插入。第二颗以 d f n [ y ] dfn[y] dfn[y]为下标,按 d f n [ x ] dfn[x] dfn[x]递减插入。每个节点 v e c t o r vector vector储存下标区间对应边的另一端(第一颗是 d f n [ y ] dfn[y] dfn[y],第二颗是 d f n [ x ] dfn[x] dfn[x])。
每次查找相当于删去 v e c t o r vector vector末端的一段。直接线性扫指针,每条边存在于 log n \log n logn个节点中,所以时间复杂度为 O ( n log n ) O(n\log n) O(nlogn)。
n , m ≤ 5 × 1 0 4 n,m\leq 5\times 10^4 n,m≤5×104
点分治,记录每个点 i i i到分治重心 x x x的前缀 S i S_i Si(’(‘等价于1,’)'等价于-1)。
考虑一个合法点对 ( i , j ) (i,j) (i,j)( i i i走到 j j j,设分治重心为 x x x),则必然 S i + S j = 0 S_i+S_j=0 Si+Sj=0,且 S i S_i Si是 i i i到 x x x路径上的最大值, S j S_j Sj是 j j j到 x x x路径上的最小值。设 c n t i cnt_i cnti表示 x x x到 i i i路径上最值 S i S_i Si的出现次数。
若 S i = S j = 0 S_i=S_j=0 Si=Sj=0,则 f ( i , j ) = c n t i + c n t j f(i,j)=cnt_i+cnt_j f(i,j)=cnti+cntj,否则还要加上中间合并的一段: f ( i , j ) = c n t i + c n t j + 1 f(i,j)=cnt_i+cnt_j+1 f(i,j)=cnti+cntj+1
对于同一个极值的所有点,以 c n t cnt cnt为下标,所有点对于答案的贡献是卷积形式的,可以 F F T FFT FFT优化。
在每层分治中,设大小为 s i z e size size,分别对于每一个极值的所有点做 F F T FFT FFT,复杂度为 s i z e log s i z e size\log size sizelogsize,所以总复杂度 O ( n log 2 n ) O(n\log^2n) O(nlog2n)。
p.s.注意点分治合并的两端其中一条路径是不包含分治中心的。
#include
#define grd(i,x) for(i=head[x];i;i=nxt[i])
#define pb push_back
using namespace std;
const int N=5e4+10;
typedef long long ll;
typedef double db;
const db pi=acos(-1.0);
int n,m,typ[N],s[N],al[N],sz[N],MN,S,rt,len,L,dsz;
int head[N],to[N<<1],nxt[N<<1],tot,rv[N<<2];
ll ans[N],sum;bool vs[N];
inline char gc()
{
static char buf[N];static int p1=0,p2=0;
if(p1==p2) p1=0,p2=fread(buf,1,N,stdin);
if(p1==p2) return EOF;return buf[p1++];
}
char cp,os[100];
template<class T>inline void rd(T &x)
{
cp=gc();x=0;int f=0;
for(;!isdigit(cp);cp=gc()) if(cp=='-') f=1;
for(;isdigit(cp);cp=gc()) x=x*10+(cp^48);
if(f) x=-x;
}
template<class T>inline void ot(T &x)
{
os[0]='\n';int re=0;
for(;(!re)||x;x/=10) os[++re]='0'+x%10;
for(;~re;--re) putchar(os[re]);
}
inline void gt_char(int &x)
{for(cp=gc();(cp!='(')&&(cp!=')');cp=gc());x=(cp=='(')?1:-1;}
inline void lk(int u,int v)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}
struct cc{
db r,i;
cc(db r_=0,db i_=0):r(r_),i(i_){};
cc operator +(const cc&ky)const{return cc(r+ky.r,i+ky.i);}
cc operator -(const cc&ky)const{return cc(r-ky.r,i-ky.i);}
cc operator *(const cc&ky)const{return cc(r*ky.r-i*ky.i,r*ky.i+i*ky.r);}
void operator /=(const db&ky){r/=ky;i/=ky;}
}a[N<<2],b[N<<2];
inline void fft(cc *e,int pr)
{
int i,j,k;cc ori,x,y,pd;
for(i=0;i<len;++i) if(i<rv[i]) swap(e[i],e[rv[i]]);
for(i=1;i<len;i<<=1){
ori=cc(cos(pi/i),pr*sin(pi/i));
for(j=0;j<len;j+=(i<<1)){
pd=cc(1,0);
for(k=0;k<i;++k,pd=pd*ori){
x=e[j+k];y=pd*e[j+i+k];
e[j+k]=x+y;e[i+j+k]=x-y;
}
}
}
if(pr==1) return;
for(i=0;i<len;++i) e[i]/=len;
}
void fdrt(int x,int fr)
{
sz[x]=1;int mx=0,i,j;
grd(i,x) if((j=to[i])^fr && (!vs[j])){
fdrt(j,x);sz[x]+=sz[j];mx=max(mx,sz[j]);
}
mx=max(mx,S-sz[x]);
if(mx<MN) {MN=mx;rt=x;}
}
vector<int>f[N<<1],g[N<<1];
void dfs1(int x,int fr,int ap,int mx,int ss)
{
ss+=typ[x];dsz++;
if(!ss) ap++;
else if(ss==1) mx++,ss=ap=0;
if(!ss) f[S+mx].pb(ap);
int i,j;
grd(i,x) if(((j=to[i])^fr)&&(!vs[j])) dfs1(j,x,ap,mx,ss);
}
void dfs2(int x,int fr,int ap,int mx,int ss)
{
ss+=typ[x];
if(!ss) ap++;
else if(ss==-1) mx--,ss=ap=0;
if(!ss) g[S+mx].pb(ap);
int i,j;
grd(i,x) if(((j=to[i])^fr)&&(!vs[j])) dfs2(j,x,ap,mx,ss);
}
inline void cal(int x,int pr)
{
int sa=f[S+x].size(),sb=g[S-x].size();
if(!sa || !sb) return;
int i,la=0,lb=0;
for(i=0;i<sa;++i) la=max(la,f[S+x][i]);
for(i=0;i<sb;++i) lb=max(lb,g[S-x][i]);
for(len=1,L=0;len<=la+lb;len<<=1) L++;
for(i=1;i<len;++i) rv[i]=(rv[i>>1]>>1)|((i&1)<<(L-1));
for(i=0;i<len;++i) a[i]=b[i]=cc(0,0);
for(i=0;i<sa;++i) a[f[S+x][i]].r++;
for(i=0;i<sb;++i) b[g[S-x][i]].r++;
fft(a,1);fft(b,1);
for(i=0;i<len;++i) a[i]=a[i]*b[i];
fft(a,-1);
for(i=0;i<len;++i) ans[i+(x!=0)]+=pr*(ll)(a[i].r+0.5);
}
void del(int x,int op)
{
dsz=1;
if(op==-1) dfs1(x,0,0,0,-1);
else dfs1(x,0,0,1,0);
dfs2(x,0,0,0,0);
for(int i=0;i<=dsz;++i) cal(i,-1),f[i+S].clear(),g[S-i].clear();
}
void sol(int x)
{
vs[x]=true;dfs1(x,0,0,0,0);
int i,j,sze=S;
grd(i,x) if(!vs[j=to[i]]) dfs2(j,x,0,0,0);
g[S].pb(0);
for(i=0;i<=S;++i) cal(i,1),f[S+i].clear(),g[S-i].clear();
grd(i,x) if(!vs[j=to[i]]) del(j,typ[x]);
grd(i,x) if(!vs[j=to[i]]){
S=sz[j]>sz[x]?sze-sz[x]:sz[j];MN=N;
fdrt(j,x);sol(rt);
}
}
int main(){
int i,x,y;rd(n);
for(i=1;i<n;++i) {rd(x);rd(y);lk(x,y);lk(y,x);}
for(i=1;i<=n;++i) gt_char(typ[i]);
S=n;MN=N;fdrt(1,0);sol(rt);
sum=(ll)n*n;
for(i=1;i<=n;++i) sum-=ans[i];ans[0]=sum;
for(rd(m);m;--m)
{rd(i);ot(ans[i]);}
fclose(stdin);fclose(stdout);
return 0;
}
n ≤ 2 × 1 0 5 n\leq 2\times 10^5 n≤2×105
感性证明:
两个 < n < \sqrt n <n的数合并显然没有分别自乘优,则必然是 < n <\sqrt n <n的和 > n >\sqrt n >n的合并。
显然 > n >\sqrt n >n的质因子最多只能有一个,考虑 > 1 >1 >1个 < n <\sqrt n <n的质因子和一个 > n >\sqrt n >n的情况,由于 > n >\sqrt n >n的质因子个数显著多于 < n <\sqrt n <n的质因子个数,所以一个 < n <\sqrt n <n,一个 > n >\sqrt n >n的情况更优。
设质因子 x x x能凑出的最大的 ≤ n \leq n ≤n的数为 f ( x ) f(x) f(x)。
构造二分图,所有 < n < \sqrt n <n的数 i i i向所有 > n >\sqrt n >n的数 j j j连费用为 f ( i , j ) − f ( i ) − f ( j ) f(i,j)-f(i)-f(j) f(i,j)−f(i)−f(j)的边。
跑最大费用可行流即可。
#include
#define mid (l+r>>1)
using namespace std;
const int N=2e4,M=4e6+10,inf=0x3f3f3f3f;
typedef long long ll;
int S,T,n,p[N],bs,num,v[N],fr[N],bel[N];
int head[N],to[M],nxt[M],w[M],cc[M],tot=1;
bool pri[200005];ll ans=1,dis[N];
inline void pre()
{
int i,j;
for(i=2;i<=n;++i){
if(!pri[i]) p[++num]=i;
for(j=1;j<=num && i*p[j]<=n;++j){
pri[i*p[j]]=true;if(i%p[j]==0) break;
}
}
}
inline void lk(int u,int v,int flw,int vv)
{
to[++tot]=v;nxt[tot]=head[u];head[u]=tot;w[tot]=flw;cc[tot]=vv;
to[++tot]=u;nxt[tot]=head[v];head[v]=tot;w[tot]=0;cc[tot]=-vv;
}
struct P{int id;ll v;bool operator<(const P&ky)const{return v<ky.v;}}tp;
priority_queue<P>que;
inline bool spfa()
{
memset(dis,0x8f,sizeof(ll)*(T+1));
int i,j,x;dis[S]=0;que.push((P){S,0LL});
for(;!que.empty();){
tp=que.top();x=tp.id;que.pop();
if(dis[x]!=tp.v) continue;
for(i=head[x];i;i=nxt[i]){
j=to[i];if((!w[i])||(dis[j]>=dis[x]+cc[i])) continue;
dis[j]=dis[x]+cc[i];fr[j]=x;bel[j]=i;
que.push((P){j,dis[j]});
}
}
return dis[T]>0;
}
int main(){
int i,j,k,t,l,r,pos;
scanf("%d",&n);bs=sqrt(n);pre();S=num+1;T=S+1;
for(l=1,r=num;l<=r;) p[mid]<=bs?(l=(pos=mid)+1):r=mid-1;
for(i=1;i<=pos;++i){
for(j=k=p[i];(ll)k*j<=n;k*=j);
ans+=k;v[i]=k;lk(S,i,1,0);
}
for(i=pos+1;i<=num;++i) ans+=p[i],v[i]=p[i],lk(i,T,1,0);
for(i=1;i<=pos;++i){
t=p[i];
for(j=pos+1;j<=num;++j){
for(k=t*p[j];(ll)k*t<=n;k*=t);
if(k>v[i]+v[j] && k<=n) lk(i,j,1,k-v[i]-v[j]);
}
}
for(;spfa();){
k=inf;
for(i=T;i!=S;i=fr[i]) k=min(k,w[bel[i]]);
ans+=dis[T]*k;
for(i=T;i!=S;i=fr[i]) w[bel[i]]-=k,w[bel[i]^1]+=k;
}
printf("%lld",ans);
return 0;
}
小结
看了很久才发现T1可以KD-tree二维数点(之前还码二维线段树,BIT之类的),由于不熟练又码了很久,常数巨大且RE了,取得了 50 p t s 50pts 50pts的好成绩。
实际上是思维僵化了,线段树也是可以做的。
T2秒想点分治,但对于括号匹配的本质不熟悉,导致即使想到了FFT也不知道具体如何维护。暴力走人。
T3的 80 p t s 80pts 80pts是某年NOI(寿司晚宴)分组状压DP原题。做过但还是没想起来,难受。