[JZOJ5199]Fiend

本文介绍了一种高效算法,用于解决给定限制条件下的排列问题,并通过计算逆序对的奇偶性来确定可能解的数量。算法采用左偏树进行优化,实现了O(nlogn)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

给定 n 个限制,每个形如Li,Ri
你要生成 n 的排列{Pn},满足 i,LiPiRi
请判断生成的排列中逆序对个数为奇数的排列多还是偶数的排列多。
一个测试点 T 组数据。

1T500,1n105,1n1.5×106,1LiRin


题目分析

题目中既有排列又有逆序对个数奇偶性,这启发我们从矩阵行列式入手。
考虑构造一个 n 阶矩阵,对于第i行,我们令 Li Ri 列值为 1 ,其余列值为0。那么我们求出这个矩阵的行列式,显然这个行列式的正负就能体现答案。
但是这题 n 很大,我们不能直接高斯消元。考虑利用矩阵的特殊性质来快速消元。
注意到题目中的矩阵每一行有值的都是一段区间(且值都是1),考虑在消元中有目的地选择行来使这个性质始终成立。
将限制挂在 Li 上,并且记录它的 Ri 以及原本是哪一行( i )。
然后我们从左到右消。假设我们考虑到位置x,我们从挂在 x 上面的所有限制中选择一个Ri值最小的,然后让它作为这一行的数。那么它就会和挂在这里的其它限制进行消元,因为选择的 Ri 是最小的,我们显然只需要把这些其它限制从 x 移动到Ri+1这个位置就好了。至于行列式在此次消元中是否变号,我们只需要看看选择的 i 原本是否就是第x行。
也就是说我们需要支持若干个集合的取最小值以及合并,使用左偏树就好了。
时间复杂度 O(nlogn)


代码实现

#include <algorithm>
#include <iostream>
#include <cassert>
#include <cstdio>
#include <cctype>
#include <queue>

using namespace std;

const int N=100050;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

int idx[N],rel[N],root[N];
int T,n;

namespace leftist_tree
{
    int rank[N],key[N],cid[N];
    int son[N][2];
    queue<int> avl;

    void init()
    {
        for (;!avl.empty();avl.pop());
        for (int i=1;i<=n;++i) root[i]=0,avl.push(i);
    }

    int newnode(int val,int id)
    {
        int ret=avl.front();avl.pop();
        return key[ret]=val,cid[ret]=id,son[ret][0]=son[ret][1]=0,rank[ret]=1,ret;
    }

    void release(int x){avl.push(x);}

    int merge(int x,int y)
    {
        if (!x||!y) return x^y;
        if (key[x]>key[y]) swap(x,y);
        son[x][1]=merge(son[x][1],y);
        if (rank[son[x][0]]<rank[son[x][1]]) swap(son[x][0],son[x][1]);
        rank[x]=rank[son[x][1]]+1;
        return x;
    }

    void push(int &rt,int val,int id){rt=merge(newnode(val,id),rt);}

    void pop(int &rt,int &key_,int &id_){key_=key[rt],id_=cid[rt],release(rt),rt=merge(son[rt][0],son[rt][1]);}
};

int main()
{
    freopen("fiend.in","r",stdin),freopen("fiend.out","w",stdout);
    for (T=read();T--;putchar('\n'))
    {
        n=read(),leftist_tree::init();
        for (int i=1,l,r;i<=n;++i) idx[i]=rel[i]=i,l=read(),r=read(),leftist_tree::push(root[l],r,i);
        int ret=1;
        for (int i=1;ret&&i<=n;++i)
        {
            int key_,id_;
            for (;root[i]&&leftist_tree::key[root[i]]<i;leftist_tree::pop(root[i],key_,id_));
            if (!root[i]) ret=0;
            else
            {
                leftist_tree::pop(root[i],key_,id_);
                int tmp=idx[id_];
                if (tmp!=i) ret*=-1,swap(idx[id_],idx[rel[i]]),swap(rel[i],rel[tmp]);
                if (key_<n) root[key_+1]=leftist_tree::merge(root[i],root[key_+1]);
            }
        }
        /*for (int i=1;i<=n;++i) cerr<<idx[i]<<' ';
        cerr<<endl;
        for (int i=1;i<=n;++i) cerr<<rel[i]<<' ';
        cerr<<endl;
        for (int i=1;i<=n;++i) assert(rel[idx[i]]==i);*/
        if (ret>0) putchar('Y');
        else if (!ret) putchar('D');
        else putchar('F');
    }
    fclose(stdin),fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值