NOI 2018 简要题解

本文讨论了在线算法在解决图论问题中的应用,包括无向图的高度和长度限制的路径查询、无权图的最短路径计算、无权连通块维护以及排序优化。介绍了并查集、Kruskal重构树和卡特兰数在解决这些问题中的作用,同时也探讨了如何在限制条件下找到满足特定条件的排列。
摘要由CSDN通过智能技术生成

D1T1

洛谷题目传送门

题目描述

给定一个n个点m条边的无向图,每条边有高度和长度,Q次询问,每次给定起点,以及限制高度,求从起点能通过高度大于限制高度的边到达的点中,到1号点最短路的最小值
强制在线

65pts 不强制在线

把边权和询问的权值都排序,用并查集维护连通块内到1号点距离最小的点

100pts

解法一

在65pts上改进
我们只需要把用并查集合并的过程可持久化,就能访问任何一个版本的并查集
就可以做到在线了
若果使用按秩合并的并查集 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

解法二

建出Kruskal重构树
那么可以到达的点就是一个子树,用树上倍增维护最远能走到那个点
预处理每个点的子树内到1号店距离最近的点即可
时间复杂度都是 O ( n l o g n ) O(nlogn) O(nlogn),且细节比较少,空间消耗小

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5+3;
struct P
{
	int id;
	LL v;
};
bool operator <(const P &x,const P &y)
{
	return x.v>y.v;
}
priority_queue<P> q;
struct node
{
	int y,next,l;
}e[4*N];
struct edge
{
	int x,y,h;
}E[2*N]; 
int link[N],t;
int n,m,Q,K,s;
void Insert(int x,int y,int l)
{
	e[++t].y=y;
	e[t].l=l;
	e[t].next=link[x];
	link[x]=t;
}
bool cmp(edge a,edge b)
{
	return a.h>b.h;
}
int read(){
	int ans=0,op=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') op=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){ans=(ans<<1)+(ans<<3)+ch-'0';ch=getchar();}
	return ans*op;
}
bool vis[N];
LL dis[N];
P Pc(int x,LL v)
{
	P tem;
	tem.id=x;
	tem.v=v;
	return tem; 
} 
void dijistra()
{
	for(int i=1;i<=n;i++)
	{
		vis[i]=0;
		dis[i]=1e15;
	}
	dis[1]=0;
	q.push(Pc(1,0));
	while(!q.empty())
	{
		P tp=q.top();q.pop();
		int x=tp.id;
		if(vis[x]) continue;
		vis[x]=1;
		for(int i=link[x];i;i=e[i].next)
		{
			int y=e[i].y;
//			cout<<x<<' '<<y<<' '<<dis[x]<<' '<<e[i].l<<' '<<dis[y]<<endl;
			if(dis[x]+e[i].l<dis[y])
			{
				dis[y]=dis[x]+e[i].l;
				q.push(Pc(y,dis[y])); 
			}
		}
	}
}	
int cnt,f[2*N],val[2*N],Min[2*N];
int Find(int x)
{
	if(x==f[x]) return x;
	return f[x]=Find(f[x]);
}
int up[2*N][30];
void kruskal()
{
	for(int i=1;i<=n;i++)
	{
		Min[i]=dis[i];	
		f[i]=i;
	}
	sort(E+1,E+m+1,cmp);
	memset(link,0,sizeof(link));
	t=0;
	for(int i=1;i<=m;i++)
	{
		int x=E[i].x;
		int y=E[i].y;
		int fx=Find(x),fy=Find(y);
		if(fx==fy) continue;
		cnt++;
		val[cnt]=E[i].h;
		Min[cnt]=min(Min[fx],Min[fy]);
		f[fx]=cnt;f[cnt]=cnt;f[fy]=cnt;
		up[fx][0]=cnt;
		up[fy][0]=cnt;
		Insert(cnt,fx,0);
		Insert(cnt,fy,0);
	}
}

