zoukankan      html  css  js  c++  java
  • [Noip2019]Emiya 家今天的饭

    [Noip2019]Emiya 家今天的饭

    一.前言

    ​ 月球人何苦为难月球人……题目链接

    ​ 还有 long long,memset 真奇妙~

    二.思路

    ​ 这个题吧,我也是对着题解琢磨了很久才略懂?

    ​ 首先把题目化简为一下几个条件:

    • 有一个 n 行 m 列的矩阵
    • 每一行只能选一个数
    • 每一列选的数的个数不能超过(lfloor frac{n}{2} floor)
    • 求这些数的乘积
    • 至少要选一个数

    其实其他条件都还好,只是第三项很麻烦。这里运用容斥的思想,首先求出所有的可能性,然后减去只不满足第三项的可能情况数。

    ​ 那么所有的可能性很好求(我们记第 i 行的数的和为 sum[i] )那么所有的可能性为(prod_{i=1}^n(sum[i]-1)~~~~-1), 那是最后减的一个1啊!不要看错了,是因为必须要选才减的。

    ​ 那么我们来求后面的那个奇怪东西。由于总 n 个数,那么不满足第三项的的列肯定只有一个。(显然)我们试图枚举这个列(设这个列为 p)。那么列确定下来,设 (f[i][j][k]) 为在前 i 行的选择中,在 p 上选了 j 个,在除 p 以外的列中选了 k 个。于是很容易得到转移方程

    [f[i][j][k]=f[i-1][j][k-1]*(sum[i]-a[i][p])+f[i-1][j-1][k]*a[i][p] ]

    加的两项分别对应在第 i 行是选在 p 上还是其他列上。

    ​ 但是很遗憾的,这样的做法时间复杂度太高了。于是我们需要魔改。实际上,引用另一篇题解的话,我们其实并不在意 j,k 的值是多少,我们只需要知道 j,k 的差是多少就可以进行计算。先放转移(此处的j已经变为差了,k则为 p

    [f[i][j]=f[i-1][j]+f[i-1][j-1]*a[i][k]+f[i-1][j+1]*(sum[i]-a[i][k]) ]

    这样就降下来了,然后枚举j的时候因为可能会有负数,所以整体偏移n就好,一些小细节看代码吧。

    三.CODE

    #define m_for(i,a,b) for(int i=a;i<=b;++i)
    #define mod 998244353
    int read(){
    	........
    }
    int n,m,sum[105],a[105][2005],f[105][4005],ans=1;
    int main(){
    	n=read();m=read();
    	m_for(i,1,n){
    		m_for(j,1,m)sum[i]=(1LL*sum[i]+(a[i][j]=read()))%mod;//每一行的和
    		ans=(1LL*ans*(sum[i]+1))%mod;//总方案数
    	}
    	ans--;
    	m_for(k,1,m){
    		memset(f,0,sizeof(f));//每次清零外加初始化
    		f[0][n]=1;//实际上减去偏移的n是 f[0][0]=0
    		m_for(i,1,n)m_for(j,n-i,n+i)//n-i 和 n+i 都是偏移
    		f[i][j]=((f[i-1][j]+1LL*f[i-1][j-1]*a[i][k]%mod)%mod+1LL*f[i-1][j+1]*(sum[i]-a[i][k])%mod)%mod;
    		m_for(j,1,n)ans=(1LL*ans-f[n][n+j]+mod)%mod;//比其他的和都多就已经有一半了
    	}
    	printf("%lld
    ",(ans%mod+mod)%mod);
    	return 0;
    }
    
  • 相关阅读:
    JDBC
    两道关于回溯法,分支限界法的算法题
    旅行售货员问题
    jdbc学习
    mysql简单练习
    取会邮件客户端中的密码
    触发器的使用
    事务的数据一致性测试
    读取其他软件listview控件的内容
    sqlserver2008 ,只能选C盘目录,不能选其它盘目录
  • 原文地址:https://www.cnblogs.com/clockwhite/p/13423283.html
Copyright © 2011-2022 走看看