某 SCOI 模拟赛 T1~T3【组合数学 分段打表 01-Trie Boruvka 树哈希 状压DP】

因为题目相比其他几次水一点所以就写一起了。

T1

题意

问有 1 到 n n n n n n 个元素的二叉堆个数,模 1 0 9 + 7 10^9+7 109+7 n ≤ 1 0 9 n\leq 10^9 n109

题解

s z i sz_i szi 为编号为 i i i 的结点的子树的大小,知道 s z i sz_i szi 后我们很容易能够算出一个节点左右子树的大小。

以下有两种推导方式:

  • f i f_i fi 为大小为 i i i 的二叉堆的个数,于是 f 1 = f 2 = 1 f_1=f_2=1 f1=f2=1 f s z x = f s z l s o n ⋅ f s z r s o n ⋅ ( s z x − 1 s z l s o n ) f_{sz_x}=f_{sz_{lson}}\cdot f_{sz_{rson}}\cdot{sz_x-1\choose sz_{lson}} fszx=fszlsonfszrson(szlsonszx1)。再往下推亿点点可以得到答案为 n ! ∏ i = 1 n s z i \dfrac{n!}{\prod\limits_{i=1}^n sz_i} i=1nszin!
  • 先考虑给每个节点随便分配值,方案数为 n ! n! n!;对于每个子树,我们要求只能是这个子树里面 s z i sz_i szi 个元素中最大的 1 个元素放到根,因此要除以 s z i sz_i szi;最终方案数为 n ! ∏ i = 1 n s z i \dfrac{n!}{\prod\limits_{i=1}^n sz_i} i=1nszin!

分子部分的阶乘显然不能现场算,于是分段打表。分母部分可以考虑 g s z x = g s z l s o n ⋅ g s z r s o n ⋅ s z x g_{sz_x}=g_{sz_{lson}}\cdot g_{sz_{rson}}\cdot sz_x gszx=gszlsongszrsonszx,记忆化一下就是 O ( log ⁡ n ) O(\log n) O(logn)

总复杂度为 O ( log ⁡ n + n k ) O(\log n+{n\over k}) O(logn+kn)。其中 k k k 是分段打表的段数。

代码:

#include<bits/stdc++.h>
using namespace std;
int getint(){
	int ans=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		ans=ans*10+c-'0';
		c=getchar();
	}
	return ans*f;
}
const int N=1e6+10,mod=1e9+7;
map<int,int>f;
map<int,int>ff;
int fac[N],ifac[N];
void init_fac(int n){
	fac[0]=1;
	for(int i=1;i<=n;i++)fac[i]=fac[i-1]*1ll*i%mod;
	ifac[0]=ifac[1]=1;
	for(int i=2;i<=n;i++)ifac[i]=ifac[mod%i]*1ll*(mod-mod/i)%mod;
	for(int i=1;i<=n;i++)ifac[i]=ifac[i-1]*1ll*ifac[i]%mod;
}
int getfac(int x){ return fac[x]; }
int getifac(int x){ return ifac[x]; }
int cnt=0;
int solve(int n){
	if(n<=2)return 1;
	map<int,int>::iterator it=f.find(n);
	if(it!=f.end())return it->second;
	++cnt;
	int t=1;
	while(t<<1<=n+1)t<<=1;--t;
	int l=min(t,n-(t>>1)-1),r=n-l-1;
	int c=getfac(l+r)*1ll*getifac(l)%mod*getifac(r)%mod;
	return f[n]=c*1ll*solve(l)%mod*solve(r)%mod;
}

int table[]={1,491101308,723816384,27368307,199888908,927880474,661224977,970055531,195888993,547665832,933245637,
368925948,136026497,135498044,419363534,668123525,30977140,309058615,189239124,940567523,429277690,
358655417,780072518,275105629,99199382,733333339,608823837,141827977,637939935,848924691,724464507,
326159309,903466878,769795511,606241871,957939114,852304035,375297772,624148346,624500515,203191898,
629786193,814362881,116667533,627655552,586445753,193781724,83868974,965785236,377329025,698611116},step=2e7;
int solvee(int n){
	if(n<=1)return 1;
	map<int,int>::iterator it=ff.find(n);
	if(it!=ff.end())return it->second;
	//cerr<<"> "<<n<<endl;
	int t=1;
	while(t<<1<=n+1)t<<=1;--t;
	int l=min(t,n-(t>>1)-1),r=n-l-1;
	return ff[n]=n*1ll*solvee(l)%mod*solvee(r)%mod;
}
int qpow(int x,int y){
	int ans=1;
	while(y){
		if(y&1)ans=ans*1ll*x%mod;
		x=x*1ll*x%mod;
		y>>=1;
	}
	return ans;
}
int main(){
	int n=getint();
	if(n<=1e6){
		init_fac(n);
		cout<<solve(n)<<endl;
		//cerr<<cnt<<endl;
		return 0;
	}
	int t=n/step;
	int ans=table[t];
	//cerr<<t<<" "<<step*t+1<<" "<<n<<endl;
	for(int i=step*t+1;i<=n;i++)ans=ans*1ll*i%mod;
	ans=ans*1ll*qpow(solvee(n),mod-2)%mod;
	cout<<ans<<endl;
	return 0;
}

