完美的集合(题解)

文章目录

  • 例题
  • 技巧
    • 点数-边数=1
    • DFS序转移
  • 题解
  • 代码

例题

完美的集合

技巧

点数-边数=1

现在如果你要在树上统计某种特定集合的数量,有一种比较简单的方法就是统计包括某个点的集合数量,加起来,减去包括某条边的集合,就可以得出最后的集合了。

DFS序转移

如果要求你在树上背包你打算怎么办,神犇就给出了一个很好的方法。

一个集合利用合并从下到上做背包是 O ( n 3 ) O(n^3) O(n3),但是遍历一个个点加入进来就是 O ( n 2 ) O(n^2) O(n2)了,但是关键是如何遍历。

先把DFS序(进入的顺序)做出来,然后倒着DFS队列DP,如果一个点 x x x选择自己的话,他就选择 + 1 +1 +1的位置,如果他不选择自己的话,那么他就直接继承 + s i z e [ x ] +size[x] +size[x]的位置,就可以了。

题解

学会了技巧,这道题目就简单多了,模数是 5 23 5^{23} 523,首先利用DFS序DP得到最大的价值,同时得到包括某个点的最大价值的集合数,用组合数取模(之前的同余系列)得到包括这个点的完美集合,最后减去包括某条边的完美集合即可。(当然在DFS序DP之前要先处理那些点能否到达,这样那个重量<=的条件好处理)

另外,最大价值的集合数最大大概为 2 60 2^{60} 260

时间复杂度: O ( n 2 m + 取 模 复 杂 度 ) O(n^2m+取模复杂度) O(n2m+)

代码

