这场比赛都打的不太好,对我来说问题主要在于各个知识点的熟练程度不够,学过一个知识点刷的题少,而且早期学的知识点也没有进行总结,考场上脑袋里面根本就没有想到。所以说多复习多总结。

「雅礼集训 2017 Day8」共

loj6044

description

问你有多少种本质不同的树满足深度为奇数的点有\(K\)个。

solution

每条树边两端一定一端深度为奇数,另一端为偶数。
这样把点按照深度奇偶分成两类,发现表面上的树其实是个二分图。
因此问题转化为了文艺自动姬
若二分图左边点数\(n\),右边点数\(m\),二分图生成树的个数为\(n^{m-1}*m^{n-1}\)
因为根据prufer序共序列共选\(n+m-2\)个点,因为二分图且最后两条边一定有边相连(两端一定左右不同类),所以左边删过\(n-1\)次,每次右边加入序列都有\(m\)种选法,同理右边删过\(m-1\)次,左边加入序列有\(n\)种选法。
这道题还要考虑点的排布,1号点已经固定了。所以答案为:\(k^{n-k-1}*(n-k)^{k-1}*\tbinom{n-1}{k-1}\)

code:

戳我
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=1e6+5;
ll n,k,mod,jc[N];
ll ksm(ll a,ll b) {ll mul=1;for(;b;b>>=1,a=a*a%mod)if(b&1)mul=mul*a%mod;return mul;}
ll C(ll n,ll m) {return jc[n]*ksm(jc[n-m]*jc[m]%mod,mod-2)%mod;}
int main() {
	scanf("%lld%lld%lld",&n,&k,&mod);
	jc[0]=1;for(int i=1;i<=n;i++) {jc[i]=jc[i-1]*i%mod;}
	printf("%lld\n",ksm(k,n-k-1)*ksm(n-k,k-1)%mod*C(n-1,k-1)%mod);
	return 0;
}

[湖北省队互测week1]独钓寒江雪

luogu P4895

description

求一棵无根树上本质不同独立集的个数

solution

首先预处理各子树的树hash值。
\(dp[i][0/1]\)表示选/不选\(i\)的方案数。
树上dp时,如果两个子树的hash值不同(说明不同构),它们直接相乘即可。
如果两个子树的hash值相同,不过同构会带来重复。
递进一步,如果有\(k\)个同构的子树,每个都可从\(t\)种方案中选一种的组合
这和正常的组合数不同的地方在于,一个方案可以被多次选择。
这个问题的本质是集合(里面\(t\)种元素,每种有无限个)的\(k\)元组合数。(我在《组合数学》上面看到的,也可以用插板法理解)
套公式:答案是\(\tbinom{n+k-1}{k}\)
这样,发现第三个样例输出了15,想了很久,想到老师上课说了要用重心当根,为什么呢?如果有两个重心呢?

于是我抱着疑问打开了题解……(转载于@WeLikeStudying)

发现选不同根可能会同构。(就会出现向上面那样算重)
如果选重心\(u\)为根,那么如果\(v\)\(u\)同构,\(v\)也必将是重心。就不会存在选非重心的点与重心同构的情况。
如果是两个重心(它们之间肯定会有一条边)。
把这条边断掉,分别对两个联通块做dp.合并的时候还是判同构一下(处理跟上面是一样的)。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int N=1e6+5;
const int mod=1e9+7;
int smx[N],rt,rt2,sz[N],nxt[N],to[N],head[N],ecnt,p[N],ptot,n;
ll dp[N][2];
bool is_p[N*10];
ull f[N];
ll ksm(ll a,ll b) {ll mul=1;for(;b;b>>=1,a=a*a%mod)if(b&1)mul=mul*a%mod;return mul;}
ll C(int x,int y) {		//x is big but y is small
	ll res=1,r2=1;
	x%=mod;
	for(int i=0;i<y;i++) {res=res*(x-i)%mod;r2=r2*(i+1)%mod;}
	return res*ksm(r2,mod-2)%mod;
}
void add_edge(int u,int v) {nxt[++ecnt]=head[u];to[ecnt]=v;head[u]=ecnt;}
void _xxs() {
	int up=n*20;
	for(int i=2;ptot<n;i++) {
		if(!is_p[i]) {p[++ptot]=i;}
		for(int j=1,x;j<=ptot&&(x=p[j]*i)<=up;j++) {
			is_p[x]=1;
			if(x%p[j]==0) break;
		}
	}
	random_shuffle(p+1,p+1+ptot);
}
void gt_rt(int u,int fa) {
	sz[u]=1;smx[u]=0;
	for(int i=head[u];i;i=nxt[i]) {
		int v=to[i];if(v==fa)continue;
		gt_rt(v,u);sz[u]+=sz[v];
		smx[u]=max(smx[u],sz[v]);
	}
	smx[u]=max(smx[u],n-sz[u]);
	if(smx[u]<smx[rt]) {rt=u;}
}
void init(int u,int fa) {
	f[u]=1;sz[u]=1;
	for(int i=head[u];i;i=nxt[i]) {
		int v=to[i];if(v==fa)continue;
		init(v,u);
		sz[u]+=sz[v];f[u]+=f[v]*p[sz[v]];
	}
}
struct node {
	int p;ull hs;
	bool operator<(const node &u) const{return hs<u.hs;}
}A[N];
void DP(int u,int fa) {
	dp[u][0]=dp[u][1]=1;
	int atot=0;
	for(int i=head[u];i;i=nxt[i]) {
		int v=to[i];
		if(v!=fa)DP(v,u);
	}
	for(int i=head[u];i;i=nxt[i]) {
		int v=to[i];if(v==fa)continue;
		A[++atot]=(node){v,f[v]};
	}
	sort(A+1,A+1+atot);
	for(int i=1;i<=atot;i++) {
		int x=A[i].p,cnt=1;
		while(i<atot&&A[i+1].hs==A[i].hs) {cnt++;i++;}
//		printf("!%d %d\n",x,cnt);
		if(cnt==1) {
			dp[u][0]=dp[u][0]*(dp[x][0]+dp[x][1])%mod;
			dp[u][1]=dp[u][1]*dp[x][0]%mod;
		}
		else {
			//C(t-1+k,k)
			dp[u][0]=dp[u][0]*C(dp[x][0]+dp[x][1]+cnt-1,cnt)%mod;
			dp[u][1]=dp[u][1]*C(dp[x][0]+cnt-1,cnt)%mod;
		}
	}
//	printf("%d: %lld %lld\n",u,dp[u][0],dp[u][1]);
}
int main() {
	scanf("%d",&n);
	_xxs();
	for(int i=1;i<n;i++) {int u,v;scanf("%d%d",&u,&v);add_edge(u,v),add_edge(v,u);}
	rt=0;smx[0]=1e9;gt_rt(1,0);
//	printf("!%d\n",rt);
	for(int i=head[rt];i;i=nxt[i]) {
		int v=to[i];
		if(smx[v]==smx[rt]) {rt2=v;break;}
	}
	init(rt,rt2);DP(rt,rt2);
	if(!rt2) {printf("%lld\n",((dp[rt][0]+dp[rt][1])%mod+mod)%mod);return 0;}
	init(rt2,rt);DP(rt2,rt);
	if(f[rt]!=f[rt2]) {
		printf("%lld\n",((dp[rt][0]*(dp[rt2][0]+dp[rt2][1])+dp[rt][1]*dp[rt2][0])%mod+mod)%mod);
	}
	else {
//		printf("!(%lld %lld) (%lld %lld)\n",dp[rt][0],dp[rt][1],dp[rt2][0],dp[rt2][1]);
		printf("%lld\n",((dp[rt][0]*dp[rt2][1]+C(dp[rt][0]+1,2))%mod+mod)%mod);
	}
	return 0;
}

