Emiya 家今天的饭's 题解

又是一道令人无语的D2T1。。。

考场搞了半天还是只能敲暴力。。。
题目链接
题目太繁琐。。。简单点说就是找满足条件的组合数。

直接进暴力

深搜暴力。。。(因为我正解的转移推不出来)
首先,第一维表示目前枚举到了第几种做法,第二维表示选了几种做法,第三维表示能组成的组合数。
到当前层,你可以枚举每个不为0的来统计,如果发现可以满足条件,累乘上去,搜索下一层。最后再不要这层枚举,再有一个简单的剪枝,如果后面没有足够的做法数,直接return。最后一位枚举完后累加到答案上即可。
主要代码:

void dfs(int cnt,int tmp,int sum){
	if(tmp==ans){
		tot=padd(tot,sum,MOD);
		return ;
	}
	if(n-cnt+1+tmp<ans) return ;
	for(int i=0;++i<=m;)
		if(a[cnt][i]>0){
			++num[i];
			if(num[i]<=ans/2) dfs(cnt+1,tmp+1,pmul(sum,a[cnt][i],MOD));
			--num[i];
		}
	dfs(cnt+1,tmp,sum);
}


for(;++ans<=n;dfs(1,0,1));
一个接近正解的解法

考虑容斥。
考虑枚举不合法的一列。假设我们已经枚举了不合法的列为 c o l col col,接下来会发现我们只关心一个数的位置是否在当前列;如果属于在其他列的情况,那么它具体在哪一列对当前列的合法性并无影响,我们并不需要考虑。
接下来设计状态。 f i , j , k f_{i,j,k} fi,j,k 表示对于 c o l col col 这一列,前 i i i 行在 c o l col col 列中选了 j j j 个,在其他列中选了 k k k 个,那么令 s i s_i si 为第 i i i 行的总和。
那么对于 c o l col col 行则有 f i , j , k = f i − 1 , j , k   +   a i , c o l ∗ f i − 1 , j − 1 , k   +   ( s i − a i , c o l ) ∗ f i − 1 , j , k − 1 f_{i,j,k} = f_{i-1,j,k}\ +\ a_{i,col}* f_{i-1,j-1,k}\ +\ (s_i-a_{i,col})* f_{i-1,j,k-1} fi,j,k=fi1,j,k + ai,colfi1,j1,k + (siai,col)fi1,j,k1
再统计 ∑ j > k f n , j , k \sum_{j>k} f_{n,j,k} j>kfn,j,k 的和并对每一列求和,就求出了所有不合法的方案数。
再考虑方案总数。设 g i , j g_{i,j} gi,j 为前 i i i 行共选了 j j j 个的方案数。
那么就有转移 g i , j = g i − 1 , j + s i ∗ g i − 1 , j − 1 g_{i,j}=g_{i-1,j}+s_i*g_{i-1,j-1} gi,j=gi1,j+sigi1,j1
然后你就得到了84分的高分。
具体代码和正解并一起了。

从上面出发引出接下来的正解

用剪枝减去无用状态。
在不合法情况的计算过程中,也就是 f i , j , k f_{i,j,k} fi,j,k 的转移过程中,我们并不关心 j , k j,k j,k 的具体数值,而只关心相对的大小关系,所以我们可以将状态变为 f i , j f_{i,j} fi,j,表示前 i i i 行,当前列的数比其他列的数多了 j j j 个。
那么就可以这么转移了: f i , j = f i − 1 , j   +   a i , c o l ∗ f i − 1 , j − 1   +   ( s i − a i , c o l ) ∗ f i − 1 , j + 1 f_{i,j} = f_{i-1,j}\ +\ a_{i,col}* f_{i-1,j-1}\ +\ (s_i-a_{i,col})* f_{i-1,j+1} fi,j=fi1,j + ai,colfi1,j1 + (siai,col)fi1,j+1
(代码中可能有些变量不太一样,这只是为了上面好看点所以emmm。。。)
满分code:

#include <bits/stdc++.h>
using namespace std;
const int N=100+5,M=2e3+10,MOD=998244353;
int n,m,a[N][M],b[N][M];
long long f[N][M<<1],g[N][M],sum=0;
void freo(){
	freopen("meal.in","r",stdin);
	freopen("meal.out","w",stdout);
}
int padd(int a,int b){
	return a+b>=MOD?a+b-MOD:a+b;
}
int psub(int a,int b){
	return a-b<0?a-b+MOD:a-b;
}
int pmul(int a,int b){
	return (long long)a*b%MOD;
}
void init(){
	scanf("%d%d",&n,&m);
	for(int i=0;++i<=n;)
		for(int j=0;++j<=m;scanf("%d",&a[i][j]),b[i][0]=padd(b[i][0],a[i][j]));
}
void work(){
	for(int i=0;++i<=n;) for(int j=0;++j<=m;b[i][j]=psub(b[i][0],a[i][j]));
	for(int i=0;++i<=m;){
		memset(f,0,sizeof(f));
		f[0][n]=1;
		for(int j=0;++j<=n;)
			for(int h=n-j-1;++h<=n+j;
				f[j][h]=padd(padd(f[j-1][h],pmul(f[j-1][h-1],a[j][i])),
					pmul(f[j-1][h+1],b[j][i])));
		for(int j=0;++j<=n;sum=padd(sum,f[n][n+j]));
	}
	g[0][0]=1;
	for(int i=0;++i<=n;)
		for(int j=-1;++j<=n;g[i][j]=padd(g[i-1][j],pmul(g[i-1][j-1],b[i][0])));
	for(int i=0;++i<=n;sum=psub(sum,g[n][i]));
}
void prin(){
	printf("%d",pmul((int)sum,MOD-1));
}
int main(){
//	freo();
	init();
	work();
	prin();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值