1. 程式人生 > 其它 >3月13日測試

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個點
很明顯極限資料就會超時