1. 程式人生 > 其它 >【考試總結】2022-05-17

【考試總結】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<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\) 在區間中出現次數的字首和

    預處理每個下標是這個數字在序列中第幾次出現,從 \(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<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)\)

此時查詢即可進行一次重鏈剖分,維護每個點到重鏈鏈頂的二元組,最後分 \(\rm 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\) 所在的鏈上邊權和,\(m\)\(S,T\) 之間的鏈的條數,Sum_edge 表示這個點雙裡面的邊權和

實現的時候可以給每個點雙開一個 unordered_map<int,vector<pair<int,int> > > 來存點雙裡面的邊,同時可以建立圓方樹之後再進行邊的分配,不難發現如果設 \(bel_x\) 表示覆蓋 \(x\) 的淺的點雙,那麼 \((u,v)\) 屬於 \(bel_u\) 或者 \(bel_v\) 之一

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;
}