1. 程式人生 > >HDU 6058 2017 Multi-University Training Contest

HDU 6058 2017 Multi-University Training Contest

題意:給定一個n(<=5e5)的全排列,以及一個 K 。定義value(l , r , k)=區間[ l , r ]中第 k 大的元素,如果區間長度 len = r - l + 1 < k 則 value( l , r , k ) = 0 求所有子序列的value( k )之和。

題解:又是一道計數題,那麼這個題一個比較好的思路是:列舉全排列中的每一個 a[ i ],讓他作為value的答案,求出這樣的序列有num個,那麼a[ i ] 貢獻的答案就是a[ i ] * num。num的求法可以分類討論:讓左邊有x個比a[ i ]大的數字,右邊有k-1-x個。那麼我們需要列舉每一個a[ i ]這就有了n的複雜度。那麼我們必須對於給定的a[ i ],快速地求出左右分別最多k個比他大的數字的下標。比他大的數字!那麼我們就排個序吧,從小到大的來計算a[ i ]的貢獻,那麼所有沒考慮的的數字都是比a[ i ]大的。可以用一個類似連結串列的東西維護每一個i位置的pre和nxt位置,這個指標是a[ i ]意義下的,意思是隻要pre[ x ]是x左邊第一個比a[ i ]大的數字的位置,而不是和a[ x ]比較。所以初始化為pre[ i ] = i-1,nxt[ i ] = i+1,因為初始是在0意義下的。考慮完1這個數字之後,要把 nxt[ pre[ 1 ] ] = nxt [ 1 ] 且 pre[ nxt[ i ] ] = pre[ i ],相當於把 i 從雙向連結串列刪除掉。

當然還有對應的從大到小來考慮的思路:每次用插排的方法,把新的值=i 的下標插入到已有的下標序列,那麼對於數字 i-1 來說,這些下標就是比我大的所有的數字的下標,然後可以在這個有序序列中用lower_bound查一下 i-1的下標,從而分出左右,然後相同的分別向兩邊取 k 個,統計答案,但是。。。如果使用vector不知道為什麼常數巨大orz。。。自己造的雙向連結串列應該可以不TLE的。

文末有更多計數問題的題解傳送門。本部落格裡有更多,請自行尋找(因為我不會回頭維護之前的部落格的。orz)

Code:

#include<bits/stdc++.h>
using namespace std;
const int MAX = 500050;
int pre[MAX],nxt[MAX];
int index[MAX];
int n,k,t;
inline int read(){
	char ch = getchar();
	int re = 0;
	while (ch>='0'&&ch<='9'){
		re = re*10+ch-'0';
		ch = getchar();
	}
	return re;
}
inline void input(){
	n = read();
	k = read();
	for (int i = 1;i<=n;i++){
		index[read()] = i;
	}
} 
inline void init(){
	for (int i = 0;i<=n+1;i++){
		pre[i]= i-1;
		nxt[i]= i+1;
	}
//	ans = 0;
}
void erase(int kk){
	int pp = pre[kk];
	int nn = nxt[kk];
	if (pp) nxt[pp] = nn;
	if (nn<n+1) pre[nn] = pp;
	pre[kk]=nxt[kk] = 0;
}
long long ans;
const int MAXK = 85;
int lans[MAXK],rans[MAXK];
void print(){
	cout<<"INFO"<<endl;
	for (int i = 0;i<=k;i++){
		cout<<"L["<<i<<"]="<<lans[i]<<endl;
	}
	for (int i = 0;i<=k;i++){
		cout<<"R["<<i<<"]="<<rans[i]<<endl;
	}
	cout<<"ANS:"<<ans<<endl;
	cout<<"INFO END"<<endl;
} 
inline void work(){
	ans = 0;
	for (int i = 1;i<=n-k+1;i++){
		int l = 0,r = 0;
		int p = index[i];
		memset(lans,0,sizeof(lans));
		memset(rans,0,sizeof(rans));
		for (int ii = p;ii>=0&&l<=k;ii=pre[ii]){
			lans[l++] = ii;
		}
		for (int ii = p;ii<=n+1&&r<=k;ii = nxt[ii]){
			rans[r++]= ii;
		}
		lans[l] = 0;
		rans[r] = n+1;
		for (;l>=1;l--){
			if (k-l+1<=r&&k-l>=0){
				ans += 1LL*i*(lans[l-1]-lans[l])*(rans[k-l+1]-rans[k-l]);
//				cout<<l-1<<" "<<k-l<<" "<<ans<<endl;
			}
		}
//		print();
		erase(p);
	}
	printf("%I64d\n",ans);
}
int main(){
	while (t--){
		input();
//		cout<<"INPUT"<<n<<" "<<k<<endl;
		init();
		work();
	}
	return 0;
}