[2017 山东一轮集训 Day3]第二题

loj 6066

description

给一棵树,问满足存在同构(考虑子树顺序)的深度为\(k+1\)抠出来的两个子树的最大的\(k\)

solution

正常的同构的定义是:交换子树后会相同,而这里会考虑子树顺序。
不像我卡在这里不会了,有的同学就自创了新的树hash。
当然正常的树hash肯定用不了了。比较冷门的括号序就可以恰当而完美地配合字符串hash解决这个问题。
然后就二分\(k\)。找到每个点下\(k+1\)层的所有点(dfs时\(O(n)\))就可以找到。
每个点求它的子树hash,把那些刚刚找到的不要的部分删掉,相当于hash的合并,也挺好写的,不过还是对hash不熟调了很久。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
typedef unsigned long long ull;
int n,nn,dfn[N],Time,nxt[N],to[N],head[N],ecnt,In[N],Out[N];
vector<int> V[N];
ull Hash[N],seed=13,pw[N],H[N];
int tp,st[N],mxd[N],dep[N];
void add_edge(int u,int v) {nxt[++ecnt]=head[u];to[ecnt]=v;head[u]=ecnt;}
void init(int u) {
	dfn[++Time]=1;In[u]=Time;
//	printf("%d ",u);
	for(int i=head[u];i;i=nxt[i]) {
		int v(to[i]);
		mxd[v]=dep[v]=dep[u]+1;
		init(v);
		mxd[u]=max(mxd[u],mxd[v]);
	}
	dfn[++Time]=2;Out[u]=Time;
}
ull Sum(int l,int r) {return (l>r)?0:Hash[r]-Hash[l-1]*pw[r-l+1];}
void dfs(int u,int k) {
	if(tp>k) V[st[tp-k]].push_back(u);
	st[++tp]=u;
	for(int i=head[u];i;i=nxt[i]) dfs(to[i],k);
	tp--;
}
bool check(int mid) {
//	printf("%d:~~~~~~~ \n",mid);
	dfs(1,mid);
	int htot=0;
	for(int i=1;i<=n;i++) {
		if(mxd[i]<dep[i]+mid) continue;
		++htot;H[htot]=0;
		int len=0,pre=In[i];		//(len)the digit of Hx
		for(int j=0;j<V[i].size();j++) {
			int x=V[i][j];
			H[htot]=H[htot]*pw[In[x]-pre]+Sum(pre,In[x]-1);
			pre=Out[x]+1;
		}
		H[htot]=pw[Out[i]-pre+1]*H[htot]+Sum(pre,Out[i]);
//		printf("[%d,%d] %llu\n",pre,Out[i],H[htot]);
		V[i].clear();
	}
	sort(H+1,H+1+htot);
	for(int i=2;i<=htot;i++)if(H[i-1]==H[i])return 1;
	return 0;
}
void solve() {
	int l=0,r=n,bst;
	while(l<=r) {
		int mid=(l+r)>>1;
		if(check(mid)) {bst=mid;l=mid+1;}
		else r=mid-1;
	}
	printf("%d",bst);
}
void _init() {
	init(1);nn=Time;
	Hash[1]=dfn[1];for(int i=2;i<=nn;i++) Hash[i]=Hash[i-1]*seed+dfn[i];
	pw[0]=1;for(int i=1;i<=nn;i++)pw[i]=pw[i-1]*seed;
}
int main() {
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {
		int x;scanf("%d",&x);
		for(int j=1;j<=x;j++) {int y;scanf("%d",&y);add_edge(i,y);}
	}
	_init();
	solve();
	return 0;
}