[提高组集训2021] 模拟赛4

A

题目描述

(n) 个数 (a_i) 分成 (k) 非空集合,若该集合有 (x) 个数能量和为 (y),产生的代价是 (x imes y)

试问每一种方案产生的代价之和,答案对 (998244353) 取模。

(1leq mleq nleq 10^6)

解法

一开始我是把贡献放在单点上,但是要算一整列的斯特林数(我算不来

更好的方法是把组合意义用到底,我们考虑把 (x) 这个系数拆成每对 (u,v) 之间的贡献:

[sum_{i=1}^{n}a_icdot {nrace k}+sum_{u>v}(a_u+a_v){n-1race k} ]

[sum_{i=1}^na_icdot({nrace k}+(n-1){n-1race k}) ]

算单个斯特林数是很容易的,我们把盒子看成不同,容斥空盒的数量,剩下任意放置:

[{nrace k}cdot k!=sum_{i=0}^{k-1}(-1)^icdot{kchoose i}cdot (k-i)^n ]

因为 (x^n) 是完全积性函数,所以可以用欧拉筛 (O(n)) 计算。

总结

本题的贡献是和集合大小有关联的,你可以把它转化成两个单点同时出现在一个集合的贡献。所以算贡献的主体也是要时刻注意的,选定好的主体也许可以让你从源头解决问题。

#include <cstdio>
const int M = 1000005;
const int MOD = 998244353;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,k,sum,ans,fac[M],inv[M];
void init(int n)
{
	fac[0]=inv[0]=inv[1]=1;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
	for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
}
int C(int n,int m)
{
	if(n<m || m<0) return 0;
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int qkpow(int a,int b)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=r*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return r;
}
int S(int n,int m)
{
	int res=0;
	for(int i=0;i<m;i++)
	{
		int f=(i%2)?MOD-1:1;
		res=(res+f*C(m,i)%MOD*qkpow(m-i,n))%MOD;
	}
	return res*inv[m]%MOD;
}
signed main()
{
	freopen("ichigo.in","r",stdin);
	freopen("ichigo.out","w",stdout); 
	n=read();k=read();init(n);
	ans=S(n,k)+(n-1)*S(n-1,k);ans%=MOD;
	for(int i=1;i<=n;i++) sum=(sum+read())%MOD;
	printf("%lld
",ans*sum%MOD);
}

D

题目描述

(n) 个点,每个点可以分配一个非负实数权值 (t),权值和为 (x),有 (m) 条边 ((u,v)),每一条边贡献是 (t_u imes t_v),试分配权值使得贡献最大,答案保留 (6) 位小数。

(nleq 40)

解法

考虑调整法,对于任意一种分配方案和两个不相连的点 (u,v),设与其相连的点权值和分别是 (s_u,s_v),那么如果 (s_uleq s_v),可以把 (t_u) 全部加到 (t_v) 上去,答案不变差,如果 (s_u>s_v) 亦然。

这说明了最优方案中两个不相连的点 (u,v) 中有一个 (t)(0)所以最优方案一定是原图的一个团,应该将点权平均分配,所以如果团的大小是 (k),那么最大贡献是 (x^2cdotfrac{k(k-1)}{2k^2})

显然最大团是最优的,数据范围启示我们可以折半搜索。设 (f[s]) 表示集合 (s) 的导出子图中最大团,我们枚举后 (n/2) 点的团 (t),找到所有点的连边的交集 (s'),取 (|t|+f[s]) 就是全图的团。

总结

实数问题可以用调整法来推结论,还是要注意贡献的主体。

为什么本题会有这样的结论?感性理解就是要让点权集中,边的贡献就大,那么团是最优选择。

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 45;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,x,ans,a[M],f[1<<20];
int pd(int s)
{
	for(int i=0;i<n;i++)
		if((s>>i&1) && (s&a[i])!=s)
			return 0;
	return __builtin_popcountll(s);
}
signed main()
{
	freopen("nanami.in","r",stdin);
	freopen("nanami.out","w",stdout);
	n=read();m=read();x=read();
	for(int i=0;i<n;i++) a[i]|=1ll<<i;
	for(int i=1;i<=m;i++)
	{
		int u=read()-1,v=read()-1;
		a[u]|=1ll<<v;a[v]|=1ll<<u;
	}
	k=n/2;
	for(int s=0;s<(1ll<<k);s++)
	{
		f[s]=pd(s);
		for(int i=0;i<k;i++) if(s>>i&1)
			f[s]=max(f[s],f[s^(1ll<<i)]);
	}
	for(int t=0;t<(1ll<<n-k);t++)
	{
		int s=(1ll<<k)-1;
		for(int j=0;j<n-k;j++) if(t>>j&1)
			s&=a[j+k];
		ans=max(ans,f[s]+pd(t<<k));
	}
	double r=x*x*(ans-1)/(2.0*ans);
	printf("%.6f
",r);
}
原文地址:https://www.cnblogs.com/C202044zxy/p/15481322.html