隔了(鸽了)半年多,重新回来做这道题,还是感觉很难… …果然蒟蒻还是蒟蒻… …
https://www.luogu.com.cn/problem/P5664
题意:给一个 n ∗ m n*m n∗m的矩阵,限制每行最多只能取一个点,每列所取的点数不能超过总点数的 1 2 \frac{1}{2} 21 ,取的总点数不能为0,求总方案数。
32 p t s 32pts 32pts
n
≤
10
,
m
≤
3
n\le10,m\le3
n≤10,m≤3 。
暴力枚举。
复杂度
O
(
4
n
)
O(4^n)
O(4n) 。(3种菜+不选)
//省掉了头文件、变量定义和读优,最后AC代码里有
void dfs(int k,long long sum,int x,int y)
//k是当前枚举到的行,sum是目前统计的总个数
//x是共要选的行数(要选菜的个数),y是已经选了的行数
{
if(x==y) {ans=(ans+sum)%mod;return;}
if(k==n+1) return;
for(int i=1;i<=m;i++)
{
if(q[i]+1<=x/2)
{
q[i]++;
dfs(k+1,sum*a[k][i]%mod,x,y+1);
q[i]--;
}
}
dfs(k+1,sum,x,y);
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) a[i][j]=read();
for(int i=2;i<=n;i++) dfs(1,1,i,0); //从2开始枚举,因为只做一道菜绝对不合法
printf("%lld",ans);
return 0;
}
64 p t s 64pts 64pts
n
≤
40
,
m
≤
3
n\le40,m\le3
n≤40,m≤3 。
虽然暴力枚举不行了,但我们可以dp(一道dp好题怎能不用dp??? ) 。
考虑简单的dp。
开四维数组
f
[
i
]
[
j
]
[
k
]
[
p
]
f[i][j][k][p]
f[i][j][k][p],表示前
i
i
i行,选了
j
j
j个菜品1,
k
k
k个菜品2,
p
p
p个菜品3。
背包走起,
O
(
n
4
)
O(n^4)
O(n4)
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) a[i][j]=read();
f[0][0][0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<=i;j++)
for(int k=0;k<=i;k++)
for(int p=0;p<=i;p++)
{
f[i][j][k][p]=(f[i][j][k][p]+f[i-1][j][k][p])%mod;
if(j) f[i][j][k][p]=(f[i][j][k][p]+f[i-1][j-1][k][p]*a[i][1]%mod)%mod;
if(k) f[i][j][k][p]=(f[i][j][k][p]+f[i-1][j][k-1][p]*a[i][2]%mod)%mod;
if(p) f[i][j][k][p]=(f[i][j][k][p]+f[i-1][j][k][p-1]*a[i][3]%mod)%mod;
}
for(int i=0;i<=n/2;i++)
for(int j=0;j<=n/2;j++)
for(int k=0;k<=n/2;k++)
if(i<=k+j&&j<=i+k&&k<=i+j)
ans=(ans+f[n][i][j][k])%mod;
printf("%lld",ans-1);
return 0;
}
100 p t s 100pts 100pts
好了,到目前我们已经得到了64分,对于我这种蒟蒻在考场上就已经满足走人了,然而这不是考场,我们还是要写出正解。
n
≤
100
,
m
≤
2000
n\le100,m\le2000
n≤100,m≤2000 。
此时,我们就要逆向思考了。
我们可以轻易得出:合法的情况数=总情况数–不合法的情况数。
总情况数可以预处理(见代码)。
不合法的情况就是有一列选的点数大于总点数的
1
2
\frac{1}{2}
21,我们可以枚举这个超过了
1
2
\frac{1}{2}
21的列
k
k
k,然后开二维数组
f
[
i
]
[
j
+
100
]
f[i][j+100]
f[i][j+100],表示dp到第
i
i
i行,
k
k
k列与其它列点数之和的差,加100是为了避免第二维为负数RE
然后就可以快乐的写状态转移方程啦:
f
[
i
]
[
j
+
100
]
=
(
f
[
i
−
1
]
[
j
+
100
]
+
f
[
i
−
1
]
[
j
+
1
+
100
]
∗
B
[
i
]
)
m
o
d
M
o
d
;
f[i][j+100]=(f[i-1][j+100]+f[i-1][j+1+100]*B[i])\mod Mod;
f[i][j+100]=(f[i−1][j+100]+f[i−1][j+1+100]∗B[i])modMod;
i
f
(
j
+
100
−
1
≥
0
)
f
[
i
]
[
j
+
100
]
=
(
f
[
i
]
[
j
+
100
]
+
f
[
i
−
1
]
[
j
+
100
−
1
]
∗
A
[
i
]
)
m
o
d
M
o
d
;
if(j+100-1\ge0)f[i][j+100]=(f[i][j+100]+f[i-1][j+100-1]*A[i])\mod Mod;
if(j+100−1≥0)f[i][j+100]=(f[i][j+100]+f[i−1][j+100−1]∗A[i])modMod;
f
[
i
−
1
]
[
j
+
100
]
f[i-1][j+100]
f[i−1][j+100]是什么都不选,
f
[
i
−
1
]
[
j
+
1
+
100
]
∗
B
[
i
]
f[i-1][j+1+100]*B[i]
f[i−1][j+1+100]∗B[i]是选除
k
k
k列外其它点,
f
[
i
−
1
]
[
j
+
100
−
1
]
∗
A
[
i
]
f[i-1][j+100-1]*A[i]
f[i−1][j+100−1]∗A[i]是选
k
k
k列上的点。
时间复杂度
O
(
m
n
2
)
O(mn^2)
O(mn2)
#include<bits/stdc++.h>
using namespace std;
int read()
{
int i=0;char ch;
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) {i=i*10+ch-'0';ch=getchar();}
return i;
}
const int mod=998244353;
int n,m,a[105][2005],s[105];
long long ans=1,sum,f[105][2005],A[105],B[105];
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) a[i][j]=read();
for(int i=1;i<=n;i++) //预处理总情况
{
for(int j=1;j<=m;j++)
s[i]=(s[i]+a[i][j])%mod;
ans=ans*(s[i]+1)%mod;
}
for(int k=1;k<=m;k++)
{
for(int i=1;i<=n;i++)
{
A[i]=a[i][k]; //当前行属于第k列的情况数
B[i]=(s[i]-a[i][k]+mod)%mod;//当前行不属于第k列的总情况数(加mod以防负数)
}
f[0][100]=1;
for(int i=1;i<=n;i++)
{
for(int j=-i;j<=i;j++)
{
f[i][j+100]=(f[i-1][j+100]+f[i-1][j+1+100]*B[i])%mod;
if(j+100-1>=0) f[i][j+100]=(f[i][j+100]+f[i-1][j+100-1]*A[i])%mod;
}
}
for(int i=1;i<=n;i++) sum=(sum+f[n][i+100])%mod;
}
printf("%lld",(ans-sum-1+mod)%mod); //-1除掉都不选的方案,+mod以防负数
return 0;
}