码蹄集 - MT3143 - 试管装液

Posted Tisfy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了码蹄集 - MT3143 - 试管装液相关的知识,希望对你有一定的参考价值。

传送门


试管装液

时间限制:1秒
空间限制:128M


题目描述

炼金术士小码哥最近学到了新的炼金方法,将炼金材料制成液料加入试管中再混合进行炼金能提高炼金品质。现在小码哥想要将一批基础的炼金原料全部制成液料存储在试管中。

小码哥现在有n个试管(试管被编号为1,2,···,n),并且她将炼金原料制成液料后一共得到了m单位质量的液料。每个试管最多能装k单位质量的液料。小码哥为了方便,每个试管中只会被装入整数个单位质量的液料,并且所有液料都会被装入试管

小码哥想知道,一共有多少种方案装入液料,方案对1e8+7取模

不同方案的定义为两组不同方案中至少有一个同号试管的液料的质量不同(例如2个试管中1 2跟2 1为不同方案)


输入描述

第一行一个正整数k。

第二行一个正整数T,表示数据组数

接下来T行,每行两个整数n,m。

数据范围

1≤T≤10000,1≤n,k≤100,0≤m≤n*k


输出描述

输出T行,每行一个整数,表示对应行的方案数


样例一

输入

9
5
3 12
9 67
5 27
4 1
3 6

输出

73
315315
4840
4
28

题目分析

我觉得这道题不简单。

如果数据量再小一些,可以尝试递归。但是这道题递归会超时,记忆化的前提下也只能通过7组。

如果使用动态规划,那么这道题麻烦的一点就是“每根试管是不同的”,也就是说方案1 2和方案2 1是两种方案。并且每个试管最大容量位k

于是不得不让我们想到生成函数

首先介绍比较容易理解的递归,数据量小的时候可以使用:

写一个递归函数getAns(long long n, long long m),返回“n个试管里放m单位体积”的方案数。

递归终止条件为m = 0 n n n个试管都空着的方案数为 1 1 1)或n = 0 0 0 0个试管放 m m m( m > 0 m>0 m>0)体积的方案数为 0 0 0