int main()
{
	freopen("return.in","r",stdin);
	freopen("return.out","w",stdout);
	int T;
	cin>>T;
	while(T--)
	{		
		t=0;

		memset(link,0,sizeof(link));
		memset(vis,0,sizeof(vis));
		memset(Min,89,sizeof(Min));
		memset(up,0,sizeof(up));
		n=read();m=read();		cnt=n;
		for(int i=1;i<=m;i++)
		{
			int x,y,l,h;
			x=read();
			y=read();
			l=read();
			h=read();
			Insert(x,y,l);
			Insert(y,x,l);
			E[i].x=x;
			E[i].y=y;
			E[i].h=h;
		}
		dijistra();
		kruskal();
		for(int k=1;(1<<k)<=cnt;k++) 
			for(int i=1;i<=cnt;i++) 
				up[i][k]=up[up[i][k-1]][k-1];
		Q=read();K=read();s=read();
		LL last=0;
		while(Q--)
		{
			int v0,h0;
			v0=read();
			h0=read();
			int v=(v0+K*last-1)%n+1;
			int h=(h0+K*last)%(s+1); 
			for(int k=22;k>=0;k--)
				if(up[v][k]&&val[up[v][k]]>h) 
					v=up[v][k];
			last=Min[v];
			printf("%lld\n",Min[v]);
		}		
	} 

	return 0;
}

附赠Kruskal重构树三道题
BZOJ3545Peaks
CF1408G
[IOI2018] werewolf 狼人

D1T2

洛谷题目传送门

8pts

n!枚举排列,然后判断即可

44pts

首先考虑什么样的序列是符合条件的
题目提示启发我们,可以考虑每个元素的移动
因为总交换次数是要达到下界,所以每一次交换都必须是有益的
考虑如果排列第i个位置是p[i]
p [ i ] = i p[i]=i p[i]=i,则这位置不能被交换
p [ i ] < i p[i]<i p[i]<i,则这个数需要往左移动,且不能向右移动
那么不能存在一个j满足, j > i , p [ j ] < p [ i ] j>i,p[j]<p[i] j>i,p[j]<p[i],否则这两个位置一定会交换一次,那么 p [ i ] p[i] p[i]就向相反方向移动了,答案一定会变大,即 i i i右边的数都比 p [ i ] p[i] p[i]
p [ i ] > i p[i]>i p[i]>i,类似的可以得到i左边的数都比 p [ i ] p[i] p[i]
且这三个限制和原限制是等价的
直接状压选了那些数字即可
字典序的限制可以类似数位dp去搞
复杂度 O ( 2 n n ) O(2^nn) O(2nn)

#include<bits/stdc++.h>
using namespace std;
const int N = 6e5+7;
typedef long long LL;
const int mod = 998244353;
int n,a[N];
LL f[1<<20][2];
void solve()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	int m=(1<<n);
	memset(f,0,sizeof(f));
	f[0][1]=1;
	for(int s=0;s<m-1;s++)
	{
		int p=0;
		int a1=0,a2=n+1;
		for(int i=1;i<=n;i++)
		if((s>>(i-1))&1)
		{
			p++;
			a1=max(a1,i);
		}
		else a2=min(a2,i);
		p++;
		for(int limit=0;limit<=1;limit++)
		{
			int up=(limit?a[p]:1);
			for(int i=up;i<=n;i++)
			{
				int k=(limit&&(i==up));
				if(!((s>>(i-1))&1))
				{
					if(i>=p)
					{
						if(a1<i)
						f[s+(1<<(i-1))][k]=(f[s+(1<<(i-1))][k]+f[s][limit])%mod;
					}
					else
					{
						if(i<=a2)
						f[s+(1<<(i-1))][k]=(f[s+(1<<(i-1))][k]+f[s][limit])%mod;
					}
				}
			}
		}
	}
	printf("%lld\n",f[m-1][0]);
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		solve();
	}
	return 0;
}

