【题解】Emiya家今天的饭

Emiya家今天的饭

看到题目,第一反应当然是拿部分分

  • Part1.for循环(\(8\)pts)

\(n==2\)时比较简单,所以考试时就拿了这八分我们先从\(n==2\)入手。我们可以来枚举所有组合,并选择符合要求的方案。

8pts code

#include<cstdio>
#include<iostream>
using namespace std;
int m,n;
long long cnt;
int a[150][2500];
const long long mod=998244353;
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<=m;i++)
    {
        if(!a[1][i]) continue;
        for(int j=1;j<=m;j++)
        {
            if(a[2][j]&&i!=j)
            {
                cnt+=a[1][i]*a[2][j];
                cnt%=mod;
            }
        }
    }
    printf("%lld",cnt%mod);
    return 0;
}
  • part2.dfs暴搜(32pts)

枚举每种方法和食材

dfs参数含义:

        x:烹饪方法;

        tot:当前菜共用了多少次;

        sum:方案总数;

易得:该方法不选时,进入下一种方法\((x+1)\)\(sum\)不变,\(tot\)不变

32pts code

#include<cstdio>
#include<iostream>
using namespace std;
int m,n;
int num[105][2005];
long long ans;
//n 烹饪方法  m  食材
//num[i][j]  i个烹饪方法 j个食材
const int mod=998244353;
int cnt[2500];
void dfs(int x,int tot,long long sum)
//x 烹饪方法
{
    if(x>n) //所有烹饪方法都用过了
    {
        if(tot>1)
        {
            for(int j=1;j<=m;j++)
                if(cnt[j]>tot/2) return;//超过一半
            ans+=sum,ans%=mod;
        }
        return;
    }
    dfs(x+1,tot,sum);//这个烹饪方法不选菜
    for(int i=1;i<=m;i++)
        if(num[x][i])
        {
            cnt[i]++;
            dfs(x+1,tot+1,sum*num[x][i]%mod);//该烹饪方法选一道菜
            cnt[i]--;
        }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d",&num[i][j]);
    dfs(1,0,1);
    printf("%lld",ans);
    return 0;
}
  • Part3.DP(84pts)

做完32分,我们发现正面做比较困难...

正难则反我们可以尝试从反面解决这个问题。

前i-1种烹饪方法中共有x种选法,第i种烹饪方法共y种食材,则前i种烹饪方法共有几种选法?\(x*y\)?恭喜你,答错了。如果这样,那么会有两个问题。第一:若有一种烹饪方法一道菜都不会做,会发生什么?会使所有方法清零。第二:该方法没有选菜的情况未被包含在内。所以前i种烹饪方法共有\(x*(y+1)\)种选法。

值得注意的是:最后要把ans--,去掉什么都不选的情况。

我们需要先求出所有情况,然后再判断是否非法。

\(dp[i][j][k][p]\)表示前\(i\)种烹饪方法,前\(j\)种食材,共做\(k\)道菜,其中有\(p\)道为第\(j\)种食材做的。当\(p*2>k\)时,该方案非法。

tip:状态转移时,\(k,p\)都要从\(0\)开始,\(k-1\)\(p-1\)可能为负数。为防止这种情况导致的数组越界,可以采用坐标平移法,把\(k\)\(p\)统一加一个数\(N\)

可以得到:

    dp[i][j][k+N][p+N]=dp[i-1][j][k+N][p+N];//不选这种烹饪方法
    dp[i][j][k+N][p+N]+=dp[i-1][j-1][k+N-1][p+N-1]*num[i][j];//选这种烹饪方法的这种食材
    dp[i][j][k+N][p+N]+=dp[i-1][j][k+n-1][p+N]*(sum[i]-num[i][j]);//选这种烹饪方法,但不是这种食材

84pts code

#include<cstdio>
#include<iostream>
using namespace std;
const int N=2;
int m,n;
long long dp[45][505][45][45];
int sum[45];
int num[41][505];
const int mod = 998244353;
long long ans=1;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
            scanf("%d",&num[i][j]),sum[i]+=num[i][j];
        ans=(sum[i]+1)*ans%mod;

    }
    ans--;
    for(int i=1;i<=m;i++)
        dp[1][i][0+N][0+N]=1,dp[1][i][1+N][1+N]=num[1][i],dp[1][i][1+N][0+N]=sum[1]-num[1][i];
    for(int i=2;i<=n;i++)
        for(int j=1;j<=m;j++)
            for(int k=0;k<=i;k++)
                for(int p=0;p<=k;p++)
                {
                    dp[i][j][k+N][p+N]=((dp[i-1][j][k+N][p+N]+dp[i-1][j][k-1+N][p-1+N]*num[i][j]%mod)%mod+dp[i-1][j][k-1+N][p+N]*(sum[i]-num[i][j]))%mod;
                    if(p*2-k>0&&i==n)
                        ans-=dp[i][j][k+N]p+N],ans%=mod;
                }
    printf("%lld",(ans+mod)%mod);
    return 0;

}

p.s. 该方法比较玄学,同一份代码不同时间提交得分不同....


AC代码来了

  • part4. DP++(AC)

我们注意到,上述代码可以利用滚动数组滚掉\(i\)一维

我们还注意到,上述方法,我们只需关心\(p*2-k_0\)(为避免混淆,在本部分,part3的\(k\)记为\(k_0\))的值。为防止出现负数,我们可以故技重施采用相同方法——坐标平移。

\(dp[j][k]\)表示:前\(j\)种食材,\(p*2-k_0\)

可以得到状态转移方程:

    dp[j][k]=dp[j][k];//不选这个烹饪方法
    dp[j][k]+=dp[j-1][k-1]*num[i][j];//选这种烹饪方法的这种食材(k项展开为:(p-1)*2-(k0-1))
    dp[j][k]+=dp[j-1][k+1]*(num[i][0]-num[i][j]);//选这种烹饪方法,但不是这种食材(同上)

AC code

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=1e2+5,M=2e3+5;
const int mod=998244353;
int n,m;
long long a[N][M],dp[N][N<<1];
long long ans=1;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            scanf("%lld",&a[i][j]);
            a[i][0]+=a[i][j],a[i][0]%=mod;//a[i][0]取代sum[i]省空间(虽然没什么用...)
        }
        ans*=(a[i][0]+1),ans%=mod;
    }
    ans--;
    for(int i=1;i<=m;i++)
    {
        memset(dp,0,sizeof(dp));
        dp[0][n]=1;// n :坐标平移
        for(int j=1;j<=n;j++)
            for(int k=n-j;k<=n+j;k++)
                dp[j][k]=((dp[j-1][k]+dp[j-1][k-1]*a[j][i])%mod+dp[j-1][k+1]*(a[j][0]-a[j][i]))%mod;
        for(int j=n+1;j<=n*2;j++)//k>n<==>2*p-k>0<==>p*2>k<==>不合法
            ans-=dp[n][j],ans=(ans%mod+mod)%mod;
    }
    printf("%lld",(ans+mod)%mod);
    return 0;
}

然后就愉快地AC了!


ps.蒟蒻第一篇题解

本篇题解 : : return 0

posted @ 2020-08-10 20:05  why_Y  阅读(141)  评论(0)    收藏  举报