AtCoder AGC039F Min Product Sum (容斥原理、组合计数、DP)

题目链接

https://atcoder.jp/contests/agc039/tasks/agc039_f

题解

又是很简单的F题我不会。。。
考虑先给每行每列钦定一个最小值\(a_i,b_j\),并假设每行每列的最小值是这个数,且每行每列只需要放\(\ge\)这个数的数即可,那么这种情况的价值是\(\prod^n_{i=1}\prod^m_{j=1}\min(a_i,b_j)\), 方案数是\(\prod^n_{i=1}\prod^m_{j=1}(n+1-\max(a_i,b_j))\)
然后我们需要把最小值的限制容斥掉,也就是枚举若干行若干列容斥掉(限制\(+1\)同时系数乘以\(-1\))。
这样的话直接暴力DP就可以解决。设\(f[k][i][j]\)表示当前用\([1,k]\)中的数填满了\(i\)行\(j\)列。转移可以直接枚举不被容斥的行数、不被容斥的列数、容斥的行数、容斥的列数,乘上贡献系数,得到了一个多项式时间复杂度的算法。
但是我们发现这样转移显然很浪费,我们可以把四个变量同时枚举改成分四个阶段依次枚举,这样转移时间复杂度降到了\(O(n)\).(注意因为要保证从小到大填数,所以必须先枚举不被容斥再枚举被容斥)
不过这题还挺卡常的……需要\(O(n^3)\)预处理一下转移系数,详见代码
时间复杂度\(O(n^4)\)
orz myh

代码

#include<bits/stdc++.h>
#define llong long long
#define mkpr make_pair
#define riterator reverse_iterator
using namespace std;

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

const int N = 100;
int P;
llong pw[N+3][N*N+3];
llong comb[N+3][N+3];
llong f[2][N+3][N+3];
llong trans[N+3][N+3];
int n,m,p;

llong quickpow(llong x,llong y)
{
    llong cur = x,ret = 1ll;
    for(int i=0; y; i++)
    {
        if(y&(1ll<<i)) {y-=(1ll<<i); ret = ret*cur%P;}
        cur = cur*cur%P;
    }
    return ret;
}

void initmath()
{
    for(int i=0; i<=N; i++)
    {
        pw[i][0] = 1ll; for(int j=1; j<=N*N; j++) pw[i][j] = pw[i][j-1]*i%P;
    }
    comb[0][0] = 1ll;
    for(int i=1; i<=N; i++)
    {
        comb[i][0] = comb[i][i] = 1ll;
        for(int j=1; j<i; j++) comb[i][j] = (comb[i-1][j]+comb[i-1][j-1])%P;
    }
}

llong updsum(llong &x,llong y) {x = x+y>=P?x+y-P:x+y;}

int main()
{
    scanf("%d%d%d%lld",&n,&m,&p,&P);
    initmath();
    int curk = 0; f[0][0][0] = 1ll;
    for(int k=1; k<=p; k++)
    {
        curk^=1; memset(f[curk],0,sizeof(f[curk]));
        for(int j=0; j<=m; j++) for(int ii=0; ii<=n; ii++) trans[j][ii] = pw[k][ii*(m-j)]%P*pw[p-k+1][ii*j]%P;
        for(int i=0; i<=n; i++)
        {
            for(int j=0; j<=m; j++)
            {
                llong x = f[curk^1][i][j]; if(!x) continue;
                for(int ii=0; ii+i<=n; ii++)
                {
                    updsum(f[curk][i+ii][j],x*comb[i+ii][i]%P*trans[j][ii]%P);
                }
            }
        }
        curk^=1; memset(f[curk],0,sizeof(f[curk]));
        for(int i=0; i<=n; i++) for(int jj=0; jj<=m; jj++) trans[i][jj] = pw[k][jj*(n-i)]%P*pw[p-k+1][jj*i]%P;
        for(int i=0; i<=n; i++)
        {
            for(int j=0; j<=m; j++)
            {
                llong x = f[curk^1][i][j]; if(!x) continue;
                for(int jj=0; jj+j<=m; jj++)
                {
                    updsum(f[curk][i][j+jj],x*comb[j+jj][j]%P*trans[i][jj]%P);
                }
            }
        }
        curk^=1; memset(f[curk],0,sizeof(f[curk]));
        for(int j=0; j<=m; j++) for(int ii=0; ii<=n; ii++) trans[j][ii] = pw[k][ii*(m-j)]%P*pw[p-k][ii*j]%P;
        for(int i=0; i<=n; i++)
        {
            for(int j=0; j<=m; j++)
            {
                llong x = f[curk^1][i][j]; if(!x) continue;
                for(int ii=0; ii+i<=n; ii++)
                {
                    llong y = x*comb[i+ii][i]%P*trans[j][ii]%P;
                    updsum(f[curk][i+ii][j],ii&1?P-y:y);
                }
            }
        }
        curk^=1; memset(f[curk],0,sizeof(f[curk]));
        for(int i=0; i<=n; i++) for(int jj=0; jj<=m; jj++) trans[i][jj] = pw[k][jj*(n-i)]%P*pw[p-k][i*jj]%P;
        for(int i=0; i<=n; i++)
        {
            for(int j=0; j<=m; j++)
            {
                llong x = f[curk^1][i][j]; if(!x) continue;
                for(int jj=0; jj+j<=m; jj++)
                {
                    llong y = x*comb[j+jj][j]%P*trans[i][jj]%P;
                    updsum(f[curk][i][j+jj],jj&1?P-y:y);
                }
            }
        }
    }
    printf("%lld\n",f[curk][n][m]);
    return 0;
}

