SCOI2019 RGB 和 LOJ2462 完美的集合

RGB

在点数为\(N\)的树上,每个点有各自的颜色(红色、绿色或蓝色),每条边有各自的长度。

你的任务是计算点集对\((U,V)\)的数量,满足:

  1. 集合\(U\)内的点均为红色或绿色,集合\(V\)内的点均为绿色或蓝色;

  2. 集合\(U\)和集合\(V\)都是连通的(若集合内任意两点间的简单路径上的点都属于该集合,则称该集合是连通的);

  3. 存在一个既属于集合\(U\)又属于集合\(V\)的点\(x\),使得对于任意一个属于集合\(U\)或集合\(V\)的点\(y\),满足点\(x\)和点\(y\)的距离不超过\(M\)(两点之间的距离即为它们之间的简单路径上的边的长度之和)。

答案对\(10^9+7\)取模。

对于所有数据点,\(N≤2000\)

题解

https://blog.csdn.net/sslz_fsy/article/details/101315047

考虑只有一个绿点的情况,就是一个裸的树形DP,强制选当前点

\[f_R(u)=\prod (f_R(v)+1)\\ f_B(u)=\prod (f_B(v)+1)\\ ans=f_R(u)f_B(u) \]

我们可以枚举所有绿点算一遍答案,考虑有哪些情况会算重。

如果绿点不相连,很明显不会重,会重的情况只有绿色点聚成一坨的时候。

我们要让一坨的点的贡献只算一次,发现\(点数-边数=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;
}

posted on 2020-04-12 15:42  autoint  阅读(381)  评论(0编辑  收藏  举报

导航