T2

题意

有一个长为 n n n 的未知序列 a a a,每个位置已知权值 w i w_i wi。你可以花费 ⨁ i = l r w i \bigoplus\limits_{i=l}^r w_i i=lrwi 的代价获得 ⨁ i = l r a i \bigoplus\limits_{i=l}^r a_i i=lrai 的值。问:知道花费多少代价才能知道所有 a i a_i ai 的值。 n ≤ 1 0 5 n\leq 10^5 n105 w i ≤ 1 0 9 w_i\leq 10^9 wi109

题解

s i s_i si a i a_i ai 的前缀异或和,记 t i t_i ti w i w_i wi 的前缀异或和。特别的, s 0 = t 0 = 0 s_0=t_0=0 s0=t0=0

显然得到所有 a i a_i ai 就是得到所有 s i s_i si,而花费 ⨁ i = l r w i \bigoplus\limits_{i=l}^r w_i i=lrwi 的代价获得 ⨁ i = l r a i \bigoplus\limits_{i=l}^r a_i i=lrai 的值就相当于花费 t r ⊕ t l − 1 t_r\oplus t_{l-1} trtl1 的代价获得 s r ⊕ s l − 1 s_r\oplus s_{l-1} srsl1 的值。

我们一开始只知道 s 0 = 0 s_0=0 s0=0

一次查询实际上可以看作在两点之间用给定代价连边;当所有点都与 s 0 s_0 s0 直接或间接连通时,我们就可以推出所有 s i s_i si 的值,也就是要求这个图的最小生成树。

于是这题就等价于 CF888G Xor-MST 了。

代码:

#include<bits/stdc++.h>
using namespace std;
int getint(){
	int ans=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		ans=ans*10+c-'0';
		c=getchar();
	} 
	return ans*f;
}
const int N=1e5+10,L=30,M=N*L;
int ch[M][2],sz[M],cnt=1;
void add(int x){
	int u=1;
	for(int i=L-1;i>=0;--i){
		int t=(x>>i)&1;
		sz[u]++;
		if(!ch[u][t])ch[u][t]=++cnt;
		u=ch[u][t];
	}
	sz[u]++;
}
int query(int x,int val,int l){
	int ans=0;
	for(int i=l;i>=0;--i){
		int t=(val>>i)&1;
		if(ch[x][t])x=ch[x][t];
		else x=ch[x][t^1],ans|=(1<<i);
	}
	return ans;
}
vector<int>v;
long long ans=0;
void solve(int u,int val,int l){
	//cerr<<"solve "<<u<<" "<<val<<" "<<l<<endl;
	if(l<0){
		for(int i=0;i<sz[u];i++)v.push_back(val);
		//cerr<<">> "<<val<<endl;
		return;
	}
	if(!ch[u][0]){
		solve(ch[u][1],val|(1<<l),l-1);
		return;
	}
	if(!ch[u][1]){
		solve(ch[u][0],val,l-1);
		return;
	}
	int c1=ch[u][0],c2=ch[u][1];
	if(sz[c1]>sz[c2])swap(c1,c2);
	solve(c2,val|((c2==ch[u][1])<<l),l-1);
	int qaq=v.size();
	solve(c1,val|((c1==ch[u][1])<<l),l-1);
	int mn=0x7f7f7f7f;
	for(int i=qaq;i<v.size();i++){
		mn=min(mn,(1<<l)|query(c2,v[i],l-1));
	}
	ans+=mn;
	return;
}

int a[N];
int main(){
	int n=getint();
	add(0);
	for(int i=1;i<=n;i++){
		a[i]=getint()^a[i-1];
		add(a[i]);
		//cerr<<"add "<<a[i]<<endl;
	}
	solve(1,0,L-1);
	cout<<ans;
	return 0;
}

T3

题意

有两棵树 A A A B B B,大小分别为 n n n m m m,问有多少 A A A 的连通子图与 B B B 同构,答案模 1 0 9 + 7 10^9+7 109+7 n ≤ 2000 n\leq 2000 n2000 m ≤ 12 m\leq 12 m12

题解

一开始以为 A A A 是一张图然后就没法做了。

首先判断两棵树同构的方式是:选定一个根,对于每个节点排序其所有子树的树哈希,然后再把这些树哈希按正常方式合并起来。