80pts

一个重要的结论:
不考虑字典序限制
题目所求等价于求有多少个排列,满足排列中不存在长度大于等于3的下降子序列

必要性:

即若该排列合法,则不存在长度大于等于3的下降子序列
若不然,则存在三个数 i , j , k i,j,k i,j,k满足, i < j < k , p [ i ] > p [ j ] > p [ k ] i<j<k,p[i]>p[j]>p[k] i<j<k,p[i]>p[j]>p[k]
j < p [ j ] j<p[j] j<p[j],由上文可知,j左边的数都比j小,与 p [ i ] > p [ i ] p[i]>p[i] p[i]>p[i]矛盾
j > p [ j ] j>p[j] j>p[j],则j右边的数都比j大,与 p [ j ] > p [ k ] p[j]>p[k] p[j]>p[k]矛盾
所以矛盾,即原命题成立
充分性

即若不存在 p [ i ] > p [ j ] > p [ k ] p[i]>p[j]>p[k] p[i]>p[j]>p[k],则性质成立
考虑一次交换,交换了 p [ i ] , p [ i + 1 ] p[i],p[i+1] p[i],p[i+1]
则由题可知, p [ i ] > p [ i + 1 ] p[i]>p[i+1] p[i]>p[i+1]
又因为没有长度大于等于3的下降子序列,所以i左边的数都小于 p [ i ] p[i] p[i]
那么就有 p [ 1 … … i − 1 ] p[1……i-1] p[1i1] p [ i + 1 ] p[i+1] p[i+1]总共i个数比 p [ i ] p[i] p[i]小,即 p [ i ] > i p[i]>i p[i]>i
类似的我们可以得到 p [ i + 1 ] < i + 1 p[i+1]<i+1 p[i+1]<i+1,那么这次交换对最终排列而言是合法的
所以命题成立
即题目等价于求有多少的排列,满足排列中不存在长度大于等于3的下降子序列

考虑dp
d p [ i ] [ j ] dp[i][j] dp[i][j]表示有多少个长度为i的排列,满足第一个元素是j,且不存在长度大于等于3的下降子序列,则由定义知 j ≤ i j\leq i ji
考虑第二个数填k
j < = k j<=k j<=k,则加上dp[i-1][k],这里之所以能等于j,而不会重复,是因为虽然整个序列是一个排列,但是每一个子段并不是一个排列,而我们之所以能转移,是因为我们只考虑元素的相对关系,即离散化之后的值,那么长度为i的数列,长度为i-1的数列,离散化之后有值相等是显然可以的
1 < k < j 1<k<j 1<k<j,那么一定会存在一个 j , k , 1 j,k,1 j,k,1的不合法,下降子序列,因此贡献为0
k = = 1 k==1 k==1
贡献显然并不是 d p [ i − 1 ] [ 1 ] dp[i-1][1] dp[i1][1]
首先1~j-1一定是升序排列的,否则就会存在不合法的下降子序列
若不合法,则存在 x < y , j > p [ x ] > p [ y ] > 1 x<y,j>p[x]>p[y]>1 x<y,j>p[x]>p[y]>1
考虑一个映射:
把x移到x+1的位置上,特别的让j-1移到1的位置上
则新序列和原序列是一一对应的,即是一个双双射
且新数列中存在 ( j − 1 , p [ x ] − 1 , p [ y ] − 1 ) (j-1,p[x]-1,p[y]-1) (j1,p[x]1,p[y]1)的下降子序列
所以若填的k等于1,则等价于不存在以j-1开始的长度为3的下降子序列,即dp[i-1][j-1]
综合三种情况
d p [ i ] [ j ] = ∑ k = j − 1 i − 1 d p [ i − 1 ] [ k ] dp[i][j]=\sum_{k=j-1}^{i-1}dp[i-1][k] dp[i][j]=k=j1i1dp[i1][k]

