gym101964 I.Inversion(拓扑排序,思维,dp)

题意:

在这里插入图片描述

解法:
给定的图可以还原每个节点之间的大小关系,
那么根据图可以还原出原排列:
令大的位置指向小的位置,拓扑排序记录时间戳,时间戳小的放大值即可.
这题也可以直接O(n^2)根据大小关系依次暴力找出n,n-1,n-2...

有了原排列,考虑如何计算答案.
如果第一个位置选择了i,那么我们之后只能选择>a[i]的数,
因为我们如果选择了<a[i]的数,会和a[i]组成逆序对产生边,就不是独立集了.
发现我们选出的数一定是递增的.

同时[1,i-1]不选,因此a[1,i-1]中的所有值都必须>a[i],
因为不选择的点必须和选择的点之间有边,
我们选择的数是递增的,a[i]最小,因此与a[i]产生逆序对最容易.

同理,假设我们第二个数选择了a[j],i<j,
设i<k<j,k是没选的数,
那么必须满足a[k]<a[i]或者a[k]>a[j],这样才能产生逆序对,
即不能存在a[k](a[i],a[j])范围内.

与第一个数相同,选择的最后一个位置p需要满足:
p后面的数都比p小,因为这样才能产生逆序对,与上面同理.

这样就能dp了,
令d[i]为前i个数中,以i为结尾的合法序列方案数.
初始化:
d[t]=1,其中t满足t左边的所有数都大于a[t],即可以作为第一个数.
dp转移:
d[i]+=d[j],其中j<i,a[j]<a[i],且a[j+1,i-1]不在(a[j],a[i])范围内.
判断a[j+1,i-1]是否在(a[j],a[i])范围内可以直接for循环暴力.
累加答案:
对于可以作为结尾的位置p,ans+=d[p],
p需要满足p右边的所有数都<a[p].
code:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxm=111+5;
vector<int>g[maxm];
int dfn[maxm];
int idx[maxm];
int deg[maxm];
int a[maxm];
int n,m;
bool cmp(int i,int j){
    return dfn[i]>dfn[j];
}
void topo(){
    queue<int>q;
    for(int i=1;i<=n;i++){
        if(!deg[i]){
            q.push(i);
        }
    }
    int num=0;
    while(q.size()){
        int x=q.front();q.pop();
        dfn[x]=++num;
        for(int v:g[x]){
            if(deg[v]){
                deg[v]--;
                if(!deg[v]){
                    q.push(v);
                }
            }
        }
    }
    for(int i=1;i<=n;i++){
        idx[i]=i;
    }
    sort(idx+1,idx+1+n,cmp);
    for(int i=1;i<=n;i++){
        int x=idx[i];
        a[x]=i;
    }
}
void add(int i,int j){
    g[i].push_back(j);
    deg[j]++;
}
int mp[maxm][maxm];
int r[maxm];
int f[maxm];
int cnt[maxm];
void solve(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int x,y;cin>>x>>y;
        if(x>y)swap(x,y);
        mp[x][y]=1;
    }
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            if(mp[i][j]){
                add(i,j);
            }else{
                add(j,i);
            }
        }
    }
    topo();
    r[n]=a[n];
    for(int i=n-1;i>=1;i--){
        r[i]=max(a[i],r[i+1]);
    }
    int ans=0;
    for(int i=1,mi=1e9;i<=n;i++){
        if(mi>a[i]){
            cnt[i]=1;
        }
        mi=min(mi,a[i]);
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<i;j++){
            if(a[j]>a[i])continue;
            int ok=1;
            for(int k=j+1;k<=i-1;k++){
                if(a[k]>a[j]&&a[k]<a[i]){//不能有(a[j],a[i])范围内的数
                    ok=0;
                }
            }
            if(ok)cnt[i]+=cnt[j];
        }
    }
    for(int j=1;j<=n;j++){
        if(j==n||r[j+1]<a[j]){
            ans+=cnt[j];
        }
    }
    cout<<ans<<endl;
}
signed main() {
    #ifndef ONLINE_JUDGE
    freopen("../in.txt","r",stdin);
    freopen("../out.txt","w",stdout);
    #endif
    solve();
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值