传送:http://agc001.contest.atcoder.jp/tasks/agc001_f
官方菌:http://agc001.contest.atcoder.jp/data/agc/001/editorial.pdf
题意:给你一个n的排列P,以及一个数字K,你可以进行的操作为:
对于两个满足 |i − j| ≥ K 且 |P i − P j | = 1 的下标 i 与 j,交换 P i 与 P j
问你你可以得到的字典序最小的排列是什么.
woc这题有意思极了[尤其是看了题解以后]
考试的时候用O(n^2)水到了90,不过交到AtCoder上就完全不行了..
问题的转换巧妙无比啊.
1.如果我们令t[a[i]]=i,我们知道t的字典序越小,a的字典序必定也越小(很好理解)那么问题可以转化成:
你有一个排列t,相邻的两个数字t i,t(i+1)当|t i-t(i+1)|>=K的时候,你可以交换这两个数字,我们要让1尽量靠前,在1靠前的条件下2尽量靠前...以此类推,求最优的序列
那么我们可以发现,对于t中的两个数字x,y,如果|x-y|<K,那么这两个数字永远都不能交换,意思就是,x与y的相对位置是不会改变的,x在y左边那么x就永远在y左边.[*]
*[2017-10-11补充][易dalao提问解锁新点]:
我们考虑一个这样的情况,看是否可以交换i,j的值:
ai____aj_____________al_
其中i和j之间的距离 < K , j与l的距离 > K
我们似乎可以先交换j,l位置的值,再交换i,j位置的?
我们这样看看:
i与l可换则:|i-j|>=K && al=ai+1(-1情况相同)
在结合j与l可换则得:
设初始时ai=num,则al=num+1,aj=num-1;
从左到右排布为:num,num-1,num+1;
交换后:num+1,num,num-1;
我们发现num和num-1(原来 < K类关系对相对位置没!有!变!
对于这个问题,我们可以发现两个排列t和R,如果t和R之中这样的(x,y)相对位置是相同的,那么t一定可以和R转换.Because:
我们永远都可以通过交换t中相邻的数字来减少不同.
2.巧妙一步:满足这样的(x,y)相对位置与原排列t相同的,字典序最小的排列.
你有没有发现,这种谁必须在谁前面的东西--拓扑序?
问题继续变化为:
3.你有一个排列t,通过以下方式建立了一个DAG:
|t_i < t_j|<K 且 i < j 的两个点由 t_i 向 t_j连边,求字典序最小的拓扑序.
注意每个这样都加边的话,边的数量可能会达到n^2是会炸的..所以只需要往最近的连边就好,用线段树查找维护就好
字典序最小的拓扑序,只需要吧大根堆换成小根堆就好~
PS:
1.最开始写线段树的查询的时候脑一抽当l==r的时候才return,结果..T成狗...
2.heyeye上课的时候提到了一道很像的题:HNOI菜肴制作.
然而考试前一天还在写一道拓扑序的题目:NOI2010航空管制= =
3.博大精深博大精深博大精深....
4.我真是弱我真是弱我真是弱...
(贴个n^2的吧):
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define maxn 500010
using namespace std;
int n,k;
int a[maxn];
int p[maxn];
void file(){
freopen("permutation.in","r",stdin);
freopen("permutation.out","w",stdout);
}
void solve(int now,int i){
int hh=p[a[now]-1];//printf("%d\n",hh);
if(hh>i)return ;
while((hh-now)>=k && hh && hh<i){
p[a[now]-1]=now;
p[a[now]]=hh;
swap(a[hh],a[now]);
hh=p[a[now]-1];
}
}
int main(){
file();
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
p[a[i]]=i;
}
for(int i=k+1;i<=n;i++){
int no=p[a[i]+1];
while((i-no)>=k && no){
// printf("%d %d\n",a[i],a[no]);
p[a[i]+1]=i;
p[a[i]]=no;
swap(a[no],a[i]);
solve(no,i);
no=p[a[i]+1];
}
}
for(int i=1;i<=n;i++){
printf("%d\n",a[i]);
}
return 0;
}
(接下来是正解):
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<ctime>
#define maxn 500010
using namespace std;
int n,k;
int a[maxn];
int t[maxn];
void file(){
freopen("permutation.in","r",stdin);
freopen("permutation.out","w",stdout);
}
int tr[maxn<<2];
struct node{
int v,nxt;
}e[maxn<<2];
int head[maxn],cnt;
void add(int u,int v){
e[++cnt].v=v;e[cnt].nxt=head[u];head[u]=cnt;
}
int in[maxn];
void pushup(int u){
tr[u]=max(tr[u<<1],tr[u<<1|1]);
}
void insert(int u,int l,int r,int val,int pos){
if(l==r){
tr[u]=val;return ;
}
int mid=(l+r)>>1;
if(pos<=mid)insert(u<<1,l,mid,val,pos);
else insert(u<<1|1,mid+1,r,val,pos);
pushup(u);
}
int query(int u,int l,int r,int ql,int qr){
if(ql<=l && qr>=r)return tr[u];
int mid=(l+r)>>1;
int res=0;
if(ql<=mid)res=max(res,query(u<<1,l,mid,ql,qr));
if(qr>mid)res=max(res,query(u<<1|1,mid+1,r,ql,qr));
return res;
}
priority_queue<int, vector<int>, greater<int> >q;
int ans[maxn];
void gettop(){
for(int i=1;i<=n;i++)if(in[i]==0)q.push(i);
for(int i=1;i<=n;i++){
if(q.empty())return ;
int u=q.top();q.pop();
ans[i]=u;
for(int j=head[u];j;j=e[j].nxt){
int v=e[j].v;
in[v]--;
if(in[v]==0)q.push(v);
}
}
}
inline int read(){
int x=0,f=1;char ch=0;
while(ch<'0' || ch>'9'){
if(ch=='-')f=-1;ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<3)+(x<<1)+ch-'0';ch=getchar();
}
return x*f;
}
int main(){
file();
n=read();k=read();
for(int i=1;i<=n;i++){
a[i]=read();
t[a[i]]=i;
}
// cerr<<clock()/1000<<endl;
for(int i=1;i<=n;i++){//printf("*\n");
int pos=query(1,1,n,t[i]+1,t[i]+k-1);
insert(1,1,n,i,t[i]);
add(t[pos],t[i]);
if(pos!=0 && t[pos]!=0){
in[t[i]]++;
}
}
// cerr<<clock()/1000<<endl;
gettop();
// cerr<<clock()/1000<<endl;
for(int i=1;i<=n;i++){
a[ans[i]]=i;
}
for(int i=1;i<=n;i++)printf("%d\n",a[i]);
// cerr<<clock()/1000<<endl;
return 0;
}
小总结:
1.问题的巧妙转化,把变量转化为不变量再来考虑问题
2.巧妙联想-拓扑序的经典套路
3.利用传递性(技巧)减少边数优化空间
4.关键:把a[i]的字典序问题转化为t[a[i]]的字典序问题!这样原本又要考虑坐标还要考虑权值的转换就只需要重点关注坐标了(因为权值变成相邻)
于是就可以放飞自我了O.O