考虑字典序的限制
枚举与给定排列的最长公共前缀i
设前缀最大值为x,满足 p [ j ] < = x , j > = i p[j]<=x,j>=i p[j]<=x,j>=i的j的个数为s
那么离散化之后,x就是s
又因为有字典序的限制,所以填的数字应该>p[i],也就是说,当前位置可以填的最小的数是s+1,最大的是n-i+1,当然是离散化之后的
即答案是
A n s = ∑ i = 1 n − 1 ∑ k = s + 1 n − i + 1 d p [ n − i + 1 ] [ k ] Ans=\sum_{i=1}^{n-1}\sum_{k=s+1}^{n-i+1}dp[n-i+1][k] Ans=i=1n1k=s+1ni+1dp[ni+1][k]
预处理dp值,通过前缀和优化,复杂度 O ( n 2 ) O(n^2) O(n2)
因为代码很简单,所以略

100pts

观察到答案实际上若干dp数组的后缀和
s ( n , m ) = ∑ i = m n d p [ n ] [ i ] s(n,m)=\sum_{i=m}^ndp[n][i] s(n,m)=i=mndp[n][i],其中 m ≤ n m\leq n mn

s ( n , m ) = ∑ i = m + 1 n d p [ n ] [ i ] + d p [ n ] [ m ] s(n,m)=\sum_{i=m+1}^ndp[n][i]+dp[n][m] s(n,m)=i=m+1ndp[n][i]+dp[n][m]
= ∑ i = m + 1 n d p [ n ] [ i ] + ∑ i = m − 1 n − 1 d p [ n − 1 ] [ i ] =\sum_{i=m+1}^ndp[n][i]+\sum_{i=m-1}^{n-1}dp[n-1][i] =i=m+1ndp[n][i]+i=m1n1dp[n1][i]
= s ( n , m + 1 ) + s ( n − 1 , m − 1 ) =s(n,m+1)+s(n-1,m-1) =s(n,m+1)+s(n1,m1)
考虑组合意义,
在平面直角坐标系上从 ( 0 , 0 ) (0,0) (0,0)走到 ( n , m ) (n,m) (n,m)每次向下走一步或者向右上走一步的方案数
并且不能超过 y = x y=x y=x这条线和x轴之下
首先仅通过这两种走法,随便走,不可能越过 y = x y=x y=x,但有可能越过x轴
因为只有第二种会向右走一步,所以一共有n次第二种操作,也可以得到有n-m次第一种操作
为了让这两种操作相似,不妨把想下,改为向右
则原先走到(n,m),新的操作就会走到(2n-m,m),因为多向右走了n-m次
即,每次向右上或者右下走一步,走到(2n-m,m)的方案数,且不能超过x轴
这和原问题也是双射
因为不能超过x轴,所以每个前缀里,向上走的次数要大于等于向下走的次数
这就是卡特兰数的变种问题,方案数为 s ( n , m ) = C 2 n − m n − m − C 2 n − m n − m − 1 s(n,m)=C_{2n-m}^{n-m}-C_{2n-m}^{n-m-1} s(n,m)=C2nmnmC2nmnm1
A n s = ∑ i = 1 n − 1 s ( n − i + 1 , s + 1 ) Ans=\sum_{i=1}^{n-1}s(n-i+1,s+1) Ans=i=1n1s(ni+1,s+1)
预处理组合数,用树状数组求s
复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

