【洛谷】P8367 [LNOI2022] 盒

原题链接

题意

数轴上有 \(n\) 个位置,第 \(i\) 个位置有 \(a_i\) 个球,将一个球从 \(i\) 移动到 \(i+1\) 或从 \(i+1\) 移动到 \(i\) 需要花费 \(w_i\) 的代价。

对于所有的结果数量序列 \(b\),满足 \(\sum_{i=1}^{n}a_i=\sum_{i=1}^{n}b_i=S\)。计算 \(a\) 变成 \(b\) 的最小代价之和。

\(1 \leq n \leq 5 \times 10^5,1 \leq S \leq 2 \times 10^6\)

思路:

先考虑给定 \(b\) 序列的情况如何计算答案。

\(Sa\)\(Sb\) 分别表示 \(a,b\) 序列的前缀和。单独计算每条边对于答案的贡献,对于 \(i \sim i+1\) 这条边,在最优方案下通过它的次数为 \(|Sa_i - Sb_i|\),那么最终的答案就是:

\[\sum_{i=1}^{n-1} w_i \times |Sa_i - Sb_i| \]

而题意是要求所有结果序列 \(b\) 的最小代价之和,那么此时就可以枚举 \(Sb_i=j\),由于 \(\sum_{i=1}^{n}b_i=S\)。就有 \(\sum_{k=1}^{i} b_k=j,\sum_{k=i+1}^{n} b_k=S-j\)。题目中要求 \(b_i \geq 0\),那么就等价于求不定方程的非负整数解的个数,利用插板法可以得到答案的表达式:

\[\sum_{i=1}^{n-1} w_i \sum_{j=0}^{S} |Sa_i-j| \binom{j+i-1}{i-1} \binom{S-j+n-i-1}{n-i-1} \]

考虑去掉绝对值,对于 \(j \leq Sa_i\) 的部分,内层循环可以拆成:

\[Sa_i\sum_{j=0}^{Sa_i}\binom{j+i-1}{i-1} \binom{S-j+n-i-1}{n-i-1}-\sum_{j=0}^{Sa_i} j \binom{j+i-1}{i-1} \binom{S-j+n-i-1}{n-i-1} \]

令:

\[f(n,S,i,k)=\sum_{j=0}^{k} \binom{j+i-1}{i-1} \binom{S-j+n-i-1}{n-i-1} \]

\[g(n,S,i,k)=\sum_{j=0}^{k} j\binom{j+i-1}{i-1} \binom{S-j+n-i-1}{n-i-1} \]

那么这一部分的答案转化为:

\[\sum_{i=1}^{n-1} w_i \times (Sa_i \times f(n,S,i,Sa_i)-g(n,S,i,Sa_i)) \]

对于 \(j \geq Sa_i\) 的部分同理,得到最终答案的表达式:

\[\sum_{i=1}^{n-1} w_i \times ((g(n,S,i,S)-g(n,S,i,Sa_i))-Sa_i \times(f(n,S,i,S)-f(n,S,i,Sa_i)))+(Sa_i \times f(n,S,i,Sa_i)-g(n,S,i,Sa_i)) \]

\[=\sum_{i=1}^{n-1} w_i \times (g(n,S,i,S)-Sa_i \times f(n,S,i,S)+2Sa_i\times f(n,S,i,Sa_i)-2 \times g(n,S,i,Sa_i)) \]

考虑 \(f(n,S,i,k)\) 的组合意义:一共有 \(n\) 个盒子,在前 \(i\) 个盒子中放入了 \(\leq k\) 个球,且总的球数为 \(S\),的方案数。

那么也就等价于第 \(k+1\) 个球不在前 \(i\) 个盒子中,考虑枚举第 \(k+1\) 个球所在的盒子,得到:

\[f(n,S,i,k)=\sum_{j=i+1}^{n} \binom{j+k-1}{j-1} \binom{n-j+S-(k+1)}{n-j} \]

利用类似于吸收恒等式的方法,可以进行如下转换:

\[j\binom{i+j-1}{j-1}=i\binom{i+j-1}{i} \]

证明只需要将组合数展开,带入\(g(n,S,i,k)\),可以得到:

\[g(n,S,i,k)=i\sum_{j=0}^{k}\binom{i+j-1}{i} \binom{S-j+n-i-1}{n-i-1} \]

注意到当 \(k=0\) 时没有意义,将 \(j\)\(j-1\) 替换掉,可以得到:

