集训做题杂记1

[CTS2024] 众生之门

小清新构造题。

观察大样例可以发现答案不大于 \(3\),感性猜测可以在路径长度不超过 \(3\) 的情况下遍历整棵树,事实也确实如此。

进一步考虑答案一般为 \(0\)\(1\),只有 \(n\) 比较小或者图为菊花时答案是固定的。

所以我们对较小的 \(n\) 跑暴力,菊花图特判,对其余情况随机一个排列,每次交换两项,直到答案小于等于 \(1\) 输出即可。

由于答案值域是 \(O(n)\) 的,所以期望也是 \(O(n)\) 次找到答案,复杂度没问题。

#include<bits/stdc++.h>
using namespace std;
#define PII pair<int,int>
#define PLI pair<ll,int>
#define PIL pair<int,ll>
#define PLL pair<ll,ll>
#define fi first
#define se second
#define YES() cout<<"YES\n",0
#define NO() cout<<"NO\n",0
#define Yes() cout<<"Yes\n",0
#define No() cout<<"No\n",0
using ll=long long;
using uint=unsigned int;
using ull=unsigned long long;
using lb=long double;
const int N=5e4+5;
int T,n,s,t,dep[N],p[N],ANS,ans[N];
int siz[N],wc[N],top[N],fa[N];
vector<int>e[N];
void dfs1(int now,int f){
	siz[now]=1,wc[now]=0,fa[now]=f;
	for(auto it:e[now]){
		if(it!=f){
			dep[it]=dep[now]+1;
			dfs1(it,now);
			siz[now]+=siz[it];
			if(siz[it]>siz[wc[now]])	wc[now]=it;
		}
	}
}
void dfs2(int now,int Top){
	top[now]=Top;
	if(wc[now])	dfs2(wc[now],Top);
	for(auto it:e[now]){
		if(it!=fa[now]&&it!=wc[now])	dfs2(it,it);
	}
}
int LCA(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])	swap(x,y);
		x=fa[top[x]];
	}
	return dep[x]<dep[y]?x:y;
}
bool flower(){
	for(int i=1;i<=n;i++)	if(e[i].size()==n-1)	return 1;
	return 0;
}
int dis(int x,int y){return dep[x]+dep[y]-2*dep[LCA(x,y)];}
mt19937 rd(std::random_device{}());
int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	cin>>T;
	while(T--){
		cin>>n>>s>>t;
		for(int i=1;i<=n;i++)	e[i].clear();
		for(int i=1,u,v;i<n;i++){
			cin>>u>>v;
			e[u].push_back(v);
			e[v].push_back(u);
		}
		dep[1]=0;dfs1(1,1),dfs2(1,1);
		if(n<=8){
			ANS=10;
			for(int i=1;i<=n;i++)	p[i]=i;
			do{
				if(p[1]!=s||p[n]!=t)	continue;
				int tmp=0;
				for(int i=2;i<=n;i++)	tmp^=dis(p[i],p[i-1]);
				if(tmp<ANS){
					ANS=tmp;
					for(int i=1;i<=n;i++)	ans[i]=p[i];
				}	
			}while(next_permutation(p+1,p+n+1));
			for(int i=1;i<=n;i++)	cout<<ans[i]<<" ";
			cout<<"\n";
		}else{
			p[1]=s,p[n]=t;
//			for(int i=2;i<=n-1;i++)	ans[i]=p[i]=i-1+(i-1>=s)+(i-1+(i-1>=s)>=t);
			for(int i=1;i<=n;i++)if(i!=s&&i!=t)	p[i-(i>s)-(i>t)+1]=i;
			if(flower()){
				for(int i=1;i<=n;i++)	cout<<p[i]<<" ";
				cout<<"\n";
				continue;
			}
			int tmp=0;
			for(int i=2;i<=n;i++)	tmp^=dis(p[i],p[i-1]);
			while(tmp>1){
				int l=rd()%(n-2)+2,r=rd()%(n-2)+2;
				while(l==r)	r=rd()%(n-2)+2;
				tmp^=dis(p[l],p[l-1])^dis(p[l],p[l+1])^dis(p[r],p[r-1])^dis(p[r],p[r+1]);
				swap(p[l],p[r]);
				tmp^=dis(p[l],p[l-1])^dis(p[l],p[l+1])^dis(p[r],p[r-1])^dis(p[r],p[r+1]);
			}
			for(int i=1;i<=n;i++)	cout<<p[i]<<" ";
			cout<<"\n";
		}
	}
}

