CSP-S2019 D2T1 Emiya家今天的饭
CSP-S2019 D2T1 Emiya家今天的饭
题目描述
Emiya 是个擅长做菜的高中生,他共掌握 nn 种烹饪方法,且会使用 mm 种主要食材做菜。为了方便叙述,我们对烹饪方法从 1 \sim n1∼n 编号,对主要食材从 1 \sim m1∼m 编号。
Emiya 做的每道菜都将使用恰好一种烹饪方法与恰好一种主要食材。更具体地,Emiya 会做 a_{i,j}a**i,j 道不同的使用烹饪方法 ii 和主要食材 jj 的菜(1 \leq i \leq n, 1 \leq j \leq m1≤i≤n,1≤j≤m),这也意味着 Emiya 总共会做 \sum\limits_{i=1}^{n} \sum\limits_{j=1}^{m} a_{i,j}i=1∑n**j=1∑mai,j 道不同的菜。
Emiya 今天要准备一桌饭招待 Yazid 和 Rin 这对好朋友,然而三个人对菜的搭配有不同的要求,更具体地,对于一种包含 kk 道菜的搭配方案而言:
- Emiya 不会让大家饿肚子,所以将做至少一道菜,即 k \geq 1k≥1
- Rin 希望品尝不同烹饪方法做出的菜,因此她要求每道菜的烹饪方法互不相同
- Yazid 不希望品尝太多同一食材做出的菜,因此他要求每种主要食材至多在一半的菜(即 \lfloor \frac{k}{2} \rfloor⌊2k⌋ 道菜)中被使用
这里的 \lfloor x \rfloor⌊x⌋ 为下取整函数,表示不超过 xx 的最大整数。
这些要求难不倒 Emiya,但他想知道共有多少种不同的符合要求的搭配方案。两种方案不同,当且仅当存在至少一道菜在一种方案中出现,而不在另一种方案中出现。
Emiya 找到了你,请你帮他计算,你只需要告诉他符合所有要求的搭配方案数对质数 998,244,353998,244,353 取模的结果。
输入格式
第 1 行两个用单个空格隔开的整数 n,mn,m。
第 2 行至第 n + 1n+1 行,每行 mm 个用单个空格隔开的整数,其中第 i + 1i+1 行的 mm 个数依次为 a_{i,1}, a_{i,2}, \cdots, a_{i,m}a**i,1,a**i,2,⋯,a**i,m。
输出格式
仅一行一个整数,表示所求方案数对 998,244,353998,244,353 取模的结果
说明/提示
【样例 1 解释】
由于在这个样例中,对于每组 i, ji,j,Emiya 都最多只会做一道菜,因此我们直接通过给出烹饪方法、主要食材的编号来描述一道菜。
符合要求的方案包括:
- 做一道用烹饪方法 1、主要食材 1 的菜和一道用烹饪方法 2、主要食材 2 的菜
- 做一道用烹饪方法 1、主要食材 1 的菜和一道用烹饪方法 2、主要食材 3 的菜
- 做一道用烹饪方法 1、主要食材 3 的菜和一道用烹饪方法 2、主要食材 2 的菜
因此输出结果为 3 \mod 998,244,353 = 33mod998,244,353=3。 需要注意的是,所有只包含一道菜的方案都是不符合要求的,因为唯一的主要食材在超过一半的菜中出现,这不满足 Yazid 的要求。
【样例 2 解释】
Emiya 必须至少做 2 道菜。
做 2 道菜的符合要求的方案数为 100。
做 3 道菜的符合要求的方案数为 90。
因此符合要求的方案数为 100 + 90 = 190。
【数据范围】
测试点编号 | n=n= | m=m= | a_{i,j}<a**i,j< | 测试点编号 | n=n= | m=m= | a_{i,j}<a**i,j< |
---|---|---|---|---|---|---|---|
11 | 22 | 22 | 22 | 77 | 1010 | 22 | 10^3103 |
22 | 22 | 33 | 22 | 88 | 1010 | 33 | 10^3103 |
33 | 55 | 22 | 22 | 9\sim 129∼12 | 4040 | 22 | 10^3103 |
44 | 55 | 33 | 22 | 13\sim 1613∼16 | 4040 | 33 | 10^3103 |
55 | 1010 | 22 | 22 | 17\sim 2117∼21 | 4040 | 500500 | 10^3103 |
66 | 1010 | 33 | 22 | 22\sim 2522∼25 | 100100 | 2\times 10^32×103 | 998244353998244353 |
对于所有测试点,保证 1 \leq n \leq 1001≤n≤100,1 \leq m \leq 20001≤m≤2000,0 \leq a_{i,j} \lt 998,244,3530≤a**i,j<998,244,353。
题解:
D1T1、D2T1和D1T2是考场上唯三动手写了的题,其他的都没什么思路(蒟蒻太菜了没办法)。
但是最终只有D1T1切掉了,还挂了5分见祖宗。其他的两道题部分分都没拿满,最终弱省省二收场。
主要还是太菜,这道题的暴搜都没写出来。
不多回忆了,开讲。
分析题意:
说了一堆废话,提炼出来就是:
有一个\(N\)行\(M\)列的矩阵,至少选一行,每行最多选一种,每列最多选\(\lfloor\frac{k}{2}\rfloor\)种。统计方案数模上998244353。
注意是种而不是个,所以在计数的时候需要使用乘法原理,当前种类的\(a_{i,j}\)都要×进去。
一看部分分,发现有32分的暴搜(就是蒟蒻考场上想写的),也就是每行可以有4种选择:食材1、2、3以及不选。总复杂度是\(O(4^{10})\),可过。
然后就是这个过程的递归模拟...考码力了...(莫名想哭,这32拿到了我就省一了啊)
首先,我们想到的是递归到最后一层,然后判断答案是否合法。合法的话就统计进去,不合法的话就不统计。但是这样的方式只能统计到\(k=n\)的情况,对\(k<n\)的就不太可做。
所以我们不如把选几个作为参量往里传,这样的话思路会明晰很多。
所以蒟蒻又来总结了:搜索算法重要的是递归出口,以及在递归过程中维护自己想要的信息。这个信息的维护,如果发现当前过程比较难维护,莫不如尝试着把它归到递归函数的参量里。比如这道题,我的思路过程已经在上面展示了。
32p代码:
#include<cstdio>
#define ll long long
using namespace std;
const int mod=998244353;
int n,m;
int a[20][5],cnt[5];//cnt[i]表示第i种食材一共做了几道菜
ll ans;
ll b[20];//b[i]表示第i道菜
void dfs(int dep,ll tot,int x,int y)
//表示当前进行到第dep种烹饪方法,方案数为tot,要选x道菜,已经选了y道菜
{
if(x==y)
{
ans=(ans+tot)%mod;
return;
}
if(dep>n)
return;
for(int i=1;i<=m;i++)
if(cnt[i]+1<=x/2)
{
cnt[i]++;
dfs(dep+1,tot*a[dep][i]%mod,x,y+1);
cnt[i]--;
}
dfs(dep+1,tot,x,y);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
for(int i=1;i<=n;i++)
dfs(0,1,i,0);
printf("%lld",ans);
return 0;
}
然后我们开始深入思考。
其实考场上想到了DP,因为计数类问题我们其实也拿不出什么其他的算法了(本蒟蒻肤浅之言)
Code:
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int maxn=110,maxm=2010,mod=998244353;
int n,m;
ll ans=1;
ll cnt[maxn],w[maxn][maxm],dp[maxn][maxm];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
scanf("%lld",&w[i][j]),cnt[i]=(cnt[i]+w[i][j])%mod;
ans=(ans*(cnt[i]+1))%mod;
}
ans=(ans+mod-1)%mod;
for(int i=1;i<=m;i++)
{
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int j=1;j<=n;j++)
for(int k=0;k<=2*(j-1);k++)
{
dp[j][k]=(dp[j][k]+dp[j-1][k]*(cnt[j]-w[j][i]))%mod;
dp[j][k+1]=(dp[j][k+1]+dp[j-1][k])%mod;
dp[j][k+2]=(dp[j][k+2]+dp[j-1][k]*w[j][i])%mod;
}
for(int j=n+1;j<=2*n;j++)
ans=(ans+mod-dp[n][j])%mod;
}
printf("%lld",ans);
return 0;
}