#include
#define  S  23
#define  N  70
#define  NN  140
#define  LLp  pair
using  namespace  std;
typedef  long  long  LL;
template  <class  T>
inline  T  mymax(T  x,T  y){return  x>y?x:y;}
template  <class  T>
inline  T  mymin(T  x,T  y){return  x<y?x:y;}
LL  mod=(LL)11920928955078125;
inline  LL  mul(LL  x,LL  y){return  (x*y-(LL)((long  double)x*y/mod+1e-10)*mod);}
inline  LL  pow(LL  x,LL  k)//求逆元专用 
{
	LL  ans=1;
	while(k)
	{
		if(k&1)ans=mul(x,ans);
		x=mul(x,x);k>>=1;
	}
	return  ans;
}
namespace  Big_num//大整数组合数 
{
	LL  pw[S]={1},C[S][S],K/*表示选出几个集合*/;
	struct  poly//又是你!!! 
	{
		LL  a[S];
		poly(LL  x=0,LL  y=0){memset(a,0,sizeof(a));a[0]=x;a[1]=y;}
		void  init(LL  k)
		{
			static  LL  ret[S];memset(ret,0,sizeof(ret)); 
			for(int  i=1;i<S;i++)pw[i]=mul(pw[i-1],k);
			for(int  i=0;i<S;i++)
			{
				for(int  j=0;j<=i;j++)ret[j]=(ret[j]+mul(a[i],mul(pw[i-j],C[i][j/*从不选的里面挑出几个*/])))%mod;
			}
			memcpy(a,ret,sizeof(ret));
		}
		poly  operator*(poly  x)
		{
			poly  z;
			for(int  i=0;i<S;i++)
			{
				if(x.a[i])
				{
					for(int  k=i;k<S;k++)z.a[k]=(z.a[k]+mul(x.a[i],a[k-i]))%mod;
				}
			}
			return  z;
		}
	}P[10005];
	//-----------poly
	poly  facpoly(LL  n)//求阶乘 
	{
		if(n<=10000)return  P[n];
		LL  k=n/10*10;
		poly  t1=facpoly(k>>1),t2=t1;
		t2.init(k>>1);
		t1=t1*t2;
		for(LL  i=k+1;i<=n;i++)
		{
			if(i%5!=0)t1=t1*poly(i,1);
		}
		return  t1;
	}
	LLp  solve(LL  n)//表示的是求阶乘并返回5的阶乘 
	{
		LLp  ret=make_pair(facpoly(n).a[0],n/5);
		if(n>=5)
		{
			LLp  tmp=solve(n/5);
			ret.first=mul(ret.first,tmp.first);
			ret.second+=tmp.second;
		}
		return  ret;
	}
	LL  Combk(LL  n)//求组合数 
	{
		if(n<K)return  0;
		LLp  f1=solve(n),f2=solve(K),f3=solve(n-K);
		f1.second-=f2.second+f3.second;
		return  mul(mul(f1.first,pow(mul(f2.first,f3.first),mod/5*4-1)),pow(5,f1.second));
	}
	void  Init()//预处理某些组合数,细节处理的好其实不用的 
	{
		C[0][0]=1;
		for(int  i=1;i<S;i++)
		{
			C[i][0]=1;
			for(int  j=1;j<=i;j++)
			{
				C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
			}
		}
		P[0]=poly(1,0);
		for(int  i=1;i<=10000;i++)
		{
			if(i%5!=0)P[i]=P[i-1]*poly(i,1);
			else  P[i]=P[i-1];
		}
	}
};
struct  node
{
	int  y,next,c;
}a[NN];LL  len,last[N];
inline  void  ins(int  x,int  y,int  c){a[++len].y=y;a[len].c=c;a[len].next=last[x];last[x]=len;}
int  dis[N][N],fa[N];bool  can[N]/*能否到达*/;
void  Dfs(int  x,int  f,int  gen,int  di)
{
	dis[gen][x]=di;fa[x]=f;
	for(int  k=last[x];k;k=a[k].next)
	{
		int  y=a[k].y;
		if(y!=fa[x])Dfs(y,x,gen,di+a[k].c);
	}
}
LL  up,f[N][10007],g[N][10007];int  dfn[N]/*dfs序*/,cnt,siz[N];
void  dfs(int  x,int  fa)
{
	dfn[++cnt]=x;siz[x]=1;
	for(int  k=last[x];k;k=a[k].next)
	{
		int  y=a[k].y;
		if(can[y]  &&  y!=fa)dfs(y,x),siz[x]+=siz[y];
	}
}
int  n,m;LL  mmax;
LL  w[N],v[N];
LLp  dp(int  x,int  y/*必选点*/)//DP的过程
{
	cnt=0;
	dfs(x,0);//处理DFS序
	for(int  i=0;i<=m;i++)f[cnt+1][i]=0,g[cnt+1][i]=1;
	for(int  i=cnt;i>=1;i--)
	{
		int  u=dfn[i];
		for(int  j=0;j<=m;j++)
		{
			if(u==y  &&  j<w[u])f[i][j]=g[i][j]=0;//必选点没有方案
			else  if(u==y  ||  (j>=w[u]  &&  f[i+1][j-w[u]]+v[u]>f[i+siz[u]][j]))//选自己 
			{
				f[i][j]=f[i+1][j-w[u]]+v[u];
				g[i][j]=g[i+1][j-w[u]];
			}
			else  if(j<w[u]  ||  f[i+1][j-w[u]]+v[u]<f[i+siz[u]][j])//不选自己 
			{
				f[i][j]=f[i+siz[u]][j];
				g[i][j]=g[i+siz[u]][j];
			}
			else//都一样 
			{
				f[i][j]=f[i+1][j-w[u]]+v[u];
				g[i][j]=g[i+1][j-w[u]]+g[i+siz[u]][j];
			}
		} 
	}
	return  make_pair(f[1][m],g[1][m]);
}
LL  get_ans(int  x,int  y)//得到包括x,y的答案 
{
	for(int  i=1;i<=n;i++)
	{
		if((LL)dis[x][i]*v[i]<=mmax  &&  (LL)dis[y][i]*v[i]<=mmax)can[i]=1;
		else  can[i]=0;
	}
	if(!can[x]  ||  (y  &&  !can[y]))return  0;//特判 
	LLp  ret=dp(x,y);
	if(ret.first==up)return  Big_num::Combk(ret.second);
	return  0;
}

int  main()
{
//	freopen("std.in","r",stdin);	
//	freopen("vio.out","w",stdout);
	scanf("%d%d%lld%lld",&n,&m,&Big_num::K,&mmax);
	memset(can,1,sizeof(can));
	Big_num::Init();//初始化
	for(int  i=1;i<=n;i++)scanf("%lld",&w[i]);
	for(int  i=1;i<=n;i++)scanf("%lld",&v[i]);
	for(int  i=1;i<n;i++)
	{
		int  x,y,c;scanf("%d%d%d",&x,&y,&c);
		ins(x,y,c);ins(y,x,c);
	}
	for(int  i=1;i<=n;i++)up=mymax(up,dp(i,0).first);
	for(int  i=n;i>=1;i--)Dfs(i,0,i,0);//处理出dis 
	LL  ans=0;
	for(int  i=1;i<=n;i++)
	{
		ans=(ans+get_ans(i,0)-(fa[i]?get_ans(i,fa[i]):0))%mod;//点数-边数,其实就是容斥 
	}
	printf("%lld\n",(ans+mod)%mod);
	return  0;
}

你可能感兴趣的:(技巧,题解)