#include<bits/stdc++.h>
using namespace std;
const int N = 6e5+7;
typedef long long LL;
const int mod = 998244353;
int c[N];
int n;
void add(int x,int v)
{
	for(int i=x;i<=n;i+=i&-i)
	c[i]+=v;
}
int ask(int x)
{
	int res=0;
	for(int i=x;i;i-=i&-i)
	res+=c[i];
	return res;
}
int a[N];
LL fac[N*2],inv[N*2];
LL M = N*2-2;
LL Pow(LL a,LL b)
{
	LL res=1;
	while(b)
	{
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}
int pre[N],suf[N];
int leq[N];
LL C(int n,int m)
{
	if(n<m) return 0;
	return (LL)fac[n]*inv[m]%mod*inv[n-m]%mod;
}
LL S(int n,int m)
{
	return (C(2*n-m,n-m)-C(2*n-m,n-m-1)+mod)%mod;
}
void solve()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)
	pre[i]=max(pre[i-1],a[i]);
	suf[n+1]=n+1;
	for(int i=n;i>=1;i--)
	suf[i]=min(suf[i+1],a[i]);
	for(int i=n;i>=1;i--)
	{
		add(a[i],1);
		leq[i]=ask(pre[i]);
	} 
	for(int i=1;i<=n;i++)
	add(a[i],-1);
	int Max=0;
	LL ans=0;
	for(int i=1;i<=n;i++)
	{
		if(Max>suf[i]) break;
		if(leq[i]+1<=n-i+1)
		ans=(ans+S(n-i+1,leq[i]+1))%mod;
		if(a[i]<pre[i]) Max=max(Max,a[i]);
	}
	printf("%lld\n",ans);
}
int main()
{
	int T;
	cin>>T;
	fac[0]=1;
	for(int i=1;i<=M;i++)
	fac[i]=(LL)fac[i-1]*i%mod;
	inv[M]=Pow(fac[M],mod-2);
	for(int i=M-1;i>=0;i--)
	inv[i]=(LL)inv[i+1]*(i+1)%mod;
	while(T--)
	{
		solve();
	}
	return 0;
}

D2T1