P10813 【MX-S2-T4】 换

对于这种排序网络的题,可以考虑一个经典 trick,将序列 \(\{A_n\}\) 重赋值为 \(\{B_n(V)\}\) 满足:\(B_{i}(V)=[A_i\le V]\)

一个序列 \(\{A_n\}\) 合法,当且仅当 \(\forall V,\{B_n(V)\}\) 合法。

考虑直接预处理出所有 01 序列是否合法,这是 \(O(m2^n)\) 的。

发现序列 \(A\) 离散化后只有 \(O(n)\)\(V\) 是有意义的,考虑状压 DP,记 \(dp_{i,S}\) 表示离散化后 \(\{B_n(i)\}=S\)\(\exists x,A_x=i\) 的方案数。

转移就是填入 \(i+1\),有 \(dp_{i+1,S}=\sum_{T\subset S}dp_{i,T}\)

直接上子集枚举是 \(O(n3^n)\) 的,不太可行,考虑每一层 DP 前做一遍高维前缀和,于是复杂度变为 \(O(n^22^n)\)

总时间复杂度 \(O(n^2+m)2^n\)

#include<bits/stdc++.h>
using namespace std;
#define PII pair<int,int>
#define PLI pair<ll,int>
#define PIL pair<int,ll>
#define PLL pair<ll,ll>
#define fi first
#define se second
#define YES() cout<<"YES\n",0
#define NO() cout<<"NO\n",0
#define Yes() cout<<"Yes\n",0
#define No() cout<<"No\n",0
using ll=long long;
using uint=unsigned int;
using ull=unsigned long long;
using lb=long double;
const int N=18,M=505,mod=1e9+7;
ll n,V,m,dp[1<<N],SS[1<<N],ans;
int x[M],y[M];
bool f[1<<N],p[N+5];
bool check(int S){
	for(int i=1;i<=n;i++)	p[i]=(S>>(i-1))&1;
	for(int i=1;i<=m;i++){
		if(p[x[i]]>p[y[i]])	swap(p[x[i]],p[y[i]]);
	}
	bool tmp=1;
	for(int i=1;i<n;i++)	tmp&=(p[i]<=p[i+1]);
	return tmp;
}
ll q_pow(ll x,ll b){
	ll c=1;
	while(b){
		if(b&1)	c=c*x%mod;
		x=x*x%mod,b>>=1;
	}
	return c;
}
ll inv(ll x){return q_pow(x,mod-2);}
ll C(ll x,ll y){
	if(x<y||y<0)return 0;
	ll ret=1;
	for(int i=1;i<=y;i++)	ret=ret*(x-i+1)%mod*inv(i)%mod;
	return ret;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	cin>>n>>V>>m;
	for(int i=1;i<=m;i++)	cin>>x[i]>>y[i];
	for(int S=0;S<(1<<n);S++)	f[S]=check(S);
	dp[0]=1;
	for(int i=1;i<=n;i++){
		for(int S=0;S<(1<<n);S++)	SS[S]=dp[S];
		for(int j=0;j<n;j++){
			for(int S=0;S<(1<<n);S++){
				if(S&(1<<j))	SS[S]=(SS[S]+SS[S^(1<<j)])%mod;
			}
		}
		for(int S=0;S<(1<<n);S++)	dp[S]=(SS[S]-dp[S]+mod)*f[S]%mod;
		ans=(ans+C(V,i)*dp[(1<<n)-1]%mod)%mod;
	}
	cout<<ans<<"\n";
}

The only survival

发现每次可以钦定一堆点满足 \(dis_{1,n}\in [1,k] \cup \{> k\}\),这里 \(>k\) 当作同一类点,因为对 \(dis_{1,n}=k\) 没有意义。同时 \(dis_{1,n}\) 必须钦定为 \(k\)

新加入一个点 \(v\) 时,对于一条边 \(<u,v,w>\)\(w\in[dis_{1,v}-dis_{1,u},lim]\) 乘法原理即可算出总方案数,同时注意必须存在一条边满足 \(w=dis_{1,v}-dis_{1,u}\) ,减掉 \(\forall <u,v,w>,w>dis_{1,v}-dis_{1,u}\) 的方案数即可,快速求方案数可以记录 \(cnt_i=\sum_x [dis_{1,x}=i]\)

时间复杂度是划分数,所以 \(n\le 13\) 能跑。

