zoukankan      html  css  js  c++  java
  • 完美的集合(题解)

    例题

    完美的集合

    技巧

    点数-边数=1

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

    DFS序转移

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

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

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

    题解

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

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

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

    代码

    #include<bits/stdc++.h>
    #define  S  23
    #define  N  70
    #define  NN  140
    #define  LLp  pair<LL,LL>
    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
    ",(ans+mod)%mod);
    	return  0;
    }
    
  • 相关阅读:
    axios中put和patch的区别(都是update , put是需要提交整个对象资源,patch是可以修改局部)
    父子组件传值
    springboot+mybatis 配置sql打印日志
    spring cloud eureka
    springAop
    java线程dump分析工具
    02.java并发编程之原子性操作
    01线程的一些方法
    Spring validator常用注解
    Idea报错Command line is too long
  • 原文地址:https://www.cnblogs.com/zhangjianjunab/p/12288372.html
Copyright © 2011-2022 走看看