SCOI2019 RGB 和 LOJ2462 完美的集合
RGB
在点数为\(N\)的树上,每个点有各自的颜色(红色、绿色或蓝色),每条边有各自的长度。
你的任务是计算点集对\((U,V)\)的数量,满足:
-
集合\(U\)内的点均为红色或绿色,集合\(V\)内的点均为绿色或蓝色;
-
集合\(U\)和集合\(V\)都是连通的(若集合内任意两点间的简单路径上的点都属于该集合,则称该集合是连通的);
-
存在一个既属于集合\(U\)又属于集合\(V\)的点\(x\),使得对于任意一个属于集合\(U\)或集合\(V\)的点\(y\),满足点\(x\)和点\(y\)的距离不超过\(M\)(两点之间的距离即为它们之间的简单路径上的边的长度之和)。
答案对\(10^9+7\)取模。
对于所有数据点,\(N≤2000\)。
题解
https://blog.csdn.net/sslz_fsy/article/details/101315047
考虑只有一个绿点的情况,就是一个裸的树形DP,强制选当前点
我们可以枚举所有绿点算一遍答案,考虑有哪些情况会算重。
如果绿点不相连,很明显不会重,会重的情况只有绿色点聚成一坨的时候。
我们要让一坨的点的贡献只算一次,发现\(点数-边数=1\)
于是可以枚举每个绿点,在减去每条边的贡献就可以让一个连通块只算一次了。
时间复杂度\(O(N^2)\)。
CO int N=2e3+10;
int W;
char col[N];
struct edge {int u,v,w;} E[N];
vector<edge> to[N];
int fR[N],fB[N],ans;
void dfs(int u,int fa,int dis){
if(dis>W) {fR[u]=fB[u]=0; return;}
if(col[u]!='R') fB[u]=1;
if(col[u]!='B') fR[u]=1;
for(int i=0;i<(int)to[u].size();++i){
int v=to[u][i].v;
if(v==fa) continue;
dfs(v,u,dis+to[u][i].w);
fB[u]=mul(fB[u],fB[v]+1);
fR[u]=mul(fR[u],fR[v]+1);
}
}
IN void insert(int u){
dfs(u,0,0);
ans=add(ans,mul(fR[u],fB[u]));
}
IN void erase(int u,int v,int w){
dfs(u,v,w),dfs(v,u,w);
ans=add(ans,mod-mul(mul(fR[u],fB[u]),mul(fR[v],fB[v])));
}
int main(){
freopen("RGB.in","r",stdin),freopen("RGB.out","w",stdout);
int n=read<int>();read(W);
scanf("%s",col+1);
for(int i=1;i<n;++i){
int u=read<int>(),v=read<int>(),w=read<int>();
E[i]=(edge){u,v,w};
to[u].push_back((edge){u,v,w}),to[v].push_back((edge){v,u,w});
}
for(int i=1;i<=n;++i)if(col[i]=='G') insert(i);
for(int i=1;i<n;++i)if(col[E[i].u]=='G' and col[E[i].v]=='G') erase(E[i].u,E[i].v,E[i].w);
printf("%d\n",ans);
return 0;
}
完美的集合
小A有一棵\(N\)个点的带边权的树,树的每个节点有重量\(w_i\)和价值\(v_i\)。
现在小A要从中选出若干个节点形成一个集合\(S\),满足这些节点重量之和\(\leq M\)并且构成一个连通块。小A是一个完美主义者,因此他只会选择节点价值之和最大的那些\(S\)。我们称这样的集合\(S\)为完美的集合。
现在小\(A\)要从所有完美的集合中选出\(K\)个,并对这\(K\)个完美的集合分别进行测试。在这\(K\)次测试开始前,小A首先需要一个点\(x\)来放置他的测试装置,这个测试装置的最大功率为\(\text{Max}\)。
接下来的每次测试,小A会对测试对象\(S\)中的所有点进行一次能量传输,对一个点\(y\)进行能量传输需要的功率为\(\text{dist}(x,y)\times v_y\),其中\(\text{dist}(x,y)\)表示点\(x,y\)在树上的最短路长度。因此,如果\(S\)中存在一个点\(y\),满足\(\text{dist}(x,y)\times v_y>\text{Max}\),测试就会失败。同时,为了保证能量传输的稳定性,测试装置所在的点\(x\)需要在集合\(S\)中,否则测试也会失败。
现在小A想知道,有多少种从所有完美的集合选出\(K\)个的方法,使得他能找到一个放置测试装置的点,来完成他的测试呢?
你只需要输出方案数对\(11920928955078125\)取模的结果。
对于\(100\%\)的数据,\(N\leq 60,M\leq 10000,C_i\leq 10000,K,w_i,v_i\leq 10^9,\text{Max}\leq 10^{18}\)。
题解
http://jklover.hs-blog.cf/2020/04/10/Loj-2462-完美的集合/
此题可以看成一道二合一:树上连通块的DP技巧 + 奇怪的组合数取模。
// Output
CO int mod=11920928955078125;
IN int add(int a,int b){
return (a+=b)>=mod?a-mod:a;
}
IN int mul(int a,int b){
int64 ans=a*b-(int)((float128)a/mod*b+1e-8)*mod;
return ans<0?ans+mod:ans%mod;
}
IN int fpow(int a,int b){
int ans=1;
for(;b;b>>=1,a=mul(a,a))
if(b&1) ans=mul(ans,a);
return ans;
}
IN int inv(int a){
return fpow(a,mod/5*4-1); // phi-1
}
typedef array<int,23> poly;
poly operator*(CO poly&a,CO poly&b){
poly c={};
for(int j=0;j<23;++j)if(b[j]) // more 0s in b
for(int i=0;i+j<23;++i)if(a[i])
c[i+j]=add(c[i+j],mul(a[i],b[j]));
return c;
}
pair<int,int> operator+(CO pair<int,int>&a,CO pair<int,int>&b){
return {mul(a.first,b.first),a.second+b.second};
}
pair<int,int> operator-(CO pair<int,int>&a,CO pair<int,int>&b){
return {mul(a.first,inv(b.first)),a.second-b.second};
}
poly P[10000];
int C[23][23];
void init(){
C[0][0]=1;
for(int i=1;i<23;++i){
C[i][0]=C[i][i]=1;
for(int j=1;j<i;++j) C[i][j]=add(C[i-1][j],C[i-1][j-1]);
}
P[0]={1};
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];
}
}
poly trans(CO poly&a,int k){
static int power[23];
power[0]=1;
for(int i=1;i<23;++i) power[i]=mul(power[i-1],k);
poly b={};
for(int i=0;i<23;++i)for(int j=0;j<=i;++j)
b[j]=add(b[j],mul(a[i],mul(power[i-j],C[i][j])));
return b;
}
poly fac_poly(int n){
if(n<10000) return P[n];
int k=n/10*10;
poly ans=fac_poly(k/2);
ans=ans*trans(ans,k/2);
for(int i=k+1;i<=n;++i)if(i%5!=0)
ans=ans*(poly){i%mod,1};
return ans;
}
pair<int,int> fac_pair(int n){
pair<int,int> ans={fac_poly(n)[0],n/5};
if(n>=5) ans=ans+fac_pair(n/5);
return ans;
}
int binom(int n,int k){
if(n<k) return 0;
pair<int,int> ans=fac_pair(n)-fac_pair(k)-fac_pair(n-k);
if(ans.second>=23) return 0;
return mul(ans.first,fpow(5,ans.second));
}
// Tree DP
CO int N=70,M=1e4+10,inf=1e9;
int n,m,K,Max;
int w[N],val[N],dist[N][N];
vector<int> to[N];
int valid[N],rnk[N],siz[N],fa[N],_fa[N],idx;
void dfs(int u){
siz[u]=1,rnk[++idx]=u;
for(int v:to[u])if(v!=fa[u] and valid[v])
fa[v]=u,dfs(v),siz[u]+=siz[v];
}
int f[N][M],g[N][M],mx;
pair<int,int> calc(int x,int y){
idx=0,fa[x]=0,dfs(x);
for(int i=0;i<=m;++i) f[idx+1][i]=0,g[idx+1][i]=1;
for(int i=idx;i>=1;--i){
int u=rnk[i];
for(int j=0;j<=m;++j){
int v1=j>=w[u]?f[i+1][j-w[u]]+val[u]:0;
int v2=f[i+siz[u]][j];
if(u==y and j<w[u]) // can't choose y
f[i][j]=g[i][j]=0;
else if(u==y or (j>=w[u] and v1>v2))
f[i][j]=v1,g[i][j]=g[i+1][j-w[u]];
else if(j<w[u] or v1<v2)
f[i][j]=v2,g[i][j]=g[i+siz[u]][j];
else f[i][j]=v1,g[i][j]=g[i+1][j-w[u]]+g[i+siz[u]][j];
}
}
return {f[1][m],g[1][m]};
}
int solve(int x,int y){
for(int i=1;i<=n;++i)
valid[i]=dist[x][i]*val[i]<=Max and (!y or dist[y][i]*val[i]<=Max);
if(!valid[x] or (y and !valid[y])) return 0;
pair<int,int> ans=calc(x,y);
if(ans.first<mx) return 0;
return binom(ans.second,K);
}
signed main(){
init();
read(n),read(m),read(K),read(Max);
for(int i=1;i<=n;++i) read(w[i]);
for(int i=1;i<=n;++i) read(val[i]);
for(int i=1;i<=n;++i)for(int j=1;j<=n;++j) dist[i][j]=i==j?0:inf;
for(int i=1;i<n;++i){
int u=read<int>(),v=read<int>(),l=read<int>();
to[u].push_back(v),to[v].push_back(u);
dist[u][v]=dist[v][u]=l;
}
for(int k=1;k<=n;++k)
for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)
dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
fill(valid+1,valid+n+1,1);
for(int i=1;i<=n;++i) mx=max(mx,calc(i,0).first);
copy(fa+1,fa+n+1,_fa+1); // fa will change when solve calls calc
int ans=0;
for(int i=1;i<=n;++i)
ans=add(ans,add(solve(i,0),mod-(_fa[i]?solve(i,_fa[i]):0)));
printf("%lld\n",ans);
return 0;
}