【考试总结】2022-05-17


思考 \(2^{n}n^2\) 的做法:枚举在第一棵树上那些点是叶子,计算方案数,并据此计算第二棵树的形成方案数

此时无论第一棵树还是第二棵树都需要求 \(f_{i,j,k}\) 表示前/后面还有 \(j\) 个元素不是叶子但是没有出点,有 \(k\) 个有出点的枝杈 的树的数量

将两棵树的 \(\rm DP\) 揉起来得到一些高复杂度的做法,而其冗余都出现在了要记录没有连边的非叶子在两棵树上分别的出现次数

那么进行 容斥降维,钦定一些点在两棵树上一定是不被连边的非叶子并附上 \(-1\) 的系数

具体而言,设 \(f_{j,k}\) 表示在已经处理过的点中第一棵树前面有 \(j\) 个非叶子而第二棵树后面有 \(k\) 个非叶子的方案数,注意这里的非叶子不包括 \(1,n\)

转移有如下几种:

  • 在第一棵树上是非叶子,在第二棵树上是叶子: \(f_{i+1,j,k}\leftarrow f_{i,j-1,k}\times j\times(k+1)\)

  • 在第二棵树上是非叶子,在第一棵树上是叶子: \(f_{i+1,j,k}\leftarrow f_{i,j,k+1}\times (j+1)\times(k+1)\)

  • 钦定当前点在第一棵树或者第二棵树上是没有连边的非叶子,转移合并表达为 \(f_{i+1,j,k}\leftarrow f_{i,j,k}\times(j+1)\times(k+1)\)

    这种转移中,由于钦定了其不合法性,所以其它的能连边的点的数量不发生变化,所以后两位不进行 \(\pm1\)

初始化即计算 \(1\) 的连边方式:\(f_{1,0,i}=i+1\),而答案也就是枚举 \(n\) 的决策: \(\sum f_{k,i,0}\times(i+1)\)

可能有点点卡常,实现的时候可以枚举到某个元素的时候再取模