原文地址:https://www.cnblogs.com/suncongbo/p/12267624.html

时间: 2024-10-17 21:43:32

AtCoder AGC039F Min Product Sum (容斥原理、组合计数、DP)的相关文章

Atcoder Grand Contest 039C(容斥原理,计数DP)

//每次操作相当于将最低位取反加到最高位(N~1位)#define HAVE_STRUCT_TIMESPEC#include<bits/stdc++.h>using namespace std;char s[200007];int mi2[200007],num[200007];const int mod = 998244353;int main(){ ios::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); int n; cin&

[ZJOI2010]排列计数 (组合计数/dp)

[ZJOI2010]排列计数 题目描述 称一个1,2,...,N的排列P1,P2...,Pn是Magic的,当且仅当2<=i<=N时,Pi>Pi/2. 计算1,2,...N的排列中有多少是Magic的,答案可能很大,只能输出模P以后的值 输入输出格式 输入格式: 输入文件的第一行包含两个整数 n和p,含义如上所述. 输出格式: 输出文件中仅包含一个整数,表示计算1,2,?, 的排列中, Magic排列的个数模 p的值. 输入输出样例 输入样例#1: 20 23 输出样例#1: 16 说明

HDU 4832 组合计数dp

Chess Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 509    Accepted Submission(s): 198 Problem Description 小度和小良最近又迷上了下棋.棋盘一共有N行M列,我们可以把左上角的格子定为(1,1),右下角的格子定为(N,M).在他们的规则中,"王"在棋盘上的走法遵循十字

Codeforces Round #344 (Div. 2) E. Product Sum 二分斜率优化DP

E. Product Sum Blake is the boss of Kris, however, this doesn't spoil their friendship. They often gather at the bar to talk about intriguing problems about maximising some values. This time the problem is really special. You are given an array a of

HDU 4390 Number Sequence (容斥原理+组合计数)

HDU 4390 题意: 大概就是这样.不翻译了: Given a number sequence b1,b2-bn. Please count how many number sequences a1,a2,...,ansatisfy the condition thata1?a2?...?an=b1?b2?-?bn(ai,bi>1). 思路: 我们能够确定一件事:等号两边由同样数量的质因子组成. 假设ai能够等于1,答案就是把这些质因子分配进n个位置的方案数. 设左边的数字共由x个质因子组成

hdu4779 组合计数+dp

提交 题意:给了n*m的网格,然后有p个重型的防御塔,能承受1次攻击,q个轻型防御塔不能接受任何攻击,然后每个防御搭会攻击他所在的行和所在的列,最后求在这个网格上放至少一个防御塔的方案数, 我们枚举 选取多少个重型防御塔然后这个重型防御塔有多少是两个在一行,和两个在一列 O(P^3)的效率 #include <iostream> #include <algorithm> #include <cstdio> #include <string.h> #inclu

Singer House CodeForces - 830D (组合计数,dp)

大意: 一个$k$层完全二叉树, 每个节点向它祖先连边, 就得到一个$k$房子, 求$k$房子的所有简单路径数. $DP$好题. 首先设$dp_{i,j}$表示$i$房子, 分出$j$条简单路径的方案数, 那么最终答案就为$dp_{i,1}$. 考虑两棵$i-1$房子转移到$i$房子的情况, 分四种情况. 两个子树间不与根节点连边, 那么$dp_{i,j+k}=\sum dp_{i-1,j}dp_{i-1,k}$ 两个子树只有一条路径与根节点连边, $dp_{i,j+k}=\sum dp_{i-

3.29省选模拟赛 除法与取模 dp+组合计数

LINK:除法与取模 鬼题.不过50分很好写.考虑不带除法的时候 其实是一个dp的组合计数. 考虑带除法的时候需要状压一下除法操作. 因为除法操作是不受x的大小影响的 所以要状压这个除法操作. 直接采用二进制状压是不明智的 2的个数最多为13个 2^13也同样到达了1e4的复杂度. 考虑 hash状压 即 2的个数有x个 那么我们就有状态w表示2还有x个. 这样做的原因是把一些相同的东西给合并起来 而并非分散开来.即有多个2直接记录有多少个即可. 可以发现 这样做不同的除数最多只有5个 状态量较

Yue Fei&#39;s Battle(组合计数递推)

//求一个直径为 k 的树有多少种形态,每个点的度不超过 3 // 非常完美的分析,学到了,就是要细细推,并且写的时候要细心 还有除法取模需要用逆元 #include <iostream> #include <stdio.h> #include <string.h> #include <math.h> #include <stdlib.h> using namespace std; #define MOD 1000000007 #define L