\[g(n,S,i,k)=i\sum_{j=0}^{k-1}\binom{i+j}{i} \binom{S-j+n-i-2}{n-i-1} \]

\[=i \times f(n+1,S-1,i+1,k-1) \]

那么只需要分别维护四个 \(f(n,S,i,k)\),移动 \(k\) 的时候用到 \(f(n,S,i,k)\) 的第一个表达式,移动 \(i\) 个时候用到 \(f(n,S,i,k)\) 的第二个表达式。

由于 \(i\)\(k\) 单调不降,所以总的移动次数为 \(O(n+S)\)

code:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=3e6+10,mod=998244353;
int n,m,fac[N],infac[N],inv[N],a[N],w[N],s[N];
int read()
{
	int res=0,f=1;char c=getchar();
	while(c<'0'||c>'9') ((c=='-')&&(f=-f,0)),c=getchar();
	while(c>='0'&&c<='9') res=(res<<1)+(res<<3)+c-'0',c=getchar();
	return res;
}
void init()
{
	fac[0]=fac[1]=infac[0]=infac[1]=inv[0]=inv[1]=1;
	for(int i=2;i<N;i++) fac[i]=1ll*fac[i-1]*i%mod;
	for(int i=2;i<N;i++) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=2;i<N;i++) infac[i]=1ll*infac[i-1]*inv[i]%mod;
}
int C(int n,int m){if(n<0||m<0||n<m) return 0;return 1ll*fac[n]*infac[m]%mod*infac[n-m]%mod;}
void Add(int &a,int b){a+=b;if(a>=mod) a-=mod;}
void Sub(int &a,int b){a-=b;if(a<0) a+=mod;}
struct binom
{
	int n,S,i,k,res;
	void print(){printf("%d %d %d %d %d\n",n,S,i,k,res);}
	void init(int n_,int S_,int i_,int k_)
	{
		n=n_,S=S_,i=i_,k=k_;res=0;
		for(int j=0;j<=k;j++) Add(res,1ll*C(j+i-1,i-1)*C(S-j+n-i-1,n-i-1)%mod);
	}
	void movei(int new_i)
	{
		for(int j=i+1;j<=new_i;j++) Sub(res,1ll*C(j+k-1,j-1)*C(n-j+S-(k+1),n-j)%mod);i=new_i;
	}
	void movek(int new_k)
	{
		for(int j=k+1;j<=new_k;j++) Add(res,1ll*C(j+i-1,i-1)*C(S-j+n-i-1,n-i-1)%mod);k=new_k;
	}
}f1,f2,f3,f4;
//g(n,S,i,S)-Sa_i*f(n,S,i,S)+2Sa_i*f(n,S,i,Sa_i)-2g(n,S,i,Sa_i)
//f1=f(n+1,S-1,i+1,S-1),f2=f(n,S,i,S),f3=f(n,S,i,Sa_i),f4=f(n+1,S-1,i+1,Sa_i-1)
void solve()
{
	n=read();for(int i=1;i<=n;i++) a[i]=read(),s[i]=s[i-1]+a[i];int S=s[n],ans=0;
//	f3.init(2,5,0,2);printf("%d\n",f3.res);f3.movei(1);printf("%d\n",f3.res);
	f1.init(n+1,S-1,1,S-1);f2.init(n,S,1,S);f3.init(n,S,1,0);f4.init(n+1,S-1,1,-1);
//	f3.init(2,5,0,0);f3.movei(1);f3.print();
	for(int i=1;i<n;i++)
	{
		int res=0;
		f1.movei(i+1);f2.movei(i);f3.movei(i);f4.movei(i+1);
		f3.movek(s[i]);f4.movek(s[i]-1);
//		printf("%d %d %d %d\n",f1.res,f2.res,f3.res,f4.res);
//		printf("%d %d %d %d\n",f3.n,f3.S,f3.i,f3.k);
		Add(res,1ll*i*f1.res%mod);Sub(res,1ll*s[i]*f2.res%mod);
		Add(res,2ll*s[i]*f3.res%mod);Sub(res,2ll*i*f4.res%mod);
		Add(ans,1ll*res*read()%mod);
	}
	printf("%d\n",ans);
}
int main()
{
	init();int T;scanf("%d",&T);while(T--) solve();
	return 0;
}
posted @ 2023-03-10 10:05  曙诚  阅读(61)  评论(0)    收藏  举报