T1
题解
大概的思路就是对于每个位置的数,如果他前面比他大的数的个数>=k,那么将他向前移动k位,否则扔到一个数组中。最后对这个数组从小到大排序,然后将数组中的数插入序列中的空位置。
为什么这样做是对的,对于前面比他大的数个数大于等于k的数来说,他们是没有机会自己移动的,只可能是他前面比他大的前k个数向后移而使他向前移动。对于小于k的数来说,首先他前面比他大的数会使他向前移动,其次他自己会向后移动。对于剩下位置不能直接计算的数来说一定是有序的,因为他停下来一定是遇到了一个比他大的数或者不能移动,如果他后面有比他小的数那么他一定会向后移动。那么有没有可能x-1,x,x-2,其中x的位置是直接计算的。x-1一定是停在了第k个比他大的数的前面(k包括一开始在他前面比他大的数以及移动过程中遇到的比他大的数),那么上述位置中相当于x-2前面有k+1个数比他大,这种数的位置是可以直接计算的。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 200003
using namespace std;
int ans[N],a[N],b[N],c[N],root[N],n,k,top,st[N],sz,cnt,vis[N];
struct data{
int ls,rs,sum;
}tr[N*60];
void insert(int &i,int l,int r,int x)
{
tr[++sz]=tr[i]; i=sz;
tr[i].sum++;
if (l==r) return;
int mid=(l+r)/2;
if (x<=mid) insert(tr[i].ls,l,mid,x);
else insert(tr[i].rs,mid+1,r,x);
}
int query(int now,int l,int r,int ll,int rr)
{
if (ll>rr) return 0;
if (ll<=l&&r<=rr) return tr[now].sum;
int mid=(l+r)/2; int ans=0;
if (ll<=mid) ans+=query(tr[now].ls,l,mid,ll,rr);
if (rr>mid) ans+=query(tr[now].rs,mid+1,r,ll,rr);
return ans;
}
int main()
{
freopen("fable.in","r",stdin);
freopen("fable.out","w",stdout);
scanf("%d%d",&n,&k);
for (int i=1;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
sort(b+1,b+n+1);
cnt=unique(b+1,b+n+1)-b-1;
for (int i=1;i<=n;i++) c[i]=lower_bound(b+1,b+cnt+1,a[i])-b;
for (int i=1;i<=n;i++)
root[i]=root[i-1],insert(root[i],1,cnt,c[i]);
for (int i=1;i<=n;i++) {
int t=query(root[i-1],1,cnt,c[i]+1,cnt);
if (t>=k) ans[i-k]=a[i],vis[i-k]=1;
else st[++top]=a[i];
}
sort(st+1,st+top+1);
int now=1;
for (int i=1;i<=n;i++)
if (!vis[i]) {
ans[i]=st[now];
now++;
}
for (int i=1;i<=n;i++) printf("%d\n",ans[i]);
}
T2
题解
可并堆。
首先说明一下矩阵的行列式。
其中 s g n(σ)是排列σ的符号差
通俗一点说,就是如果我们从一个n*n的矩阵中选择出n个点,是每行每列都只有一个数别选中,那么选择的方案有n!种,正好对应了1..n的所有排列。对于一个排列如果逆序对数为奇数那么sgn为-1,否则为1.后面的乘积就是选中位置的数的乘积。
对于这道题来说,每个数有一个可以选取的范围
[li,ri]
,如果我们令这些位置的值为1,剩下的位置的值为0,那么求矩阵的行列式其实就可以看出满足
pi∈[li,ri]
是逆序对为奇数的多还是偶数的多,因为1的乘积都是1,所以会相互抵消。
求行列式比较常见的方式就是高斯消元,但是那是
O(n3)
,因为这个矩阵的每行都是一个区间,所以考虑用这个性质优化。
对于每一列只保留r最小的区间,剩下l相同的区间消掉最小的区间后,与r+1列合并。这个过程用可并堆来实现。
消完后,对于每列只有一个区间的端点在上面,因为我们要求第i行的区间的左端点必须是第i列,所以需要进行交换,每交换一次行列式的值乘(-1)。最后如果行列式的值为1,那么输出Y,如果行列式的值为-1,那么输出F。如果某列没有区间那么行列式的值为0,输出D。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 100003
using namespace std;
struct data{
int l,r,h;
}a[N],b[N];
int fa[N],lch[N],rch[N],d[N],root[N],pd[N],n,pos[N],mp[N];
int cmp(data a,data b){
return a.l<b.l;
}
int find(int x)
{
if (fa[x]==x) return x;
fa[x]=find(fa[x]);
return fa[x];
}
int merge(int x,int y)
{
if (!x) return y;
if (!y) return x;
if (a[x].r>a[y].r) swap(x,y);
rch[x]=merge(rch[x],y);
if (d[lch[x]]<d[rch[x]]) swap(lch[x],rch[x]);
d[x]=d[rch[x]]+1;
return x;
}
int main()
{
freopen("fiend.in","r",stdin);
freopen("fiend.out","w",stdout);
int T; scanf("%d",&T);
while (T--) {
scanf("%d",&n);
memset(pd,0,sizeof(pd));
memset(lch,0,sizeof(lch));
memset(rch,0,sizeof(rch));
memset(d,0,sizeof(d));
memset(fa,0,sizeof(fa));
for (int i=1;i<=n;i++){
scanf("%d%d",&a[i].l,&a[i].r);
a[i].h=i; pd[a[i].l]=1;
}
for (int i=1;i<=n;i++) fa[i]=i;
sort(a+1,a+n+1,cmp);
memset(root,0,sizeof(root));
for (int i=1;i<=n;i++) {
int t=0;
if (a[i].l==a[i-1].l) {
int r1=find(i); int r2=find(i-1);
t=merge(r1,r2);
fa[r2]=fa[r1]=t;
}
if (a[i].l!=a[i+1].l||i==n)
if (t) root[a[i].l]=t;
else root[a[i].l]=i;
}
bool mark=false;
for (int i=1;i<=n;i++) {
if (root[i]) b[i]=a[root[i]];
else {
mark=true;
break;
}
int t=merge(lch[root[i]],rch[root[i]]);
fa[lch[root[i]]]=fa[rch[root[i]]]=t;
while (a[t].r==b[i].r)
t=merge(lch[t],rch[t]),fa[lch[t]]=fa[rch[t]]=t;
int t1=merge(root[b[i].r+1],t);
fa[root[b[i].r+1]]=fa[t]=t1;
root[b[i].r+1]=t1;
}
if (mark) {
printf("D\n");
continue;
}
int ans=1;
for (int i=1;i<=n;i++) pos[i]=b[i].h,mp[b[i].h]=i;
for (int i=1;i<=n;i++)
if (pos[i]!=i) {
ans=ans*(-1);
pos[mp[i]]=pos[i];
mp[pos[i]]=mp[i];
}
if (ans==-1) printf("F\n");
else printf("Y\n");
}
}