首先使用muiltiset可以方便的维护出每个龙使用的是哪一把剑
因为题目要求x恰好让血量变为0,多了少了都不行
所以
等价于求方程组
{ c 1 x ≡ a 1 ( m o d    p 1 ) c 2 x ≡ a 2 ( m o d    p 2 ) … … c n x ≡ a n ( m o d    p n ) \left\{ \begin{matrix} c_1x\equiv a_1(mod\;p_1) \\ c_2x\equiv a_2(mod\;p_2) \\ ……\\ c_nx\equiv a_n(mod\;p_n) \end{matrix} \right. c1xa1(modp1)c2xa2(modp2)cnxan(modpn)
的通解x
因为模数不互质,所以选择exCRT求解
但时x有系数,考虑化为不带系数
c i x ≡ a i ( m o d    p i ) c_ix\equiv a_i(mod\;p_i) cixai(modpi)
等价于
c i x + p i y = a i c_ix+p_iy=a_i cix+piy=ai
求出一个特解 x 0 x_0 x0
那么 x x x的通解可化为
X = x 0 + k × p i g c d ( p i , c i ) X=x0+k\times\frac{p_i}{gcd(p_i,c_i)} X=x0+k×gcd(pi,ci)pi
同时模 p i g c d ( p i , c i ) \frac{p_i}{gcd(p_i,c_i)} gcd(pi,ci)pi
即可得到
X ≡ x 0 ( m o d    p i g c d ( p i , c i ) ) X\equiv x0(mod\;\frac{p_i}{gcd(p_i,c_i)}) Xx0(modgcd(pi,ci)pi)
这就化成了一般形式
使用exCRT求解即可

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5+7;
LL gcd(LL a,LL b)
{
	if(!b) return a;
	return gcd(b,a%b);
}
LL lcm(LL a,LL b)
{
	return a/gcd(a,b)*b;
}
LL exgcd(LL a,LL b,LL&x,LL &y)
{
	if(!b)
	{
		x=1;
		y=0;
		return a;
	}
	LL d=exgcd(b,a%b,x,y);
	LL z=x;
	x=y;
	y=z-(a/b)*y;
	return d;
}
LL mul(LL a,LL b,LL mod)
{
	LL res=0;
	while(b)
	{
		if(b&1) res=(res+a)%mod;
		a=(a+a)%mod;
		b>>=1; 
	}
	return res;
}
LL Ans(LL a,LL b,LL m)
{
	LL x,y;
	LL d=exgcd(a,m,x,y);
	if(b%d!=0) return -1;
	LL t=m/d;
	x=mul(x,b/d,t);
	return (x%t+t)%t;
}
LL c[N],a[N],p[N];
LL ceil(LL a,LL b)
{
	if(a%b==0) return a/b;
	return a/b+1;
}
LL search(LL a,LL b,LL c)
{
	if(a>=b) return a;
	return a+1ll*ceil(b-a,c)*c;
}
LL EXCRT(int n,LL Base)
{
	LL x=Ans(c[1],a[1],p[1]),m=p[1]/gcd(p[1],c[1]);
	if(x==-1) return -1;
	for(int i=2;i<=n;i++)
	{
		LL t=Ans(mul(c[i],m,p[i]),((a[i]-mul(c[i],x,p[i]))%p[i]+p[i])%p[i],p[i]);
		if(t==-1) return -1;
		LL nm=lcm(m,p[i]/gcd(c[i],p[i]));
		x=(x%nm+mul(t,m,nm))%nm;
		m=nm;
	}
	return search(x,Base,m);
}
LL w[N];
multiset<LL> sward;
multiset<LL>::iterator it;
void solve()
{
	int n,m;
	scanf("%d %d",&n,&m);
	sward.clear();
	for(int i=1;i<=n;i++)
	scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++)
	scanf("%lld",&p[i]);
	for(int i=1;i<=n;i++)
	scanf("%lld",&w[i]);
	for(int i=1;i<=m;i++)
	{
		LL s;
		scanf("%lld",&s);
		sward.insert(s);
	}
	for(int i=1;i<=n;i++)
	{
		it=sward.begin();
		if(a[i]<(*it)) c[i]=(*it);
		else
		{
			it=sward.upper_bound(a[i]);
			it--;
			c[i]=(*it);
		}
		sward.erase(it);
		sward.insert(w[i]);
	}
	LL Base=0;
	for(int i=1;i<=n;i++)
	{
		Base=max(Base,ceil(a[i],c[i]));	
	}
	for(int i=1;i<=n;i++)
	c[i]%=p[i],a[i]%=p[i];
	printf("%lld\n",EXCRT(n,Base));
}

int main()
{
//	freopen("P477_5.in","r",stdin);
	int T;
	cin>>T;
	while(T--)
	{
		solve();
	}
	return 0;
}

D2T2

先咕了

D2T3

只会50pts

20pts n ≤ 20 n\leq 20 n20

暴力建出图,然后用状压跑哈密顿回路计数

15pts A,K=1

答案只可能是0,1,2,三者之一
跑dfs即可搜出来

30pts K=1

考虑dp
f [ x ] [ 0 / 1 / 2 ] f[x][0/1/2] f[x][0/1/2]
分别表示,以x为根的子树,走完的方案数
1表示从x一直走到最靠左的节点,然后再走完整棵树从最右边出去的方案数
2表示从x一直走到最靠右的节点,然后再走完整棵数从最左边出去的方案数
0表示从从一边叶子进去,走完整棵数从另一端的叶子出去的方案数
转移
设son[1……len]分别是x按照dfs序排列的儿子
f [ x ] [ 1 ] = f [ s o n [ x ] [ 1 ] ] ∗ ∏ i = 2 l e n f [ s o n [ x ] ] [ 0 ] f[x][1]=f[son[x][1]]*\prod_{i=2}^{len}f[son[x]][0] f[x][1]=f[son[x][1]]i=2lenf[son[x]][0]
2,0都是类似的,看代码应该能看懂
总共50pts

#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
typedef long long LL;
const int mod = 998244353;
vector<int> son[N];
struct edge
{
	int y,next;
}e[2*N];
int link[N],t=0;
void add(int x,int y)
{
	e[++t].y=y;
	e[t].next=link[x];
	link[x]=t;
}
LL f[N][4],ans=0;
void dfs(int x,int pre)
{
	for(int i=link[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(y==pre) continue;
		son[x].push_back(y);
	}
	sort(son[x].begin(),son[x].end());
	for(auto y:son[x])
	dfs(y,x);
	int len=(int)son[x].size()-1;
	if(len==-1)
	{
		f[x][0]=1;
		f[x][1]=1;
		f[x][2]=1;
		return;
	}
	LL res=1;
	for(int i=1;i<=len;i++)
	res=res*f[son[x][i]][0]%mod;
	f[x][1]=f[son[x][0]][1]*res%mod;
	res=1;
	for(int i=0;i<len;i++)
	res=res*f[son[x][i]][0]%mod;
	f[x][2]=f[son[x][len]][2]*res%mod;
	if(len>=1)
	{
		for(int i=0;i<len;i++)
		{
			res=1;
			for(int j=0;j<i;j++) res=res*f[son[x][j]][0]%mod;
			for(int j=i+2;j<=len;j++) res=res*f[son[x][j]][0]%mod;
			res=res*f[son[x][i]][2]%mod;
			res=res*f[son[x][i+1]][1]%mod;
			f[x][0]=(f[x][0]+res)%mod; 
		}
	}
	if(x==1&&len>=1)
	{
		ans=1;
		ans=f[son[x][0]][1]*f[son[x][len]][2]%mod;
		for(int i=1;i<len;i++)
		ans=ans*f[son[x][i]][0]%mod; 
	}
}
int n,k;
int seq[N];
int tot=0;
void get(int x,int pre)
{
	for(int i=link[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(y==pre) continue;
		son[x].push_back(y);
	}
	sort(son[x].begin(),son[x].end());
	if((int)son[x].size()==0)
	{
		seq[++tot]=x;
		return;
	} 
	for(auto y:son[x])
	get(y,x);
}
LL F[1<<21][21];
void put(int S)
{
	for(int i=1;i<=n;i++)
	if((S>>(i-1))&1)
	{
		cout<<i<<"--->";
	}
	cout<<"=="<<endl;
}
LL Pow(LL a,LL b)
{
	LL res=1;
	while(b)
	{
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}
int main()
{
	cin>>n>>k;
	for(int i=1;i<n;i++)
	{
		int u;
		scanf("%d",&u);
		add(u,i+1);
		add(i+1,u);
	}
	if(k==1)
	{
		dfs(1,0);
		printf("%lld\n",(ans+f[1][0])%mod);
		return 0;
	}
	get(1,0);
	for(int i=1;i<=tot;i++)
	{
		for(int j=i+1;j<=tot;j++)
		{
			if(min(j-i,tot-(j-i))<=k)
			{
				add(seq[i],seq[j]);
				add(seq[j],seq[i]);
			}
		}
	}
	F[1][1]=1;
	int s=(1<<n);
	for(int S=1;S<s;S++)
	{
		for(int x=1;x<=n;x++)
		{
			if(!((S>>(x-1))&1)) continue;
			if(!F[S][x]) continue;
			for(int i=link[x];i;i=e[i].next)
			{
				int y=e[i].y;
				if(((S>>(y-1))&1)) continue;
				F[S+(1<<(y-1))][y]=(F[S+(1<<(y-1))][y]+F[S][x])%mod;
			}
		}
	}
	LL res=0;
	for(int x=2;x<=n;x++)
	{
		bool f=0;
		for(int i=link[x];i;i=e[i].next)
		{
			int y=e[i].y;
			if(y==1)
			{
				f=1;
				break;
			}
		}
		if(f==1) res=(res+F[s-1][x])%mod;
	}
	cout<<res*Pow(2,mod-2)%mod;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值