之后就模拟这 n n n根试管的第一根试管盛放的液体量thisV: 0 -> min(k, m),并继续递归getAns(n - 1, m - thisV)(剩下的 n − 1 n-1 n1根试管盛放 m − t h i s V m - thisV mthisV

ll getAns(int n, int m)   // n个试管里放m体积
    if (m == 0) 
        return 1;
    
    if (n == 0) 
        return 0;
    
    ll ans = 0;
    for (int thisV = 0; thisV <= k && thisV <= m; thisV++) 
        ans = (ans + getAns(n - 1, m - thisV)) % MOD;
    
    return ans;

接下来进行记忆化操作:

使用unordered_map<int, int>来记录已经计算过的值。

这就需要把nm映射到一个数字中。

因为m ≤ n * k ≤ 10000,所以我们可以令m乘以100000再和n相加,这样就能“把 m m m n n n糅合到一个数中”了

糅合函数:

inline int two2one(int n, int m) 
    return n * 100000 + m;

分解函数:

inline void one2two(int a, int& n, int& m) 
    m = a % 100000;
    n = a / 100000;

  • 这里为什么不使用unordered_map<pair<int, int>, int>来更方便地存放nm?先不说效率问题,如果使用unordered_map<pair<int, int>, int>,你就得自定义一个pair<int, int>的哈希函数,这其实已经和上述糅合操作差不多了,甚至更麻烦。感兴趣的可以 点我参考
  • 这里为什么不使用map<pair<int, int>, int>来避免自定义哈希函数?因为递归的解法本来就超时,map存放的键值是有序的,这也就导致了存取的复杂度增加(unordered_map的O(1)变成了map的O(log n))

进入函数,如果已经计算过了 g e t A n s ( n , m ) getAns(n, m) getAns(n,m),就直接返回MAP[two2one(n, m)]

否则进行递归计算,在返回结果之前,把结果存放在map中。

unordered_map<int, ll> ma;

inline int two2one(int n, int m) 
    return n * 100000 + m;


inline void one2two(int a, int& n, int& m) 
    m = a % 100000;
    n = a / 100000;


ll getAns(int n, int m)   // n个试管里放m体积
    if (m == 0) 
        return 1;
    
    if (n == 0) 
        return 0;
    
    int a = two2one(n, m);
    if (ma.count(a)) 
        return ma[a];
    
    ll ans = 0;
    for (int thisV = 0; thisV <= k && thisV <= m; thisV++) 
        ans = (ans + getAns(n - 1, m - thisV)) % MOD;
    
    return ma[a] = ans;

好了,到此为止,我们只需要愉快地调用getAns这个函数就可以了

int main() 
    cin >> k;
    int N;
    cin >> N;
    while (N--) 
        int n, m;
        scanf("%d%d",&n, &m);
        printf("%lld\\n", getAns(n, m));
    
    return 0;

更小的数据时才能使用的代码

#include <bits/stdc++.h>
using namespace std;
#define mem(a) memset(a, 0, sizeof(a))
#define dbg(x) cout << #x << " = " << x << endl
#define fi(i, l, r) for (int i = l; i < r; i++)
#define cd(a) scanf("%d", &a)
typedef long long ll;

const ll MOD = 1e8 + 7;  // ???

int k;

unordered_map<int, ll> ma;

inline int two2one(int n, int m) 
    return n * 100000 + m;


inline void one2two(int a, int& n, int& m) 
    m = a % 100000;
    n = a / 100000;


ll getAns(int n, int m)   // n个试管里放m体积
    if (m == 0) 
        return 1;
    
    if (n == 0) 
        return 0;
    
    int a = two2one(n, m);
    if (ma.count(a)) 
        return ma[a];
    
    ll ans = 0;
    for (int thisV = 0; thisV <= k && thisV <= m; thisV++) 
        ans = (ans + getAns(n - 1, m - thisV)) % MOD;
    
    return ma[a] = ans;


int main() 
    cin >> k;
    int N;
    cin >> N;
    while (N--) 
        int n, m;
        scanf("%d%d",&n, &m);
        printf("%lld\\n", getAns(n, m));
    
    return 0;

接下来言归正传,使用生成函数正确解决此题

一个试管最多装 k k k体积的液体( x k x^k xk),最少装 0 0 0体积的液体( x 0 = 1 x^0 = 1 x0=1),因此一个试管可以表示为: 1 + x + x 2 + ⋯ + x k 1 + x + x^2 + \\cdots + x^k 1+x+x2++xk

所以 m m m个试管可表示为: F ( x ) = ( 1 + x + x 2 + ⋯ + x k ) m F(x) = (1 + x + x^2 + \\cdots + x^k) ^ m F(x)=(1+x+x2++xk)m

因为一共要装 n n n体积的液体,所以 F ( x ) F(x) F(x) x n x^n xn的系数即为答案。

接下来求 x n x^n xn的系数:

F ( x ) = ( 1 + x + x 2 + ⋯ + x k ) m            = ( 1 − x k + 1 1 − x ) m            = ( 1 − x k + 1 ) m ( 1 − x ) − m            = ∑ r = 0 m C m r ( − 1 ) r x ( k + 1 ) r ∑ s = 0 + ∞ C m + s − 1 s x s F(x) = (1 + x + x^2 + \\cdots + x^k) ^ m\\\\\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ = (\\frac1 - x^k + 11 - x)^m\\\\\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ = (1 - x^k + 1)^m(1-x)^-m\\\\\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ = \\sum_r=0^mC_m^r(-1)^rx^(k+1)r\\sum_s=0^+\\infinC_m+s-1^sx^s F(x)=(1+x+x2++xk)m          =(1x1xk+1)m          =(1xk+1)m(1x)m          =r=0mCmr(1)rx(k+1)rs=0+Cm+s1sxs

所以 x n x^n xn的系数为:

∑ r = 0 ⌊ n k

以上是关于码蹄集 - MT3143 - 试管装液的主要内容,如果未能解决你的问题,请参考以下文章

码蹄集 - MT3111· 赋值

算法竞赛入门码蹄集新手村600题(MT1501-1550)

算法竞赛入门码蹄集新手村600题(MT1401-1450)

算法竞赛入门码蹄集新手村600题(MT1201-1250)

算法竞赛入门码蹄集新手村600题(MT1551-1600)

算法竞赛入门码蹄集新手村600题(MT1351-1400)