题解--luogu--CSP2019.S.Day2.T4--Emiya 家今天的饭

今年CSP我不想说了,想AFO

我看了很久luogu题解,才略微懂了点

P5664 Emiya 家今天的饭【民间数据】

首先这是一道dp考场上没看出来?



思路分析

1.Emiya的条件很容易满足。

2.Rin的条件也是比较死的,同一烹饪方法只有一种菜(也就是输入中的每一行只有一个菜)

3.Yazid的要求很特殊,先不考虑。

可以看出,维护每列已选的节点复杂度太大,不太可行;因此很容易想到,先不考虑每列不超过一半的这个限制,求出总方案数,然后再减去考虑这个限制后不合法的方案数。现在问题就变成,求任意列选的节点超过所有选的节点的一半的方案数之和。

总数:每行的可能数相乘。见下代码。

for(int i=1;i<=n;i++)
{
    for(int j=1;j<=m;j++)
    {
        a[i][j]=read();
        sum[i]=(a[i][j]+sum[i])%mod; //每种烹饪一共能做多少菜
    }
    ans=(ans*(sum[i]+1))%mod; //sum[i]+1是因为可以不用某种烹饪
}
ans--; //ans-1是因为不能一样菜都没有

接下来咋处理?

设f[i][j][k]表示前i行选j个节点,当前枚举到的列选k个节点的方案数。对于每个列,复杂度为O($n^3$),总的复杂度为O($mn^3$),可以得到84分的高分。

要拿满分怎么办?

考虑将某两个状态合并!!!“观察状态,实际上我们想知道的只是j,k的大小关系(即k>
⌊j/2
 ),对于具体的值并不关心,考虑将它们合并到一维。” 
考虑我们需要的限制条件j>⌊k/2 ,变形一下可以得到2*k+n-j>n

观察这个式子,可以发现,nj就是这n行里没有选的行数。2*k就只需要存两次就行了

然后一个奇妙的想法就出来了:

对于每个节点,选它时,当做该列选了两次。

而对于某一行不选时,当做所有列选了一次。

最终要找的就是当前列被选超过n次的方案。这样就成功地优化掉了第二维。

f[i][k]=(f[i][k]+f[i-1][k]*(sum[i]-a[i][j]))%mod;//若不选此列,所以总次数是每行除开a[i][j]外的任何数之积。
f[i][k+1]=(f[i][k+1]+f[i-1][k])%mod;//若不选此行,总次数是i-1行的方案,和这一行没任何关系,直接改变k+1的值
f[i][k+2]=(f[i][k+2]+f[i-1][k]*a[i][j])%mod; //若要选节点,同上面的两次分i-1行的值乘上该节点值,也就是k+2的值了

注意:开long long

代码如下:

#include<cstdio> 
#include<string>
#define ll long long 
using namespace std;

const int maxn=105,maxm=2005,mod=998244353;
int n,m;
ll a[maxn][maxm];
ll sum[maxn];
ll ans=1;
ll f[maxn][maxm];

int read()
{
    int x=0,f=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=x*10+c-'0';c=getchar();}
    return x*f;
}

void readdata()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            a[i][j]=read();
            sum[i]=(a[i][j]+sum[i])%mod;
        }
        ans=(ans*(sum[i]+1))%mod;
    }
    ans--;
    for(int j=1;j<=m;j++)
    {
        memset(f,0,sizeof(f));
        f[0][0]=1;
        for(int i=1;i<=n;i++)
        {
            for(int k=0;k<=(i-1)<<1;k++) //第i行可能涉及到(i-1)*2,"<<1"相当于“*2”
            {
                f[i][k]=(f[i][k]+f[i-1][k]*(sum[i]-a[i][j]))%mod;
                f[i][k+1]=(f[i][k+1]+f[i-1][k])%mod;
                f[i][k+2]=(f[i][k+2]+f[i-1][k]*a[i][j])%mod;
            }
            
        }
        for(int k=n+1;k<=2*n;k++)
        {
            ans=(ans-f[n][k]+mod)%mod; //每次会重复
        }
    }
    printf("%lld",ans%mod);
}

int main()
{
    readdata();
    return 0;
}

AFO^……

原文地址:https://www.cnblogs.com/mzyczly/p/11914291.html