Code Display
int n,f[2][510][510];
signed main(){
	freopen("tree.in","r",stdin); freopen("tree.out","w",stdout);
	n=read(); mod=read();
	int cur=0;
	for(int i=0;i

众数

如果新序列众数在原序列中没有出现过,那么它的出现次数一定不多于原序列众数,而在保证所有数字不完全一样的情况下容易构造使得新序列众数的出现次数大于原序列中众数出现次数

此时问题变成了找到一个区间并将区间里面的众数改成区间外众数并最大化数字的出现次数,有以下两种暴力:

  • 枚举每个数字 \(x\) 作为区间内众数,维护 \(x\) 在区间中出现次数的前缀和

    预处理每个下标是这个数字在序列中第几次出现,从 \(1\sim n\) 扫描整个序列便可找到出现次数差得最多的区间

  • 枚举区间内部众数的出现次数 \(k\),找到每个位置前面第 \(k-1\) 个与之相同的元素的位置 \(pre_i\) 并维护前缀 \(\max\)

    遍历每一种出现过的元素 \(x\),枚举 \(x\) 的每次出现作为右端点 \(+1\) 并根据上面维护的 \(pre_i\) 找到在满足众数出现 \(k\) 次的情况下 \(x\) 最少出现几次

    使用双指针将这部分做到 \(\Theta(1)\)

根号分治,对于出现次数大于 \(\sqrt n\) 的元素跑第一个暴力,枚举每个 \([1,\sqrt n]\) 中的 \(k\) 跑第二个暴力

注意处理翻转区间右端点是 \(n\) 的情况,也就是枚举完某个元素所有出现位置后还可以进行一个后缀的翻转

Code Display
const int N=5e5+10,B=500;
int b[N],a[N],n,m,tim[N],Mx[N],Mn[N],pre[N];
vector app[N];
signed main(){
	freopen("mode.in","r",stdin); freopen("mode.out","w",stdout);
	int T=read(); while(T--){
		n=read();
		for(int i=1;i<=n;++i) b[i]=a[i]=read();
		sort(b+1,b+n+1);
		m=unique(b+1,b+n+1)-b-1;
		for(int i=1;i<=n;++i){
			a[i]=lower_bound(b+1,b+m+1,a[i])-b;
			app[a[i]].emplace_back(i);
			tim[i]=app[a[i]].size();
		}
		int most=0,ans=0;
		rep(i,1,m) ckmax(most,Mx[i]=app[i].size());
		rep(x,1,m) if(app[x].size()>B){
			for(int i=1;i<=n;++i) pre[i]=pre[i-1]+(a[i]==x);
			for(int i=1;i<=n;++i) if(a[i]!=x){
				int dlt=pre[i]-tim[i]-Mn[a[i]];
				ckmin(Mn[a[i]],pre[i]-tim[i]);	
				ckmax(Mx[a[i]],dlt+(int)app[a[i]].size()+1);
			}
			rep(i,1,m) if(i!=x){
				int dlt=pre[n]-app[i].size()-Mn[i];
				ckmax(Mx[i],(int)app[i].size()+dlt);
				Mn[i]=0;
				ckmax(ans,Mx[i]);
			}
		}
		for(int t=1;t<=B;++t){
			if(most+t<=ans) continue;
			pre[0]=-1;
			rep(i,1,n){
				if(tim[i]app[i][indic]) ++indic;
					int dlt=t-(tim[p]-indic-1);
					ckmax(Mx[i],(int)app[i].size()+dlt);
				}
				if(pre[n]!=-1){
					while(indicapp[i][indic]) ++indic;
					int dlt=t-((int)app[i].size()-indic);
					ckmax(Mx[i],(int)app[i].size()+dlt);
				}
				ckmax(ans,Mx[i]);
			}
		}
		print(ans);
		rep(i,1,m){
			if(ans==Mx[i]) print(b[i]);
			app[i].clear();
		}
	}
	return 0;
}

简单题

题目中给出的限制表明每个点双最多只有两个点满足度数大于 \(2\) 且这两个点之间通过若干条链相连接,称度数较大的这两个点为 \(S,T\)

用二元组 \((num,sum)\) 来表示路径条数以及路径权值和的信息,想合并或者分裂都好说

建立圆方树并将每个实点的点权设置为该点通过其在圆方树上的父亲对应的点双中的边走到它在圆方树上二级父亲的 \((num,sum)\)

此时查询即可进行一次重链剖分,维护每个点到重链链顶的二元组,最后分 \(\rm LCA\) 是虚点还是实点进行简单处理即可

那么剩下的问题就是求点双中任意两点 \(x,y\)(令 \(dis(S,x)) 之间的 \((num,sum)\),若两者之一是 \(S/T\) 那么可以简单计算

否则如果两个点在链接 \(S/T\) 的同一条链上,计算结果是 (m,Sum_edge+(m-2)*dis(S,x)+dis(y,T))

如果两个点不在同一条链上则为 ((m-1)*2,Sum_edge*2+(m-3)*(len[chain_u]+len[chain_v]))

其中 len[chain_x] 表示 \(x\) 所在的链上边权和,\(m\)\(S,T\) 之间的链的条数,Sum_edge 表示这个点双里面的边权和

实现的时候可以给每个点双开一个 unordered_map > > 来存点双里面的边,同时可以建立圆方树之后再进行边的分配,不难发现如果设 \(bel_x\) 表示覆盖 \(x\) 的浅的点双,那么 \((u,v)\) 属于 \(bel_u\) 或者 \(bel_v\) 之一

Code Display
const int N=1e6+10;
int n,m,Q;
vector G[N],g[N];
int nds,dfn[N],pw[N],low[N],tim,stk[N],Top;
struct data{
	int num,sum; data(){num=sum=0;} 
	data(int x,int y){num=x; sum=y; assert(num > >G;
	unordered_mapid;
	vector num,node,ch,dis;
	inline void ins_edge(int u,int v,int w){
		if(!id.count(v)||!id.count(u)) return ;
		G[u].emplace_back(v,w);
		G[v].emplace_back(u,w);
	}
	inline void ins_node(int x){
		id[x]=node.size();
		node.emplace_back(x);
	}
	inline void dfs(int x,int fat){
		if(x==T){
			ch.emplace_back(dis[id[T]]);
			sum+=dis[id[T]];
			return ;
		}
		for(auto e:G[x]) if(e.fir!=fat){
			dis[id[e.fir]]=dis[id[x]]+e.sec;
			num[id[e.fir]]=ch.size();
			dfs(e.fir,x);
		}
		return ;
	}
	inline void init(){
		dis.resize(node.size());
		num.resize(node.size());
		int mxdeg=0;
		for(int i=0;iid[y]) swap(x,y);
		int u=id[x],v=id[y];
		if(x==S){
			if(y==T) return data(ch.size(),sum%mod);
			return data(ch.size(),(sum+(ch.size()-2)*(ch[num[v]]-dis[v]))%mod);	
		}
		if(x==T){
			return data(ch.size(),(sum+(ch.size()-2)*dis[v])%mod);
		}
		if(y==S){
			return data(ch.size(),(sum+(ch.size()-2)*(ch[num[u]]-dis[u]))%mod);
		}
		if(y==T){
			return data(ch.size(),(sum+(ch.size()-2)*dis[u])%mod);
		}
		if(num[u]==num[v]){
			return data(ch.size(),(sum+(ch.size()-2)*(ch[num[v]]-abs(dis[u]-dis[v])))%mod);
		}
		return data((ch.size()-1)*2,(2*sum+(ch[num[u]]+ch[num[v]])*((ch.size()+mod-3)%mod))%mod);
	}
}dcc[N];
int bel[N];
inline void tarjan(int x){
	reverse(g[x].begin(),g[x].end());
	stk[++Top]=x; dfn[x]=low[x]=++tim;
	for(auto t:g[x]){
		if(!dfn[t]){
			tarjan(t); ckmin(low[x],low[t]);
			if(dfn[x]<=low[t]){
				++nds;
				G[nds].emplace_back(x);
				G[x].emplace_back(nds);
				dcc[nds].ins_node(x);
				bel[x]=nds;
				do{
					G[stk[Top]].emplace_back(nds);
					G[nds].emplace_back(stk[Top]);
					dcc[nds].ins_node(stk[Top]);
					bel[stk[Top]]=nds;
					--Top;
				}while(stk[Top+1]!=t);
				
			}
		}else ckmin(low[x],dfn[t]);
	}
	return ;
}
int dep[N],top[N],fa[N],son[N],siz[N];
inline void dfs1(int x,int fat){
	dep[x]=dep[fa[x]=fat]+(siz[x]=1); 
	if(fa[fa[x]]&&x<=n) a[x]=dcc[fa[x]].calc(x,fa[fa[x]]);
	else a[x]={1,0};
	for(auto t:G[x]) if(t!=fat){
		dfs1(t,x); siz[x]+=siz[t];
		if(siz[t]>siz[son[x]]) son[x]=t;
	}
	return ;
}
int ocnt;
inline void dfs2(int x,int topf){
	top[x]=topf; f[x]=a[x];
	if(top[x]!=x) f[x]=f[fa[x]]*a[x];
	if(son[x]) dfs2(son[x],topf);
	for(auto t:G[x]) if(t!=fa[x]&&t!=son[x]) dfs2(t,t);
}
int u[N],v[N],w[N];
signed main(){
	freopen("simple.in","r",stdin); freopen("simple.out","w",stdout);
	n=1e6; pw[0]=1;
	for(int i=1;i<=n;++i) pw[i]=add(pw[i-1],pw[i-1]);
	
	nds=n=read(); m=read(); Q=read();
	for(int i=1;i<=m;++i){
		u[i]=read(),v[i]=read(),w[i]=read();
		g[u[i]].emplace_back(v[i]);
		g[v[i]].emplace_back(u[i]);
	}
	tarjan(1);
	for(int i=1;i<=m;++i){
		if(bel[u[i]]) dcc[bel[u[i]]].ins_edge(u[i],v[i],w[i]);
		if(bel[v[i]]&&bel[v[i]]!=bel[u[i]]) dcc[bel[v[i]]].ins_edge(u[i],v[i],w[i]); 
	}
	for(int i=n+1;i<=nds;++i) dcc[i].init();
	dfs1(1,0); dfs2(1,1);
	while(Q--){
		int x=read(),y=read(),lx=x,ly=y;
		data ans=data(1,0);
		while(top[x]!=top[y]){
			if(dep[top[x]]>dep[top[y]]) swap(lx,ly),swap(x,y);
			ans=ans*f[y];
			y=fa[ly=top[y]];
		}
		if(dep[x]>dep[y]) swap(lx,ly),swap(x,y);
		if(x!=y) ans=ans*f[y]/f[x],ly=son[x];
		if(x>n) ans=ans*dcc[x].calc(lx,ly)/a[ly]/a[lx];
		print(ans.sum);
	}
	return 0;
}

相关