P5664 [CSP-S2019] Emiya 家今天的饭 题解
题目大意
再每一组里面有很多种方案,每一行只能选一个
每一列不能选超过总数的一半
思路:
我们看题,不符合贪心,因此这是一道dp
首先考虑每一个状态需要指导那些才能转移:
- 目前选了多少个
- 目前每一列选了多少个
- 目前到了那些列
由2得
这时候需要状压
但是
m的数据规模为[1,2000]
因此放弃这样设计
于是 正难则反
处理出所有每行只选一个的方案数
再求出每行只选一个的其中一种食材超过一半的方案数
容斥原理
que:是否会有重合重复 的情况
证:
因为在选择时,不符合的不会有两个都超过了总数的一半的情况;
即
不需要减去的有可能重复,
而要减去的根本不会重复
因此满足
状态设计
我们只需处理出每列超过的就行了
设:
dp[i][j][k]表示目前在第i行,其他的用了j个,这列用了k个
转移
dp[i][j][k]= dp[i][j-1][k]*(s[i]-a[now])
+dp[i][j][k-1]*a[now]
这样时间复杂度为O(mn^3)
有80分
暴力代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<climits>
#include<cmath>
#define ll long long
using namespace std;
const int N=101,M=2001;
const ll P=998244353;
int n,m;
ll a[N][M];
ll s[N];
ll dp[N][N][N];
ll f[N][M];
ll res=0,ans=0;
int main() {
cin>>n>>m;
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
cin>>a[i][j];
s[i]+=a[i][j];
s[i]%=P;
}
}
f[0][0]=1;
for(int i=1;i<=n;i++) {
f[i][0]=1;
for(int j=1;j<=i;j++) {
f[i][j]+=(f[i-1][j-1]*s[i])%P+f[i-1][j];
f[i][j]%=P;
}
}
for(int i=1;i<=n;i++) {
ans+=f[n][i];
ans%=P;
}//所有的
for(int now=1;now<=m;now++) {
memset(dp,0,sizeof(dp));
dp[0][0][0]=1;
for(int i=1;i<=n;i++) {
for(int j=0;j<=i;j++) {
for(int k=0;k+j<=i;k++) {
dp[i][j][k]=dp[i-1][j][k];
if(j!=0) dp[i][j][k]+=(dp[i-1][j-1][k]*(s[i]-a[i][now]))%P;
if(k!=0) dp[i][j][k]+=(dp[i-1][j][k-1]*(a[i][now]))%P;
dp[i][j][k]%=P;
}
}
}
for(int i=0;i<=n;i++){
for(int j=0;j+i<=n&&j<i;j++) {
res+=dp[n][j][i];
res%=P;
}
}
}//减去的
cout<<((ans+P-res)%P);
return 0;
}
优化
注意到在不合法情况的计算过程中,
也就是dp[i,j,k]的转移过程中,
我们实际上并不关心j,k的具体数值,
而只关心相对的大小关系
因此只需要看差就行了
加一个n避免负数
复杂度来到O(n^2 m),过
100pts代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<climits>
#include<cmath>
#define ll long long
using namespace std;
const int N=101,M=2001;
const ll P=998244353;
int n,m;
ll a[N][M];
ll s[N];
ll dp[N][2*N];
ll f[N][M];
ll res=0,ans=0;
int main() {
cin>>n>>m;
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
cin>>a[i][j];
s[i]+=a[i][j];
s[i]%=P;
}
}
f[0][0]=1;
for(int i=1;i<=n;i++) {
f[i][0]=1;
for(int j=1;j<=i;j++) {
f[i][j]+=(f[i-1][j-1]*s[i])%P+f[i-1][j];
f[i][j]%=P;
}
}
for(int i=1;i<=n;i++) {
ans+=f[n][i];
ans%=P;
}
for(int now=1;now<=m;now++) {
memset(dp,0,sizeof(dp));
dp[0][n]=1;
for(int i=1;i<=n;i++) {
for(int j=n-i;j<=n+i;j++) {
dp[i][j]=dp[i-1][j];
if(j!=2*n) dp[i][j]+=(dp[i-1][j+1]*(s[i]-a[i][now]))%P;
if(j!=0) dp[i][j]+=(dp[i-1][j-1]*(a[i][now]))%P;
dp[i][j]%=P;
}
}
for(int j=n+1;j<=2*n;j++) {
res+=dp[n][j];
res%=P;
}
}
cout<<((ans+P-res)%P);
return 0;
}
总结:
-
正难则反
当正向解决某个问题的时候,可以用所有解减去不符合要求的解来达成
-
容斥原理
容斥原理难在于处理交集,要发现本题没有交集
-
优化状态
技巧:当值不影响结果,而值之间的固定操作(如加减乘除)影响结果时,可以用会影响的来弄
-
注意取模运算
本题在5pts卡了很久

浙公网安备 33010602011771号