P5871 [SEERC 2018] Inversion 解题报告

题意

定义一个长为 \(n\) 的排列为一个序列 \(p_1, p_2, \dots, p_n\),其中 \([1, n]\) 范围内的整数都恰好在这个序列中出现一次。定义排列中的一个逆序对为一对整数 \((i, j)\),其中 \(i, j \in [1,n]\),且满足 \(i<j, p_i>p_j\)

定义一个逆序对图为一个有 \(n\) 个点的图,图中存在一条 \((i, j)\) 的边当且仅当 \((i,j)\) 是一个逆序对。

定义一个图中的独立集为一个图中点的集合,满足集合中的点两两之间没有边相连。定义一个图中的支配集为一个图中点的集合,满足不在这个集合中的点都与集合中的某个点有边相连。定义一个图中的独立支配集为一个图中点的集合,这个集合既是独立集又是支配集。

给定某一个长为 \(n\) 的排列的逆序对图,请计算出这个图中独立支配集的数量。

\(\ (1 \leq n \leq 100, 0 \leq m \leq \frac{n \times (n-1)}{2})\)

思路

考虑独立集的性质,选出的点集必然是单调递增的,若产生了逆序对就一定会出现连边。

再考虑支配集的性质,一个点若能和选的点集并起来后单调递增,那么这个点和选的点集中的任意一个点都没有连边,不符合题目限制。

所以选出来的点集为最长单调递增序列。

然后动态规划即可。

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 110;
int n, m, a[N], dp[N], cnt[N], f[N], f1[N];
vector<int> ve[N];
signed main()
{
    cin >> n >> m;
    for(int i = 1; i <= m; i ++)
    {
        int u, v;
        cin >> u >> v;
        if(u > v) 
            swap(u, v);
        ve[u].push_back(v);
        f[u] ++;
        f1[v] ++;
    }
    for(int i = 1; i <= n; i ++)
    {
        cnt[i] = i + f[i] - f1[i];
    }
    for(int i = 1; i <= n; i ++)
    {
        dp[i] = 0;
        if(f1[i] == i - 1) dp[i] = 1;
        for(int j = i - 1; j >= 1; j --)
        {
            if(cnt[j] < cnt[i])
            {
                bool ok = true;
                for(int k = j + 1; k < i; k ++)
                {
                    if(cnt[j] < cnt[k] && cnt[k] < cnt[i])
                    {
                        ok = false;
                        break;
                    }
                }
                if(ok)
                    dp[i] += dp[j];
            }
        }
    }
    int ans = 0;
    for(int i = 1; i <= n; i ++)
    {
        if(f[i] == n - i)
            ans += dp[i];
    }
    cout << ans << endl;
}
posted @ 2026-06-14 17:31  Luowj  阅读(5)  评论(0)    收藏  举报