zoukankan      html  css  js  c++  java
  • P5664 [CSP-S2019] Emiya 家今天的饭

    题目描述

    给出一个矩阵 要求每横行最多选一个点 每列选的点不超过总结点数一半(向下取整)
    再给出每个节点选取的方案数,求总方案数

    题解

    直接考虑dp
    为什么我们要容斥呢 因为既然有列的限制 满足条件的列可以有许多 但是违法的列只能有一个
    因为这个违法的列要选出多于一半的点 即使另外所有点都在同一列也没用
    如果我们只计算有解的部分,在设计dp状态的时候 无法考虑到加上前面的选择是否非法

    接下来枚举不合法的这一列 设计一个简单的状态
    首先枚举不合法的这一列 (col)
    (f[i][j][k]) 表示前 (i) 行在 (col) 这一列选择了了 (j) 个点 其它列选择了 (k) 个点
    有多少种方案
    (s) 表示第 (i)(a[i][j]) 的总和
    推出方程:

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

    分别表示在第 (i) 行不选 (col) 选择 (col) 以及什么都不选
    这一步复杂度是 (O(mn^3)) 然后考虑如何统计答案
    不合法方案数为

    [sum=f[n][j][k] , j>k ]

    然后随便算一下总方案数
    (g[i][j]) 表示前 (i) 行选择 (j) 个的方案数
    则有

    [g[i][j]=g[i-1][j]+g[i-1][j-1]*s[i] ]

    然后所有(g[n][j])就是总方案数
    最后我们获得了 (84) 分的好成绩
    说实在的 考场上能推出这些个人就满足了
    接着想想下一步怎么优化
    对于一个状态 (f[i][j][k])我们发现只统计了 (j>k) 时候的答案
    也就是说 我们没必要存储下 (j,k) 的具体值
    只需要记录 (j)(k) 大多少即可
    所以现在 (f[i][j]) 表示在前 (i) 行中
    当前这列比剩下列多选j个有多少种方案

    [f[i][j]=f[i-1][j]+f[i-1][j-1]*a[i][col]+f[i-1][j+1]*(s[i]-a[i][col]) ]

    #include<bits/stdc++.h>
    #define ll long long
    #define inf 0x7fffffff
    using namespace std;
    int n,m;
    #define maxm 2009
    #define maxn  109
    ll a[maxn][maxm];
    #define mod 998244353
    ll f[maxn][maxn*2];
    ll g[maxn][maxn];
    ll s[maxn];
    ll ans=0;
    signed main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++)
    	{
    		s[i]=0;
    		for(int j=1;j<=m;j++)
    		{
    			scanf("%lld",&a[i][j]);
    			s[i]=(s[i]+a[i][j])%mod;
    		}
    	}
    	for(int col=1;col<=m;col++)
    	{
    		memset(f,0,sizeof(f));
    		f[0][n]=1;//初始状态 
    		for(int i=1;i<=n;i++)
    		{
    			for(int j=n-i;j<=n+i;j++)
    			{
    				f[i][j]=(f[i-1][j]+f[i-1][j-1]*a[i][col]+(f[i-1][j+1]*(s[i]-a[i][col])%mod))%mod;
    			}
    		}
    		for(int j=1;j<=n;j++)
    		{
    			ans=(ans+f[n][j+n])%mod;
    		}
    	}
    	g[0][0]=1;
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=0;j<=n;j++)
    		{
    			g[i][j]=g[i-1][j];
    			if(j>0)g[i][j]=(g[i][j]+g[i-1][j-1]*s[i]%mod)%mod;
    		}
    	}
    	for(int i=1;i<=n;i++)
    	{
    		ans=(ans-g[n][i]+mod)%mod;
    	}
    	printf("%lld
    ",(mod-ans+mod)%mod);
    	return 0;
    }
    
  • 相关阅读:
    虚幻4目录文件结构
    虚幻4编译手记
    几个重要的坐标系
    关于(void**)及其相关的理解
    装饰器总结篇(持续更新ing)
    Linux中find常见用法示例
    linux grep命令
    linux下IPTABLES配置详解
    分布式数据库中间件DDM的实现原理
    消息队列应用场景解析
  • 原文地址:https://www.cnblogs.com/lzy-blog/p/15305100.html
Copyright © 2011-2022 走看看