完美的集合(题解)
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;
}
以上是关于完美的集合(题解)的主要内容,如果未能解决你的问题,请参考以下文章
续:纠正:ubuntu7.04可以安装,而且完美的安装 ! for《Oracle-10.2.0.1,打补丁10.2.0.5:在 debian 版本4不含4以上,及 ubuntu 7.04不含(代码片段