2019雅礼集训day2 题解

T1 two

2019雅礼集训day2 题解_第1张图片
n ≤ 2 × 1 0 5 n\leq 2\times 10^5 n2×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的子树内和子树外。

实际上只有两种情况:

  • i n [ p ] ≤ d f n [ x ] ≤ o u t [ p ] , o u t [ p ] < d f n [ y ] in[p]\leq dfn[x]\leq out[p],out[p]<dfn[y] in[p]dfn[x]out[p],out[p]<dfn[y]
  • d f n [ x ] < i n [ p ] , i n [ p ] ≤ d f n [ y ] ≤ o u t [ p ] dfn[x]<in[p],in[p]\leq dfn[y]\leq out[p] dfn[x]<in[p],in[p]dfn[y]out[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)


T2 bracket

2019雅礼集训day2 题解_第2张图片
n , m ≤ 5 × 1 0 4 n,m\leq 5\times 10^4 n,m5×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;
}

T3 sum

在这里插入图片描述

n ≤ 2 × 1 0 5 n\leq 2\times 10^5 n2×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原题。做过但还是没想起来,难受。

你可能感兴趣的:(---多项式---,线段树,费用流,点分治,二分图匹配,FFT,NTT,2019YALIWC)