[CSP-S模拟测试]:Permutation(线段树+拓扑排序+贪心)

题目描述

你有一个长度为$n$的排列$P$与一个正整数$K$
你可以进行如下操作若干次使得排列的字典序尽量小
对于两个满足$|i−j|\geqslant K$且$|P_i−P_j|=1$的下标$i$与$j$,交换$P_i$与$P_j$


输入格式

第一行包括两个正整数$n$与$K$
第二行包括$n$个正整数,第$i$个正整数表示$P_i$


输出格式

输出一个新排列表示答案
输出共$n$行,第$i$行表示$P_i$


样例

样例输入:

8 3
4 5 7 8 3 1 2 6

样例输出:

1
2
6
7
5
3
4
8


数据范围与提示

对于前$20\%$的数据满足$n\leqslant 6$
对于前$50\%$的数据满足$n\leqslant 2,000$
对于$100\%$的数据满足$n\leqslant 500,000$


题解

这是一道暴力有$90$分的题……

先来考虑如何换,我们每扫到一个位置,发现比它小$1$的在它右边距离大于$K$的位置就交换,不断的扫整个序列,直到无法交换为止,这时候肯定是最优的。

交换不大于$n$次,瓶颈就在于如何快速查询交换的位置。

首先,我们设$pos[i]$表示权值为$i$的数字在哪儿,即先当与权值与下标调换。

那么,我们另$P_i$的字典序最小也就是另$pos[i]$的字典序最小,则操作转化为:相邻元素且权值差$\geqslant K$可以交换。

接着,问题开始抽象化,我们考虑建图……

先来考虑暴力建边,如果$i$与后面的$j$相比,$abs(pos[i]-pos[j])<K$则其顺序已经确定,那么可以相互连边,然后跑拓扑。

但是暴力建边显然无论是时间还是空间都会死掉(还是$90$分……)

那么我们靠有些边是无用的,即如果$A\rightarrow B$且$B\rightarrow C$,那么$A\rightarrow C$这条边就是无用的,但是显然我们现在的策略无法避免,考虑如何处理。

这种情况我们一般都考虑倒着做,因为$pos[i]$连向$(pos[i]-K,pos[i])\cup(pos[i],pos[i]+K)$,但是我们只需要分别连向两个区间内下标最小的那个,用线段树快速查询即可。

时间复杂度:$\Theta(n\log n)$。

期望得分:$100$分。

实际得分:$100$分。


代码时刻

#include<bits/stdc++.h>
#define L(x) x<<1
#define R(x) x<<1|1
using namespace std;
struct rec{int nxt,to;}e[1000001];
int head[500001],cnt;
int n,K;
int tr[2000001];
int pos[500001],du[500001];
int ans[500001];
priority_queue<int,vector<int>,greater<int> > q;
void add(int x,int y)
{
	e[++cnt].nxt=head[x];
	e[cnt].to=y;
	head[x]=cnt;
}
void pushup(int x){tr[x]=min(tr[L(x)],tr[R(x)]);}
void insert(int x,int l,int r,int k,int w)
{
	if(l==r){tr[x]=w;return;}
	int mid=(l+r)>>1;
	if(k<=mid)insert(L(x),l,mid,k,w);
	else insert(R(x),mid+1,r,k,w);
	pushup(x);
}
int ask(int x,int l,int r,int L,int R)
{
	if(r<L||R<l)return 0x3f3f3f3f;
	if(L<=l&&r<=R)return tr[x];
	int mid=(l+r)>>1;
	return min(ask(L(x),l,mid,L,R),ask(R(x),mid+1,r,L,R));
}
int main()
{
	scanf("%d%d",&n,&K);
	memset(tr,0x3f,sizeof(tr));
	for(int i=1;i<=n;i++){int a;scanf("%d",&a);pos[a]=i;}
	for(int i=n;i;i--)
	{
		int x;
		x=ask(1,1,n,pos[i]+1,min(pos[i]+K-1,n));
		if(x!=0x3f3f3f3f){add(pos[i],pos[x]);du[pos[x]]++;}
		x=ask(1,1,n,max(1,pos[i]-K+1),pos[i]-1);
		if(x!=0x3f3f3f3f){add(pos[i],pos[x]);du[pos[x]]++;}
		insert(1,1,n,pos[i],i);
	}
	int now=0;
	for(int i=1;i<=n;i++)
		if(!du[i])q.push(i);
	while(!q.empty())
	{
		int x=q.top();q.pop();
		ans[x]=++now;
		for(int i=head[x];i;i=e[i].nxt)
			if(!(--du[e[i].to]))q.push(e[i].to);
	}
	for(int i=1;i<=n;i++)printf("%d\n",ans[i]);
	return 0;
}


