3月13日测试

第一题

题目简述:

如果你有一个长度为n的序列:
\(a_1\) \(a_2\) \(a_3\)...\(a_n\)
那么它的一个逆序对是一个二元组:< i , j >满足i<j且 \(a_i\)>\(a_j\),其中 i,j\(\in\)[1,n]。
我们称一个序列所包含的逆序对的个数为这个序列的逆序对数
那么问题来了:
我给出一个长度为n 的序列,需要你计算:
\(a_1\) \(a_2\)...\(a_{n-1}\) \(a_n\)
\(a_2\) \(a_3\)...\(a_n\) \(a_1\)
\(a_3\) \(a_4\)...\(a_1\) \(a_2\)
......
\(a_n\) \(a_1\)...\(a_{n-2}\) \(a_{n-1}\)
这n个序列的逆序对之和。
输入:
第1行1个整数:n,表示给定序列的长度。
第2行n个整数:\(a_1\) \(a_2\)...\(a_{n-1}\) \(a_n\),表示初始序列。
输出:
输出n个序列的逆序对的和。
数据范围:
对于30% 的数据,1\(\leq\)n$\leq\(300 对于60% 的数据,1\)\leq\(n\)\leq\(5000 对于100% 的数据,1\)\leq\(n\)\leq$$10^6\(,1\)\leq\(ai\)\leq$n

题解

首先我们来思考一下暴力
普通逆序对时间复杂度O(\(n^2\))有n-1行 可得O(\(n^3\))算法

for(int i=1;i<n;i++){
    for(int j=i;j<=i+n;j++){
	for(int k=i;k<j;k++){
	    if(a[j]<a[i]){
	        ans++;
	    }
	}
}

喜提30分我们肯定不能目标这么低
那么我们可以想想有没有逆序对 时间复杂度为O(\(nlogn\))的做法
首先我没要知道归并排序

会的话跳过

void msort(int l,int r)
{
	if(l==r)return;
	int m=(l+r)>>1;
	msort(l,m);
	msort(m+1,r);
	int k=l,i=l,j=m+1;
	while(i<=m&&j<=r)
	{
	    if(a[i]<=a[j])
	    b[k++]=a[i++];
	    else
	    b[k++]=a[j++];
	}
	while(i<=m)b[k++]=a[i++];
	while(j<=r)b[k++]=a[j++];
	for(int s=l;s<=r;s++)//这里不能用i
	a[s]=b[s];
}

将归并加上一句tot+=m+1-i就可以了

while(i<=m&&j<=r)
{
	if(a[i]<=a[j])
	b[k++]=a[i++];
	else
	{b[k++]=a[j++];tot+=m+1-i;}
}

这样的话就能得到60分的好成绩
但我们不能止步于此
很明显我们发现外层的n是没法优化掉的
再由数据范围\(10^6\)得知内部循环只能为O(\(\sqrt{n}\))或O(\(\lg{n}\))级别的
但好像没有什么方法可以优化了...
我们还有一点没有用,那就是上下的关系
数字由\(a_1\) \(a_2\)...\(a_{n-1}\) \(a_n\)变为\(a_2\) \(a_3\)...\(a_n\) \(a_1\)
本质上就是把\(a_1\)放到后面去了
设最开始由ans个逆序对
那么会影响从\(a_2\) \(a_3\)...\(a_{n-1}\) \(a_n\)中比\(a_1\)小的减去 大的加上
但好像这也只优化到了O(n);
这时候我们打的归并排序就起作用了,它已经帮我们排好序了
就可以使用lower_bound和upper_bound了

#include<bits/stdc++.h>
#define re register
using namespace std;
const int MX=1e6+5;
int a[MX],n,b[MX],tp[MX];
long long ans;
int rd(){
	re int xx=0;
	re char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9'){
		xx=xx*10+ch-'0';
		ch=getchar();
	}
	return xx;
}//快读
void msort(int l,int r){
	if(l==r)return;
	int mid=(l+r)>>1;
	msort(l,mid);
	msort(mid+1,r);
	re int i=l,j=mid+1,k=l;
	while(i<=mid&&j<=r){
		if(a[i]<=a[j])tp[k++]=a[i++];
		else
		{tp[k++]=a[j++],ans+=mid-i+1;}
	}
	while(i<=mid)tp[k++]=a[i++];
	while(j<=r)tp[k++]=a[j++];	
	for(re int s=l;s<=r;s++)a[s]=tp[s];
}
int main(){
	n=rd();re long long tot=0;
	for(re int i=1;i<=n;i++)
	{a[i]=rd();b[i]=a[i];}
	msort(1,n);tot=ans;
	for(re int i=1;i<n;i++){
		re int op=lower_bound(a+1,a+n+1,b[i])-a-1;
		ans-=op;
		re int ol=upper_bound(a+op+1,a+n+1,b[i])-a-1;//这里可以直接枚举效果差不多
		ans+=n-ol;
		tot+=ans;
	}
	printf("%lld",tot);
	return 0;
} 

第二题

题目简述:

你有一堆柱子,它们竖直地并排摆放在桌子上,它们的高度分别是:
\(h_1\) \(h_2\) \(h_3\)...\(h_n\)
你从前往后看,能够看见的柱子个数为这个柱子序列的“可见度”(能够看见柱子i当且仅当\(h_j\)<\(h_i\)&&j<i)
现在给你一个长度为n 的序列,还有m 个询问,每次询问某个区间[l,r] 的柱子单独拿出来后,其可见度是多大。
输入:
第1行 2个整数:n m,表示给出的柱子序列的长度和询问数。
第2行 n个整数:\(a_1\) \(a_2\) \(a_3\)...\(a_n\),表示每根柱子对应的高度。
接下来m行,每行2个整数:l,r,表示对区间[l,r]进行询问。
输出:
对于每个询问,输出答案。
数据范围:
对于30%的数据1\(\leq\)n,m\(\leq\)\(10^3\)
对于100%的数据1\(\leq\)n,m\(\leq\)\(10^5\),1\(\leq\)\(a_i\)\(\leq\)n,1\(\leq\)l\(\leq\)r\(\leq\)n

题解

一样的暴力先行

#include<bits/stdc++.h>
#define re register
using namespace std;
int n,m,h[100005];
int rd(){
	re int xx=0;
	re char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9'){
		xx=xx*10+ch-'0';
		ch=getchar();
	}
	return xx;
}
int main(){
	n=rd(),m=rd();
	for(re int i=1;i<=n;i++)
	h[i]=rd();
	for(re int i=1;i<=m;i++)
	{
		re int l,r,ans=1,mx;
		l=rd(),r=rd();mx=h[l];
		for(re int j=l+1;j<=r;j++)
		if(h[j]>mx)
		{ans++;mx=h[j];}
		printf("%d\n",ans);
	}
	return 0;
} 

我本来打算只过3个点的,没想到数据不行,我过了7个点
很明显极限数据就会超时

posted @ 2021-07-12 14:19  指挥的智慧  阅读(46)  评论(0)    收藏  举报