[Noip2019]Emiya 家今天的饭

[Noip2019]Emiya 家今天的饭

一.前言

​ 月球人何苦为难月球人……题目链接

​ 还有 long long,memset 真奇妙~

二.思路

​ 这个题吧,我也是对着题解琢磨了很久才略懂?

​ 首先把题目化简为一下几个条件:

  • 有一个 n 行 m 列的矩阵
  • 每一行只能选一个数
  • 每一列选的数的个数不能超过(lfloor frac{n}{2} floor)
  • 求这些数的乘积
  • 至少要选一个数

其实其他条件都还好,只是第三项很麻烦。这里运用容斥的思想,首先求出所有的可能性,然后减去只不满足第三项的可能情况数。

​ 那么所有的可能性很好求(我们记第 i 行的数的和为 sum[i] )那么所有的可能性为(prod_{i=1}^n(sum[i]-1)~~~~-1), 那是最后减的一个1啊!不要看错了,是因为必须要选才减的。

​ 那么我们来求后面的那个奇怪东西。由于总 n 个数,那么不满足第三项的的列肯定只有一个。(显然)我们试图枚举这个列(设这个列为 p)。那么列确定下来,设 (f[i][j][k]) 为在前 i 行的选择中,在 p 上选了 j 个,在除 p 以外的列中选了 k 个。于是很容易得到转移方程

[f[i][j][k]=f[i-1][j][k-1]*(sum[i]-a[i][p])+f[i-1][j-1][k]*a[i][p] ]

加的两项分别对应在第 i 行是选在 p 上还是其他列上。

​ 但是很遗憾的,这样的做法时间复杂度太高了。于是我们需要魔改。实际上,引用另一篇题解的话,我们其实并不在意 j,k 的值是多少,我们只需要知道 j,k 的差是多少就可以进行计算。先放转移(此处的j已经变为差了,k则为 p

[f[i][j]=f[i-1][j]+f[i-1][j-1]*a[i][k]+f[i-1][j+1]*(sum[i]-a[i][k]) ]

这样就降下来了,然后枚举j的时候因为可能会有负数,所以整体偏移n就好,一些小细节看代码吧。

三.CODE

#define m_for(i,a,b) for(int i=a;i<=b;++i)
#define mod 998244353
int read(){
	........
}
int n,m,sum[105],a[105][2005],f[105][4005],ans=1;
int main(){
	n=read();m=read();
	m_for(i,1,n){
		m_for(j,1,m)sum[i]=(1LL*sum[i]+(a[i][j]=read()))%mod;//每一行的和
		ans=(1LL*ans*(sum[i]+1))%mod;//总方案数
	}
	ans--;
	m_for(k,1,m){
		memset(f,0,sizeof(f));//每次清零外加初始化
		f[0][n]=1;//实际上减去偏移的n是 f[0][0]=0
		m_for(i,1,n)m_for(j,n-i,n+i)//n-i 和 n+i 都是偏移
		f[i][j]=((f[i-1][j]+1LL*f[i-1][j-1]*a[i][k]%mod)%mod+1LL*f[i-1][j+1]*(sum[i]-a[i][k])%mod)%mod;
		m_for(j,1,n)ans=(1LL*ans-f[n][n+j]+mod)%mod;//比其他的和都多就已经有一半了
	}
	printf("%lld
",(ans%mod+mod)%mod);
	return 0;
}
原文地址:https://www.cnblogs.com/clockwhite/p/13423283.html