Dilworth定理及其在算法题中的应用

1. Dilworth定理

Dilworth定理由数学家Robert P. Dilworth于1950年提出,它描述了偏序集中链和反链之间的关系。

  • 偏序集:一个满足某种偏序关系集合(即一个自反、反对称、传递的关系)。
  • :偏序集的一个子集,其中任意两个元素都是可比较的(即全序子集)。
  • 反链:偏序集的一个子集,其中任意两个元素都是不可比较的。

Dilworth定理:在任意有限偏序集中,其最小链划分的大小(即将偏序集分解成链的最小数量)等于其最大反链的大小(即反链中元素的最大数量)。

例子:考虑一个序列 [3, 1, 2],其中偏序关系基于元素值和位置(例如,元素值越小越“小”,但位置也影响)。Dilworth定理可以帮助我们找到最小链分解。

2. 序列分解定理

序列分解定理是Dilworth定理在序列上的一个特例。它关注的是序列能否被分解为k个非递减子序列。

  • 非递减子序列:一个子序列其中每个元素都不小于前一个元素(即单调非递减)。
  • 分解:将原序列的每个元素分配到k个子序列中,使得每个子序列都是非递减的。

序列分解定理:一个序列可以被分解为k个非递减子序列 当且仅当 序列中最长递减子序列的长度不超过k。

这意味着:

  • 如果序列中存在一个长度为k+1的递减子序列,那么它无法被分解成k个非递减子序列。
  • 反之,如果最长递减子序列的长度为k,那么序列可以被分解成k个非递减子序列。

Dilworth定理为这个结论提供了严格的数学基础:在序列构成的偏序集(基于值和位置)中,反链对应递减子序列,链对应非递减子序列。

3. 定理在算法题中的应用 (CF-2143-D1)

题目链接:[https://codeforces.com/contest/2143/problem/D1]
这个题需要统计所有“好”子序列(即可以被分解为两个非递减子序列的子序列)。根据序列分解定理,这等价于统计所有最长递减子序列长度不超过2的子序列。这可以用dp快速解决:记dp[i][j](其中 ij 表示两个非递减链的最后一个元素值,且 i<=j)为子序列的数量。对于每个新元素 v,我们检查是否能将其添加到第一个链(如果 v>=i)或第二个链(如果 v>=j),并更新状态,最后统计即可。

直观上,如果序列中有三个元素递减(如 a > b > c 且顺序出现),那么这三个元素无法被分配到两个非递减链中(因为每个链必须非递减,但递减关系要求它们分属不同链,但只有两个链,矛盾)。反之,如果没有这样的三个元素,那么总可以通过贪心将序列分解为两个非递减链。

AC代码:

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

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);

    auto solve = [&](){
        int n;
        cin >> n;
        vector<int> a(n);
        for (int i=0; i<n; i++) cin >> a[i];
        
        vector<vector<int>> dp(n+1, vector<int>(n+1, 0));
        dp[0][0] = 1;  // 空序列
        for (auto v: a){
            vector<vector<int>> cpydp = dp;
            for(int i=0; i<=n; i++){
                for(int j=0; j<=n; j++){
                    if(dp[i][j] == 0) 
                        continue;
                    if(v >= i)
                        cpydp[v][j] = (cpydp[v][j]+dp[i][j])%mod;
                    else if(v >= j)
                        cpydp[i][v] = (cpydp[i][v]+dp[i][j])%mod;
                }
            }
            dp = cpydp;
        }

        int ans = 0;
        for(int i=0; i<=n; i++)
            for(int j=0; j<=n; j++)
                ans = (ans+dp[i][j])%mod;
        cout << ans << "\n";
    };

    int T;
    for(cin>>T; T--; ) 
        solve();

    return 0;
}

对于大数据(\(n=2000\)),可以用树状数组优化到 \(O(n^2logn)\)
题目链接:[https://codeforces.com/contest/2143/problem/D2]

AC代码:

/*
对于 dp[v][j]:需要所有 i<=v 的 dp[i][j] 的和
对于 dp[i][v]:需要所有 j<=v 的 dp[i][j] 的和

考虑用树状数组预处理:
列方向前缀和:对于固定的 j,求 i 从 0 到 v 的 dp[i][j] 的和 col[j]
行方向前缀和:对于固定的 i,求 j 从 0 到 v 的 dp[i][j] 的和 row[i]
*/

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

#define lowbit(i) i & (-i)
struct Fenwick{
    int n;
    vector<int> tree;
    Fenwick(int sz): n(sz), tree(sz+1, 0) {}
    
    void upd(int pos, int x){
        for(int i=pos; i<=n; i+=lowbit(i))
            tree[i] = (tree[i]+x)%mod;
    }

    int summ(int pos){
        int res = 0;
        for(int i=pos; i>0; i-=lowbit(i))
            res = (res+tree[i])%mod;
        return res;
    }
};


signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    auto solve = [&](){
        int n;
        cin >> n;
        vector<int> a(n);
        for(int i=0; i<n; i++) cin >> a[i];

        vector<vector<int>> dp(n+1, vector<int>(n+1, 0));
        dp[0][0] = 1;

        vector<Fenwick> row(n+1, Fenwick(n+1)); // 行方向前缀和
        vector<Fenwick> col(n+1, Fenwick(n+1)); // 列方向前缀和

        // 初始dp[0][0]=1, 由于Fenwick从1开始,upd的时候都要+1
        row[0].upd(1, 1);
        col[0].upd(1, 1);

        for(auto v: a){
            vector<int> R(n+1), C(n+1);
            // 对于列j,求行0到v的和
            for(int j=0; j<=n; j++)
                C[j] = col[j].summ(v+1);
            // 对于行i,求列0到v的和
            for(int i=v+1; i<=n; i++)
                R[i] = row[i].summ(v+1);
                
            for(int j=0; j<=n; j++){
                if(C[j] == 0) continue;
                dp[v][j] = (dp[v][j]+C[j])%mod;

                // 更新第v行,第j列
                row[v].upd(j+1, C[j]); 
                col[j].upd(v+1, C[j]);
            }

            for(int i=v+1; i<=n; i++){
                if(R[i] == 0) continue;
                dp[i][v] = (dp[i][v]+R[i])%mod;

                // 更新第i行,第v列
                row[i].upd(v+1, R[i]);
                col[v].upd(i+1, R[i]);
            }
        }

        int ans = 0;
        for(int i=0; i<=n; i++)
            for(int j=0; j<=n; j++)
                ans = (ans+dp[i][j])%mod;
        cout << ans << "\n";
    };

    int t;
    for(cin>>t; t--;)
        solve();
    return 0;
}
posted @ 2025-09-19 18:15  Unalome  阅读(51)  评论(0)    收藏  举报