于是枚举 B B B 中的每个点(以之为根时形态相同者不再重复枚举),对于每个有相同形态子树的节点,给它记录一个系数 g [ i ] g[i] g[i] 为各种形态子树的数量的阶乘之积的倒数(相同形态的子树任意交换后都是重复的)。然后在 A A A 上 DP,记录 d p [ i ] [ j ] dp[i][j] dp[i][j] A A A 中以 i i i 为根,与 B B B j j j 的子树同构的子图的个数,转移时枚举子树,状压一下。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int getint(){
	int ans=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		ans=ans*10+c-'0';
		c=getchar();
	}
	return ans*f;
}
const ll mod=1e9+7,G=19;
const int N=2010,M=14;
struct bian{
	int e,n;
};
struct graph{
	bian b[N<<1];
	int s[N],tot=0;
	void add(int x,int y){
		++tot;
		b[tot].e=y;
		b[tot].n=s[x];
		s[x]=tot;
	}
} A,B;
int m,n;

ll qpow(int x,int y){
	int ans=1;
	while(y){
		if(y&1)ans=ans*1ll*x%mod;
		x=x*1ll*x%mod;
		y>>=1;
	}
	return ans;
}
ll pw[N];
int ifac[N];

ll h[N];
int sz[N],g[N];
vector<int>ch[N];
bool cmp(int x,int y){ return h[x]<h[y]; }
void ss1(int x,int f){
	//cerr<<"ss1 "<<x<<" "<<f<<endl;
	ch[x].clear();h[x]=0;sz[x]=1;
	for(int i=B.s[x];i;i=B.b[i].n){
		if(B.b[i].e==f)continue;
		ss1(B.b[i].e,x);
		ch[x].push_back(B.b[i].e);
	}
	sort(ch[x].begin(),ch[x].end(),cmp);
	for(int i=0;i<ch[x].size();i++){
		int v=ch[x][i];
		//cerr<<x<<" -> "<<v<<endl;
		h[x]=h[x]*pw[sz[v]<<1]+h[v];
		sz[x]+=sz[v];
	}
	h[x]=h[x]*pw[sz[x]*2-1]+1;
	//cerr<<"h "<<x<<" "<<h[x]<<"|"<<sz[x]<<endl;
	g[x]=1;int l=0;
	for(int i=0;i<=ch[x].size();i++){
		if(i==ch[x].size()||h[ch[x][i]]!=h[ch[x][l]]){
			g[x]=g[x]*1ll*ifac[i-l]%mod;
			l=i;
		}
	}
}

int dp[N][M];
void ss(int x,int fa){
	for(int i=A.s[x];i;i=A.b[i].n){
		int v=A.b[i].e;
		if(v==fa)continue;
		ss(v,x);
	}
	for(int i=1;i<=m;i++){
		static int ic[M];
		int ct=0;
		for(int j=1;j<=m;j++)ic[j]=-1;
		for(int j=0;j<ch[i].size();j++)ic[ch[i][j]]=ct++;
		if(!ct){
			//cerr<<"!ct "<<i<<endl;
			dp[x][i]=1;continue;
		}
		static int f[2][1<<M];
		int S=1<<ct,t=0;
		for(int j=0;j<S;j++)f[0][j]=0;f[0][0]=1;
		for(int j=A.s[x];j;j=A.b[j].n){
			int v=A.b[j].e;
			if(v==fa)continue;
			for(int k=0;k<S;k++)f[t^1][k]=0;
			for(int k=1;k<=m;k++){
				if(~ic[k]){
					for(int l=(1<<ic[k]);l<S;l=(l+1)|(1<<ic[k])){
						f[t^1][l]+=f[t][l^(1<<ic[k])]*1ll*dp[v][k]%mod;
						if(f[t^1][l]>=mod)f[t^1][l]-=mod;
					}
				}
			}//for k
			t^=1;
			for(int k=0;k<S;k++){
				f[t][k]+=f[t^1][k];
				if(f[t][k]>=mod)f[t][k]-=mod;
			}
		}//for j
		dp[x][i]=g[i]*1ll*f[t][S-1]%mod;
		//cerr<<"dp "<<x<<" "<<i<<" "<<dp[x][i]<<" "<<g[i]<<" "<<f[t][S-1]<<endl;
	}//for i
}

int main(){
	n=getint();
	for(int i=1;i<n;i++){
		int x=getint(),y=getint();
		A.add(x,y); A.add(y,x);
	}
	m=getint();
	for(int i=1;i<m;i++){
		int x=getint(),y=getint();
		B.add(x,y); B.add(y,x);
	}
	
	pw[0]=1;for(int i=1;i<=m*2;i++)pw[i]=pw[i-1]*1ll*G;
	ifac[0]=ifac[1]=1;for(int i=2;i<=m;i++)ifac[i]=(ifac[mod%i]*1ll*(mod-mod/i))%mod;
	for(int i=1;i<=m;i++)ifac[i]=ifac[i-1]*1ll*ifac[i]%mod;
	
	ll ans=0;set<int>se;
	for(int i=1;i<=m;i++){
		ss1(i,0);
		//cerr<<">"<<i<<" "<<h[i]<<endl;
		if(!se.insert(h[i]).second)continue;
		ss(1,0);
		for(int j=1;j<=n;j++){
			ans+=dp[j][i];
			if(ans>=mod)ans-=mod;
		}
	}
	cout<<ans;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值