SCOI2019 RGB 和 LOJ2462 完美的集合
RGB
在点数为N的树上,每个点有各自的颜色(红色、绿色或蓝色),每条边有各自的长度。
你的任务是计算点集对(U,V)的数量,满足:
-
集合U内的点均为红色或绿色,集合V内的点均为绿色或蓝色;
-
集合U和集合V都是连通的(若集合内任意两点间的简单路径上的点都属于该集合,则称该集合是连通的);
-
存在一个既属于集合U又属于集合V的点x,使得对于任意一个属于集合U或集合V的点y,满足点x和点y的距离不超过M(两点之间的距离即为它们之间的简单路径上的边的长度之和)。
答案对109+7取模。
对于所有数据点,N≤2000。
题解
https://blog.csdn.net/sslz_fsy/article/details/101315047
考虑只有一个绿点的情况,就是一个裸的树形DP,强制选当前点
我们可以枚举所有绿点算一遍答案,考虑有哪些情况会算重。
如果绿点不相连,很明显不会重,会重的情况只有绿色点聚成一坨的时候。
我们要让一坨的点的贡献只算一次,发现点数−边数=1
于是可以枚举每个绿点,在减去每条边的贡献就可以让一个连通块只算一次了。
时间复杂度O(N2)。
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个点的带边权的树,树的每个节点有重量wi和价值vi。
现在小A要从中选出若干个节点形成一个集合S,满足这些节点重量之和≤M并且构成一个连通块。小A是一个完美主义者,因此他只会选择节点价值之和最大的那些S。我们称这样的集合S为完美的集合。
现在小A要从所有完美的集合中选出K个,并对这K个完美的集合分别进行测试。在这K次测试开始前,小A首先需要一个点x来放置他的测试装置,这个测试装置的最大功率为Max。
接下来的每次测试,小A会对测试对象S中的所有点进行一次能量传输,对一个点y进行能量传输需要的功率为dist(x,y)×vy,其中dist(x,y)表示点x,y在树上的最短路长度。因此,如果S中存在一个点y,满足dist(x,y)×vy>Max,测试就会失败。同时,为了保证能量传输的稳定性,测试装置所在的点x需要在集合S中,否则测试也会失败。
现在小A想知道,有多少种从所有完美的集合选出K个的方法,使得他能找到一个放置测试装置的点,来完成他的测试呢?
你只需要输出方案数对11920928955078125取模的结果。
对于100%的数据,N≤60,M≤10000,Ci≤10000,K,wi,vi≤109,Max≤1018。
题解
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; }
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
· ThreeJs-16智慧城市项目(重磅以及未来发展ai)
· .NET 原生驾驭 AI 新基建实战系列(一):向量数据库的应用与畅想
· Ai满嘴顺口溜,想考研?浪费我几个小时
· Browser-use 详细介绍&使用文档
· 软件产品开发中常见的10个问题及处理方法