皇家守卫【算法赛】
问题描述
在蓝桥王国的边疆有 NN 座高塔,每座高塔上均有一个士兵,他们被称之为皇家守卫。
每座高塔均有一个高度,第 ii 座高塔的高度为 hihi。对于第 ii 座塔,若其右边存在某座塔 jj,满足:max(hi,hi+1,hi+2,⋯,hj−1)<hjmax(hi,hi+1,hi+2,⋯,hj−1)<hj则称第 jj 座塔为第 ii 座塔 "暸望塔"。
现在王国传来 QQ 个任务,每个任务会给定两个编号 x,yx,y,需要你求解第 xx 座高塔和 第 yy 座高塔的公共 "暸望塔" 数量。
x,yx,y 的公共 "暸望塔" 定义为两者同时拥有的 "暸望塔"。
输入格式
第一行输入两个整数 N,Q(1≤N,Q≤105)N,Q(1≤N,Q≤105) 表示高塔个数和任务数。
第二行输入 NN 个整数 h1,h2,h3,⋯,hn(1≤hi≤109)h1,h2,h3,⋯,hn(1≤hi≤109) 表示高塔的高度。
接下来 QQ 行,每行输入两个整数 x,y(1≤x≤y≤N)x,y(1≤x≤y≤N) 表示一次任务询问。
输出格式
输出 QQ 行,每行一个整数表示答案
输入样例
5 3
1 3 4 5 7
1 3
2 4
3 5
输出样例
2
1
0
说明
对于第一个询问,第 11 座高塔和第 33 座高塔的公共暸望塔有 4,54,5 号塔。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.*;
public class Main {
static int N=100010;
static int a[]=new int[N];
static int next[]=new int[N];//表示右边第一个比我高的高塔的索引
static int len[]=new int[N];//表示 右边严格大于我的高塔的数量
static int log2[]=new int[N];
static int go[][];
static int n;
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws IOException {
StringTokenizer st=new StringTokenizer(br.readLine());
n=Integer.parseInt(st.nextToken());
int q=Integer.parseInt(st.nextToken());
st=new StringTokenizer(br.readLine());
for (int i = 1; i <= n; i++) {
a[i]=Integer.parseInt(st.nextToken());
}
a[n+1]=Integer.MAX_VALUE;
findMax(n);
for (int i = n-1; i >0 ; i--) {//从后往前遍历
if(next[i]!=n+1)len[i]=len[next[i]]+1;//要加判断语句
}
log2[1]=0;
for (int i = 2; i < N; i++) {
log2[i]=log2[i>>1]+1;
}
go=new int[n+2][log2[n]+1];//go(i,j)表示从i出发跳2^j步得到的下标
for (int j = 0; j <= log2[n]; j++) {
for (int i = 1; i <= n; i++) {
if(j==0)go[i][j]=next[i];
else {
go[i][j]=go[go[i][j-1]][j-1];
}
}
}
for (int i = 0; i < q; i++) {
st=new StringTokenizer(br.readLine());
int x=Integer.parseInt(st.nextToken()),y=Integer.parseInt(st.nextToken());
//题目可以理解为求x位置的瞭望塔中索引大于y塔的数量 是x的瞭望塔一定是y的
//我们可以利用倍增思想去遍历塔
int nx=x;
for (int k = log2[n]; k >=0; k--) {//这里一定要从最大步长不断递减
//当前位置能跳的最大幅度,再微调,能走到极限
//先挪一小步,把原本能跳的大步机会直接废掉,走不到最远
int nxt=go[nx][k];
if(nxt<=y && nxt!=0){
nx=nxt;
}
}
bw.write(len[nx]+"\n");
}
br.close();
bw.flush();
bw.close();
}
static void findMax(int n){//维护next
for (int i = 1; i <= n; i++) {
next[i]=n+1;
}
Stack<Integer> stack=new Stack<>();//维护一个单调递减栈
for (int i = n; i >=1; i--) {
int u=a[i];
while(!stack.isEmpty() && a[stack.peek()]<=u)stack.pop();
if(!stack.isEmpty())next[i]=stack.peek();
stack.add(i);
}
}
}
附近最小
问题描述
小蓝有一个序列 a1,a2,...,ana1,a2,...,an。
给定一个正整数 kk,请问对于每一个 11 到 nn 之间的序号 ii,ai−k,ai−k+1,...,ai+kai−k,ai−k+1,...,ai+k 这 2k+12k+1 个数中的最小值是多少?
当某个下标超过 11 到 nn 的范围时,数不存在,求最小值时只取存在的那些值。
输入格式
输入的第一行包含一整数 nn。
第二行包含 nn 个整数,分别表示 a1,a2,...,ana1,a2,...,an。
第三行包含一个整数 kk 。
输出格式
输出一行,包含 nn 个整数,分别表示对于每个序号求得的最小值。
样例输入
5
5 2 7 4 3
1
样例输出
2 2 2 3 3
评测用例规模与约定
对于 30% 的评测用例,1<=n<=1000,1<=ai<=10001<=n<=1000,1<=ai<=1000。
对于 50% 的评测用例,1<=n<=10000,1<=ai<=100001<=n<=10000,1<=ai<=10000。
对于所有评测用例,1<=n<=1000000,1<=ai<=10000001<=n<=1000000,1<=ai<=1000000。
法1(添加数据)
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.*;
public class Main {
static int N=1000010;
static int a[]=new int[N];
static int c[]=new int[2*N];
static int res[]=new int[2*N];//表示右边第一个比我高的高塔的索引
static int n;
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws IOException {
StringTokenizer st=new StringTokenizer(br.readLine());
n=Integer.parseInt(st.nextToken());
st=new StringTokenizer(br.readLine());
for (int i = 1; i <= n; i++) {
a[i]=Integer.parseInt(st.nextToken());
}
st=new StringTokenizer(br.readLine());
int k=Integer.parseInt(st.nextToken());
int ii = 1;
for (; ii <= k; ii++) {
c[ii]=Integer.MAX_VALUE;
}
for (int j=1; j<=n; ii++,j++) {
c[ii]=a[j];
}
for (int j=1; j<= k; j++,ii++) {
c[ii]=Integer.MAX_VALUE;
}
for (int i = 1; i < ii; i++) {
res[i]=Integer.MAX_VALUE;
}
Deque<Integer> deque=new LinkedList<Integer>();//维护一个单调递增的队列
for (int i = 1; i < ii; i++) {
int u=c[i];
//清除过期数据
while(!deque.isEmpty() && i-deque.getFirst()>=2*k+1)deque.pollFirst();
//维护队列
while(!deque.isEmpty() && c[deque.peekLast()]>=u)deque.pollLast();
deque.add(i);
res[i]=deque.peek();
}
for (int i =2*k+1,j=1; j<=n; j++,i++) {//注意开始下标
bw.write(c[res[i]]+" ");
}
br.close();
bw.flush();
bw.close();
}
}
法2(枚举中心点)
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.*;
public class Main {
static int N=1000010;
static int a[]=new int[N];
static int res[]=new int[N];//表示右边第一个比我高的高塔的索引
static int n;
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws IOException {
StringTokenizer st=new StringTokenizer(br.readLine());
n=Integer.parseInt(st.nextToken());
st=new StringTokenizer(br.readLine());
for (int i = 1; i <= n; i++) {
a[i]=Integer.parseInt(st.nextToken());
}
st=new StringTokenizer(br.readLine());
int k=Integer.parseInt(st.nextToken());
Deque<Integer> deque=new LinkedList<Integer>();//维护一个单调递增的队列
int right=0;//当前已经处理过的右边界
for (int i = 1; i <= n; i++) {//枚举每个中心点
int l=Math.max(1, i-k),r=Math.min(n, i+k);//保证合法索引
//清除过期数据
if(!deque.isEmpty() && deque.getFirst()<l)deque.pollFirst();//每次左移一个单位 不用写成循环
//把属于l到r范围的数据添加进去
while(right<r){
right++;
while(!deque.isEmpty() && a[deque.peekLast()]>=a[right])deque.pollLast();
deque.add(right);
}
res[i]=deque.peekFirst();
}
for (int i = 1; i <= n; i++) {
bw.write(a[res[i]]+" ");
}
br.close();
bw.flush();
bw.close();
}
}