【安徽集训】fiend

  • 考试的时候只会 \(O(Tn^3)\) 的做法,其它题还都不会,想到一整场就打这么点是人都能写的暴力没啥意思,就懒得写了。。

Description

  双人博弈。每一轮 A 和 B 同时选择一个 \(1\text{~} n\) 的排列 \(P_i\),必须满足 \(L_i\le P_i\le R_i\)。同时,A 选择的排列的逆序对数必须是偶数,B 选择的排列的逆序对数必须是奇数(显然 A 的排列和 B 的排列不可能相同)。每一轮选择的排列不能和之前出现过的排列相同。先无法选择排列的输,若双方同时无法选择排列则和局。
  \(T\) 组数据,每次给定 \(n,L_i,R_i\),求游戏的结果。

Solution

  就是让你比逆序对数是奇数的排列多 还是是偶数的排列多。

  前置知识:行列式
  看到逆序对计数,我们很容易想到行列式。
  因为行列式的公式是 $$det(a) = \sum\limits_{p_{1\cdots n}} (-1)^{\sigma(p)} \prod\limits_{i=1}^n a_{i,p_i}$$   其中 \(p\)\(1\)\(n\) 的任意排列,\(\sigma(p)\) 表示排列 \(p\) 的逆序对数。
  这个公式本身就和逆序对数有关系。
  其中排列 \(p\) 等价于本题中 A 和 B 选择的排列。
  然后关键就是后面那个 \(\prod\limits_{i=1}^n a_{i,p_i}\) 的意义。
  显然,只要有一个 \(a_{i,p_i}\)\(0\),这个式子就为 \(0\)
  那么不难想到,对于一组限制 \(L_i\le P_i\le R_i\),可以将矩阵的第 \(i\) 行的第 \(L_i\)\(R_i\) 位设为 \(1\),其余位设为 \(0\)
  这样 \(\prod\limits_{i=1}^n a_{i,p_i} = 1\) 当且仅当排列 \(p\) 中的每一个数 \(p_i\) 都满足限制 \(L_i\le p_i\le R_i\)。其余情况下 \(\prod\limits_{i=1}^n a_{i,p_i} = 0\),表示不满足限制。
  现在只有所有满足限制的排列 \(p\) 才会被计算一次,下面考虑 \(\sum\limits_{p_{1\cdots n}} (-1)^{\sigma(p)}\)
  这就是把逆序对数为奇数的排列 \(p\) 的权值记为 \(-1\),逆序对数为偶数的排列 \(p\) 的权值记为 \(1\)
  所以最后算出来的行列式的值 \(det(a) = 满足条件的逆序对数为偶数的排列数 - 满足条件的逆序对数为奇数的排列数\)
  直接判断 \(det(a)\)\(\gt 0\)\(\lt 0\) 还是 \(=0\) 就好了。

  那行列式 \(det(a)\) 的值怎么算?
  最暴力的是 \(O(n^3)\) 的高斯消元做法,即把行列式高消成上三角,对角线上所有数的乘积 就是行列式的值。
  因为有 \(100\) 组数据,这个做法只能得 \(35\) 分,但在高消时判断若 \(a_{i,i}=0\) 则直接返回行列式值为 \(0\),可以卡到 \(70\) 分。

  观察到矩阵中所有数都是 \(0,1\),而且每行只有一个连续段是 \(1\),其余位置都是 \(0\)。那么可以优化高消过程。
  依然从小到大枚举 \(x\),找到左端点为 \(x\) 的右端点最小的区间,设其右端点为 \(y\),则模拟高消过程,其余左端点为 \(x\) 的区间的左端点都会被挪到 \(y+1\)
  把所有全 \(1\) 连续段按左端点分类,那么这一轮高消就是把左端点为 \(x\) 的集合 合并到左端点为 \(y+1\) 的集合。
  同时我们还要支持查询一个集合中的最小值(集合中存储所有连续段的右端点)。
  左偏树即可。
  复杂度 \(O(Tn\log n)\)

#include<bits/stdc++.h>
#define N 100002
using namespace std;
inline int read(){
    int x=0; bool f=1; char c=getchar();
    for(;!isdigit(c); c=getchar()) if(c=='-') f=0;
    for(; isdigit(c); c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
    if(f) return x;
    return 0-x;
}
int n,l[N],r[N],siz,rt[N],ch[N][2],dis[N],lst[N];
bool vis[N];
int merge(int x, int y){
    if(!x || !y) return x|y;
    if(r[x]>r[y]) swap(x,y);
    ch[x][1] = merge(ch[x][1],y);
    if(dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][0],ch[x][1]);
    dis[x] = dis[ch[x][1]]+1;
    return x;
}
inline void ins(int L, int x){
	rt[L] = merge(rt[L], x);
}
inline int popNode(int x){
	return merge(ch[x][0], ch[x][1]);
}
int solve(){
    for(int i=1; i<=n; ++i) rt[i]=ch[i][0]=ch[i][1]=dis[i]=0, vis[i]=0;
    
	for(int i=1; i<=n; ++i) ins(l[i],i);
    int ans=1;
    for(int i=1; i<=n; ++i){
        int x=rt[i];
        if(!x) return 0;
        rt[i] = popNode(x);
        lst[i] = x;
        if(rt[i] && r[rt[i]]==r[x]) return 0;
        rt[r[x]+1] = merge(rt[i], rt[r[x]+1]);
    }
    if(ans==0) return 0;
    
    int tot = n;
    for(int i=1; i<=n; ++i) if(!vis[i]){
    	--tot; int x=i;
    	do{
    		vis[x]=1, x=lst[x];
    	}while(x!=i);
    }
    if(tot&1) ans=-ans;
    return ans;
}
int main(){
    int T=read();
    while(T--){
        n=read();
        for(int i=1; i<=n; ++i) l[i]=read(), r[i]=read();
        int ans=solve();
        if(ans==1) puts("Y");
        else if(ans==-1) puts("F");
        else puts("D");
    }
    return 0;
}
/*
3
1
1 1
2
2 2
1 1
2
1 2
1 2
*/
posted @ 2019-09-29 15:32  大本营  阅读(250)  评论(0编辑  收藏  举报