【ACM演算法競賽日常訓練】DAY4題解與分析【樹】【子序列】| 組合數學 | 動態規劃

Eriktse0發表於2023-03-27

DAY4共2題:

  • 樹(組合數學)

  • 子序列(dp,數學)

? 作者:Eriktse
? 簡介:19歲,211計算機在讀,現役ACM銀牌選手?力爭以通俗易懂的方式講解演演算法!❤️歡迎關注我,一起交流C++/Python演演算法。(優質好文持續更新中……)?
? 原文連結(閱讀原文獲得更好閱讀體驗):https://www.eriktse.com/algorithm/1095.html

題目傳送門:https://ac.nowcoder.com/acm/problem/13611

透過觀察條件“一個染色方案是合法的,當且僅當對於所有相同顏色的點對(x,y),x到y的路徑上的所有點的顏色都要與x和y相同。”我們可以發現,當且僅當染色的點可以全部連通時可以滿足條件。

所以現在問題是如何將n個點劃分為k塊。

我們可以發現在樹上,任意刪除一條邊都會使得聯通塊個數 + 1

其實塊數只要<= k即可,因為我們可以有一些顏色不使用。所以要劃分為i塊,只需要從n - 1條邊中任選i - 1條進行刪除即可,方案數是C(n - 1, i - 1)

假設現在我們得到了i (i <= k)個聯通塊,需要將i種顏色染上去,首先需要C(k, i)種方法取出顏色,然後A(i, i)一個全排列將顏色染上去。

所以答案公式如下:

$$ans=\sum_{i=1}^{k}C(n - 1, i - 1)C(k, i)i!$$

可能涉及一些快速冪乘法逆元的知識,需要自行學習。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 350, p = 1e9 + 7;

int fac[maxn];

int qmi(int a, int b)
{
    int res = 1;
    while(b)
    {
        if(b & 1)res = res * a % p;
        a = a * a % p, b >>= 1;
    }
    return res;
}

int inv(int x){return qmi(x, p - 2);}

int C(int n, int m)
{
    if(n < m || n < 0 || m < 0)return 0;
    return fac[n] * inv(fac[n - m] * fac[m] % p) % p;
}

signed main()
{
    int n, k;scanf("%lld %lld", &n, &k);
    fac[0] = 1;
    for(int i = 1;i <= n; ++ i)fac[i] = fac[i - 1] * i % p;
    
    int ans = 0;
    for(int i = 1;i <= n; ++ i)//分為i塊
    {
        int tmp = C(n - 1, i - 1) * C(k, i) % p * fac[i] % p;
        ans = (ans + tmp) % p;
    }
    printf("%lld\n", ans);
    return 0;
}

子序列

題目傳送門:https://ac.nowcoder.com/acm/problem/17065

小技巧:觀察資料範圍,比較小,應該可以容納O(n^3)的複雜度,所以可以大膽考慮dp。

首先定義狀態dp[i][j]表示以第i個元素結尾,且長度為j的序列的個數

再考慮一下轉移,題目中的條件可以進行一些轉換:

$${a_{p_i}}^{p_j} < {a_{p_j}}^{p_i}$$

等價於:

$$ \frac{log(a_{p_i})}{p_i} < \frac{log(a_{p_j})}{p_j} $$

我們可以記:

$$ b_i = \frac{log(a_{p_i})}{p_i} $$

也就是說對於選出的子序列中的每一個元素,他們滿足一個偏序關係,只要我的b[j] > b[i],那麼b[j]將會大於所有的b[k] (k < i)

所以我們可以考慮以下的轉移:

$$dp_{i, j} = \sum_{k=1}^{i - 1}[b_i > b_k] \times dp_{k, j - 1}$$

考慮初始化,當最後一個元素確定,序列長度為1(j = 1)時,方案僅有1種。

最後的答案是將所有情況加起來(注意取模,不過這道題資料較弱,不取模也可以過)。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 109, p = 1e9 + 7;

//dp[i][j]表示以第i個元素結尾,長度為j的方案數
int a[maxn], dp[maxn][maxn];


signed main()
{
	int n;scanf("%lld", &n);
	for(int i = 1;i <= n; ++ i)scanf("%lld", a + i);
	
	for(int i = 1;i <= n; ++ i)
    {
        dp[i][1] = 1;
        for(int j = 1;j <= i; ++ j)
        {
            for(int k = 1; k < i; ++ k)
            {
                if(log(a[k]) / k < log(a[i]) / i)
                {
                    dp[i][j] += dp[k][j - 1];
                    dp[i][j] %= p;
                }
            }
        }
    }

	int ans = 0;
	for(int i = 1;i <= n; ++ i)
		for(int j = 1;j <= i; ++ j)
        {
			ans = (ans + dp[i][j]) % p;
        }
	printf("%lld\n", ans);
	return 0;
}

? 本文由eriktse原創,創作不易,如果對您有幫助,歡迎小夥伴們點贊?、收藏⭐、留言?

相關文章