#include<bits/stdc++.h>
using namespace std;
#define PII pair<int,int>
#define PLI pair<ll,int>
#define PIL pair<int,ll>
#define PLL pair<ll,ll>
#define fi first
#define se second
#define YES() cout<<"YES\n",0
#define NO() cout<<"NO\n",0
#define Yes() cout<<"Yes\n",0
#define No() cout<<"No\n",0
using ll=long long;
using uint=unsigned int;
using ull=unsigned long long;
using lb=long double;
const int N=15;
ll n,lim,k,mod,cnt[N],ans;
ll q_pow(ll x,ll b){
	ll c=1;
	while(b){
		if(b&1)	c=c*x%mod;
		x=x*x%mod,b>>=1;
	}
	return c;
}
ll inv(ll x){return q_pow(x,mod-2);}
ll fac[N],C[N][N];
void init(int x){fac[0]=1;for(int i=1;i<=x;i++)	fac[i]=fac[i-1]*i%mod;}
void dfs(int dep,int num,ll tmp){
	if(dep>k){
		ll tmpv=1;
		for(int i=0;i<=k;i++)	tmpv=tmpv*q_pow(lim-(k-i),cnt[i])%mod;
		return ans=(ans+tmp*q_pow(tmpv,n-num)%mod*q_pow(lim,(n-num)*(n-num-1)/2)%mod)%mod,void();
	}else{
		ll tmpv=1,subv=1;
		for(int i=0;i<dep;i++)	tmpv=tmpv*q_pow(lim-(dep-i)+1,cnt[i])%mod,subv=subv*q_pow(lim-(dep-i),cnt[i])%mod;
		tmpv=(tmpv+mod-subv)%mod;
		for(int i=(dep==k);num+i<=n;i++)	cnt[dep]=i,dfs(dep+1,num+i,tmp*q_pow(tmpv,i)%mod*q_pow(lim,i*(i-1)/2)%mod*C[n-num-1][i-(dep==k)]%mod);
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	freopen("graph.in","r",stdin);
	freopen("graph.out","w",stdout);
	cin>>n>>k>>lim>>mod;
	if(lim<k)	return cout<<"0\n",0;
	C[0][0]=1;
	for(int i=1;i<=n;i++){
		C[i][0]=1;
		for(int j=1;j<=i;j++)	C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
	}
	init(n);cnt[0]=1,n--;
	dfs(1,0,1);
	cout<<ans<<"\n";
}

神秘式子一则

原题钦定顺序然后就是求将 \(n\) 个不同小球放进任意个相同盒子使得每个盒子小球数量 \(\ge2\) 的方案数。

每个盒子至少放一个小球方案数是简单的斯特林数,考虑对只有一个小球的盒子个数容斥。

于是有:

\[ans=\sum_{m=0}^{n}(-1)^m \binom{n}{m}\sum_{k\ge 0}S_{n-m,k} \]

考虑拆开斯特林数:

\[\begin{align} ans=\sum_{m=0}^{n}(-1)^m \binom{n}{m}\sum_{k\ge 0}S_{n-m,k} &=\sum_{m=0}^{n}(-1)^m \binom{n}{m}\sum_{k=0}^n\sum_{i=0}^k(-1)^{k-i}\frac{i^{n-m}}{i!(k-i)!} \\ &=\sum_{m=0}^{n}(-1)^m \binom{n}{m}\sum_{i=0}^n\sum_{k=i}^n(-1)^{k-i}\frac{i^{n-m}}{i!(k-i)!}\\ &=\sum_{i=0}^n\sum_{m=0}^n\binom{n}{m}(-1)^mi^{n-m}\frac{1}{i!}\sum_{k=0}^{n-i}\frac{(-1)^k}{k!}\\ &=\sum_{i=0}^n\frac{(i-1)^n}{i!}\sum_{k=0}^{n-i}\frac{(-1)^k}{k!} \end{align} \]

后半是个前缀和的形式,可以预处理出来,于是复杂度 \(O(n)\)

TEST_90

从矩阵乘法的形式来理解区间历史和问题,继而可以推广到任意双半群模型,算是个经典 trick 了。

排序后扫描线,转换为历史和问题是平凡的。

设线段树节点维护 \(\{S,PS,L\}\),代表当前区间和,历史和,区间长度,考虑两个 tag 所对应的矩阵。

Xor:

\[\begin{bmatrix} -1 & 0 & 0 \\ 0 & 1 & 0 \\ 1 & 0 & 1 \end{bmatrix} \]

Tim:

\[\begin{bmatrix} 1 & 1 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} \]

