【考试总结】2022-05-17

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

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

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

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

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

转移有如下几种:

  • 在第一棵树上是非叶子,在第二棵树上是叶子: fi+1,j,kfi,j1,k×j×(k+1)

  • 在第二棵树上是非叶子,在第一棵树上是叶子: fi+1,j,kfi,j,k+1×(j+1)×(k+1)

  • 钦定当前点在第一棵树或者第二棵树上是没有连边的非叶子,转移合并表达为 fi+1,j,kfi,j,k×(j+1)×(k+1)

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

初始化即计算 1 的连边方式:f1,0,i=i+1,而答案也就是枚举 n 的决策: fk,i,0×(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<n;++i) f[cur][0][i]=(i+1)%mod;
	for(int i=2;i<=n;++i){
		int ans=0;
		for(int j=0;j<i;++j) ckadd(ans,mul(j+1,f[cur][j][0]));
		print(ans);
		if(i==n) break;
		for(int j=0;j<=i;++j){
			for(int k=0;k<=n;++k) if(f[cur][j][k]){
				ckdel(f[cur^1][j][k],mul(2*(j+1)*(k+1)%mod,f[cur][j][k]));
				ckadd(f[cur^1][j+1][k],mul(f[cur][j][k],(j+1)*(k+1)%mod));
				if(k) ckadd(f[cur^1][j][k-1],mul(f[cur][j][k],(j+1)*k%mod));
				f[cur][j][k]=0;
			}
		}
		cur^=1;
	}
	return 0;
}

众数

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

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

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

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

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

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

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

根号分治,对于出现次数大于 n 的元素跑第一个暴力,枚举每个 [1,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<int> 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]<t) pre[i]=-1;
				else pre[i]=app[a[i]][tim[i]-t];
				ckmax(pre[i],pre[i-1]);
			}
			for(int i=1;i<=m;++i){
				int indic=0;
				for(auto p:app[i]){
					if(pre[p-1]==-1) continue;
					while(pre[p-1]>app[i][indic]) ++indic;
					int dlt=t-(tim[p]-indic-1);
					ckmax(Mx[i],(int)app[i].size()+dlt);
				}
				if(pre[n]!=-1){
					while(indic<app[i].size()&&pre[n]>app[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)

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

那么剩下的问题就是求点双中任意两点 x,y(令 dis(S,x)<dis(S,y)) 之间的 (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 所在的链上边权和,mS,T 之间的链的条数,Sum_edge 表示这个点双里面的边权和

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

Code Display
const int N=1e6+10;
int n,m,Q;
vector<int> 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<mod&&sum<mod);}
	data operator *(const data &x)const{
		data res;
		res.num=mul(num,x.num);
		res.sum=add(mul(num,x.sum),mul(x.num,sum));
		return res;
	}
	data operator /(const data &x)const{
		data res;
		int inv=ksm(x.num,mod-2);
		res.num=mul(num,inv);
		res.sum=del(sum,mul(res.num,x.sum));
		ckmul(res.sum,inv);
		return res;
	}
}f[N],a[N];
struct DCC{
	int S,T,sum;
	unordered_map<int,vector<pair<int,int> > >G;
	unordered_map<int,int>id;
	vector<int> 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;i<node.size();++i) ckmax(mxdeg,(int)G[node[i]].size());
		for(int i=0;i<node.size();++i){
			if(G[node[i]].size()==mxdeg){
				if(!S) S=node[i];
				else if(!T) T=node[i];
				else break;
			}
		}
		dfs(S,0);
		return ;
	}	
	inline data calc(int x,int y){
		if(!id.count(x)||!id.count(y)||x==y) return data(1,0);
		if(id[x]>id[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;
}

posted @   没学完四大礼包不改名  阅读(63)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示