rp++

原文地址:https://www.cnblogs.com/wzc521/p/11629296.html

时间: 2024-08-08 08:46:27

[CSP-S模拟测试]:Permutation(线段树+拓扑排序+贪心)的相关文章

bzoj 3832: [Poi2014]Rally(线段树+拓扑排序)

3832: [Poi2014]Rally Time Limit: 20 Sec  Memory Limit: 128 MBSec  Special Judge Submit: 113  Solved: 56 [Submit][Status][Discuss] Description An annual bicycle rally will soon begin in Byteburg. The bikers of Byteburg are natural long distance cyclis

HDU5638 / BestCoder Round #74 (div.1) 1003 Toposort 线段树+拓扑排序

Toposort 问题描述 给出nn个点mm条边的有向无环图. 要求删掉恰好kk条边使得字典序最小的拓扑序列尽可能小. 输入描述 输入包含多组数据. 第一行有一个整数TT, 表示测试数据组数. 对于每组数据: 第一行包含3个整数nn, mm和kk (1 \le n \le 100000, 0 \le k \le m \le 200000)(1≤n≤100000,0≤k≤m≤200000), 表示图中结点数目, 图中边的数目以及要删的边数. 接下来mm行, 每行包含两个整数u_iu?i?? and

hdu 5195 DZY Loves Topological Sorting 线段树+拓扑排序

DZY Loves Topological Sorting Time Limit: 1 Sec  Memory Limit: 256 MB 题目连接 http://acm.hdu.edu.cn/showproblem.php?pid=5195 Description A topological sort or topological ordering of a directed graph is a linear ordering of its vertices such that for ev

hdu5195 DZY Loves Topological Sorting 线段树+拓扑排序

要求在一个DAG中删去不多于k条边,使得拓扑序的字典序最大. 贪心策略:每次删去入度小于res的,序号尽量大的点的入边. 需要用线段树维护区间最小值. 代码: #include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<algorithm> #include<vector> using name

【bzoj3638】Cf172 k-Maximum Subsequence Sum 模拟费用流+线段树区间合并

题目描述 给一列数,要求支持操作: 1.修改某个数的值 2.读入l,r,k,询问在[l,r]内选不相交的不超过k个子段,最大的和是多少. 输入 The first line contains integer n (1 ≤ n ≤ 105), showing how many numbers the sequence has. The next line contains n integers a1, a2, ..., an (|ai| ≤ 500). The third line contain

BZOJ.3638.CF172 k-Maximum Subsequence Sum(模拟费用流 线段树)

题目链接 各种zz错误..简直要写疯 /* 19604kb 36292ms 朴素线段树:线段树上每个点维护O(k)个信息,区间合并时O(k^2),总O(mk^2logn)->GG 考虑费用流:建一条n+1个点的链(点权设在边上,故需n+1个点),链上每个点和S.T连边,相邻点连边 这样数列中的区间和每条增广路一一对应 每次最多增广k次,O(nmk)->still GG 考虑费用流这一过程的实质:每次增广相当于贪心,本质上只有两种情况: 选取一段(新增一个区间).从已选的某个区间中删除一段 使用

HDU5195 线段树+拓扑

DZY Loves Topological Sorting Problem Description A topological sort or topological ordering of a directed graph is a linear ordering of its vertices such that for every directed edge (u→v) from vertex u to vertex v , u comes before v in the ordering

BNU 51636 Squared Permutation 线段树

Squared Permutation 最近,无聊的过河船同学在玩一种奇怪的名为“小Q的恶作剧”的纸牌游戏. 现在过河船同学手有张牌,分别写着,打乱顺序之后排成一行,位置从左往右按照标号. 接下来小Q同学会给出个操作,分为以下两种: 1.给定,交换从左往右数的第和第张牌, 2.给定,对从左往右数的第张牌,记下位置是这张牌上的数字的牌的数字,询问所有记下的数字加起来的结果. 虽然无聊的过河船同学精通四则运算,但是要完成这么大的计算量还是太辛苦了,希望你能帮他处理这些操作. Input 第一行是一个

hdu5195 二分+线段树+拓扑序

这题说的给了n个点m条边要求保证是一个有向无环图,可以删除至多k条边使得这个图的拓扑序的字典序最大,我们知道如果我们要排一个点的时候一定要考虑比他大的点是否可以.通过拆边马上拆出来,如果可以拆当然是拆,肯定保证字典序最大,如果不能拆,就不拆留着以后拆,当初这个比他大的点度数小于k的,最大是多少,这个方法我一直想不出,后来看了题解,二分加线段树,可以做到,线段树维护每个点的d[i],然后通过二分找出小于k的最大点是多少. 1 #include <iostream> 2 #include <