直接做就行,但你发现直接矩乘是过不去的。

这个里面说的很好:

矩阵乘法更多是用来理解标记的,而不是用在代码实现上的。

发现矩阵中只有四个值会变化,其余值都是固定的,所以只维护这四个值就可以。

struct M{
	ll d[4];
	ll& operator[](size_t x){return d[x];}
	friend M operator*(M x,M y){
		return	{x[0]*y[1]+y[0],x[1]*y[1],x[2]+x[1]*y[2],x[3]+y[3]+x[0]*y[2]};
	}
};
const M I={0,1,0,0};
M xort(){return {1,-1,0,0};}
M tim(){return {0,1,1,0};}

其中 \(d_0: L\rightarrow S,d_1: S\rightarrow S,d_2: S\rightarrow PS,d_3: L\rightarrow PS\)

然后矩阵乘法常数就很小了。


#include<bits/stdc++.h>
using namespace std;
#define PII pair<int,int>
#define PLI pair<ll,int>
#define PIL pair<int,ll>
#define PLL pair<ll,ll>
#define fi first
#define se second
#define YES() cout<<"YES\n",0
#define NO() cout<<"NO\n",0
#define Yes() cout<<"Yes\n",0
#define No() cout<<"No\n",0
using ll=long long;
using uint=unsigned int;
using ull=unsigned long long;
using lb=long double;
const int N=1e6+5;
int n,d,m,a[N],l[N],lst[N];
ll ans[N];
vector<PII>q[N];
struct M{
	ll d[4];
	ll& operator[](size_t x){return d[x];}
	friend M operator*(M x,M y){
		return	{x[0]*y[1]+y[0],x[1]*y[1],x[2]+x[1]*y[2],x[3]+y[3]+x[0]*y[2]};
	}
};
const M I={0,1,0,0};
M xort(){return {1,-1,0,0};}
M tim(){return {0,1,1,0};}
struct Segment_Tree{
	struct Segment_Tree_node{
		ll S,PS,L;M tag;
		friend Segment_Tree_node operator+(Segment_Tree_node x,Segment_Tree_node y){return {x.S+y.S,x.PS+y.PS,x.L+y.L,I};}
		friend Segment_Tree_node operator*(Segment_Tree_node x,M y){return {x.S*y[1]+x.L*y[0],x.PS+x.S*y[2]+x.L*y[3],x.L,x.tag*y};}
	}t[N<<2];
	inline int ls(int x){return x<<1;}
	inline int rs(int x){return x<<1|1;}
#define mid ((l+r)>>1)
	inline void f(int p,M x){t[p]=t[p]*x;}
	inline void push_up(int p){t[p]=t[ls(p)]+t[rs(p)];}
	inline void push_down(int p){f(ls(p),t[p].tag),f(rs(p),t[p].tag),t[p].tag=I;}
	void build(int p,int l,int r){
		if(l==r)	return t[p]={0,0,1,I},void();
		else{
			build(ls(p),l,mid),build(rs(p),mid+1,r);
			push_up(p);
		}
	}
	void change(int p,int l,int r,int re_l,int re_r,int op){
		if(re_l<=l&&r<=re_r)	return f(p,op?tim():xort());
		else{
			push_down(p);
			if(re_l<=mid)	change(ls(p),l,mid,re_l,re_r,op);
			if(mid<re_r)	change(rs(p),mid+1,r,re_l,re_r,op);
			push_up(p);
		}
	}
	ll query(int p,int l,int r,int re_l,int re_r){
		if(re_l<=l&&r<=re_r)	return t[p].PS;
		else if(!(r<re_l||l>re_r))	return push_down(p),query(ls(p),l,mid,re_l,re_r)+query(rs(p),mid+1,r,re_l,re_r);
		else	return 0;
	}
}T;
int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	cin>>n;
	for(int i=1;i<=n;i++)	cin>>a[i];
	cin>>m;
	for(int i=1,l,r;i<=m;i++)	cin>>l>>r,q[r].push_back({l,i});
	T.build(1,1,n);
	for(int i=1;i<=n;i++){
		T.change(1,1,n,lst[a[i]]+1,i,0);
		T.change(1,1,n,1,i,1);
		for(auto it:q[i])	ans[it.se]=T.query(1,1,n,it.fi,i);
		lst[a[i]]=i;
	}
	for(int i=1;i<=m;i++)	cout<<ans[i]<<"\n";
}

posted @ 2025-10-28 20:37  -MornStar-  阅读(6)  评论(0)    收藏  举报