完美的集合(题解)

Posted zhangjianjunab

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了完美的集合(题解)相关的知识,希望对你有一定的参考价值。

例题

完美的集合

技巧

点数-边数=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;
}

以上是关于完美的集合(题解)的主要内容,如果未能解决你的问题,请参考以下文章

使用片段着色器在特定位置绘制完美的水平线

代码片段 - Golang 实现集合操作

续:纠正:ubuntu7.04可以安装,而且完美的安装 ! for《Oracle-10.2.0.1,打补丁10.2.0.5:在 debian 版本4不含4以上,及 ubuntu 7.04不含(代码片段

laravel特殊功能代码片段集合

在片段着色器中绘制别名像素完美线?

金蝶handler中 collection 代码片段理解