1. 程式人生 > >NOIP 2016模擬賽[南開題]題解&總結

NOIP 2016模擬賽[南開題]題解&總結

這次比賽明明可以發揮的很好的,第二題起碼60分加上去就220分了,可惜開的是longlong的20000000陣列結果空間超了……氣死我鬼大爺了

下次一定一定要觀察空!間!限!制!不要隨便開long long 了!

1. 漢諾塔+

(han.pas/c/cpp)

【問題描述】

    小易最近對漢諾塔感興趣。漢羅塔遊戲裡有三根柱子,n個盤子,盤子可以串到柱子上,但是大盤子不能放到小盤子的上面。每一步可以把一根柱子頂部的盤子移動到另一個柱子的頂部。

    小易想知道如果第i小的圓盤在第a[i]根柱子上,最少需要移動多少步才能把它們全部移到第3根柱子上。

【輸入】

    第一行一個整數n,表示圓盤個數。

    第二行

n個整數a[i],第i小的圓盤在第a[i]根柱子上(a[i]=123)

【輸出】

   只有一行一個整數,表示最少步數。(答案模1000000007

【輸入輸出樣例】

han1.in

han1.out

3

1 1 1

7

han2.in

han2.out

5

3 1 2 2 1

18

【資料範圍】

對於30%資料,1 <= n <= 10

對於100%資料,1 <= n <= 1000000


考慮到最簡單的漢諾塔問題(所有盤子都在1號柱)我們是通過遞迴做的,這道題我們也可以用遞迴做

對於最大的盤子,如果它在第3個柱子上,那麼我們就不動他,繼續討論其他N-1個盤子

如果他在第2個柱子上,我們就需要把它移動到第3個柱子上(1步),這個時候就需要把其他比他小的挪到2號柱子上(2^(n-1)-1步),因此總的步數為2^(n-1)步

對於其他的柱子以此類推,最後判斷邊界就可以了

#include<cstdio>
#include<iostream>
#define LL long long
using namespace std;
const LL maxn=1e6+5,inf=0x3f3f3f3f,mod=1000000007;
inline void _read(LL &x){
    char t=getchar();bool sign=true;
    while(t<'0'||t>'9')
    {if(t=='-')sign=false;t=getchar();}
    for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
    if(!sign)x=-x;
}
LL n,s[maxn];
LL MG(LL a,LL b){
	LL ans=1;
	a%=mod;
	while(b){
		if(b&1)ans=ans*a%mod;
		b>>=1;
		a=a*a%mod;
	}
	return ans;
}
LL Solve(LL a,LL b,LL c,LL N,LL p[]){
	if(N==1)return p[N]!=c;
	if(p[N]==c)return (Solve(a,b,c,N-1,p))%mod;
	if(p[N]==a)return (Solve(a,c,b,N-1,p)+MG(2,N-1))%mod;
	if(p[N]==b)return (Solve(b,c,a,N-1,p)+MG(2,N-1))%mod;
}
int main(){
	_read(n);
	LL i,j;
	for(i=1;i<=n;i++)_read(s[i]);
	cout<<Solve(1,2,3,n,s)%mod;
}

2. 逆序對+

(ni.pas/c/cpp)

對於n個數的序列A1,A2,…,An。

定義數對(Ai,Aj)為逆序對+當且僅當i<j且Ai>2*Aj。

計算逆序對+的個數。

【輸入】

     1行,一個數n,表示序列的長度。

     2行,n個數A1,A2,…,An。

【輸出】

     只有1行1個數,表示逆序對+的個數

【輸出輸出樣例】

ni1.in

ni1.out

5

9 3 5 3 1

6

ni2.in

ni2.out

14

7 19 11 10 2 18 14 5 8 17 9 3 19 16

20

【資料範圍】
對於30%的資料,  1 <= n <= 100

對於100%的資料,1 <= n <= 200000,Ai在int範圍內。


顯然普通的樹狀陣列求逆序對肯定不好過所有的資料(親測60分)

然而我們離散化一下就可以過了……簡直了

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<queue>
#include<vector>
#include<stack>
#define lowbit(xx) (xx&-xx) 
using namespace std;
const int maxn=4e5+5,inf=0x3f3f3f3f;
inline void _read(int &x){
    char t=getchar();bool sign=true;
    while(t<'0'||t>'9')
    {if(t=='-')sign=false;t=getchar();}
    for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
    if(!sign)x=-x;
}
int s[maxn],p[maxn],c[maxn],n,N,ans;
void insert(int x,int d){for(int i=x;i<=N;i+=lowbit(i))c[i]+=d;}
int getsum(int x){
	int sum=0,i;
	for(i=x;i;i-=lowbit(i))sum+=c[i];
	return sum;
}
int main(){
	_read(n);
	int i,j,k;
	for(i=1;i<=n;i++){
		_read(s[i]);
		p[i]=s[i]-1;
		p[i+n]=s[i]<<1;
	}
	N=n<<1;
	sort(p+1,p+1+N);
	N=unique(p+1,p+1+N)-p;
	for(i=n;i;i--){
		int a=lower_bound(p+1,p+1+N,s[i]-1)-p;
		int b=lower_bound(p+1,p+1+N,s[i]<<1)-p;
		ans+=getsum(a);
		insert(b,1);
	}
	cout<<ans;
}

3. 吃貨的煩惱

(chi.pas/c/cpp)

【問題描述】

    某吃貨有n桶泡麵,泡麵有不同口味,用不同小寫字母表示不同口味。

    他一次只能吃其中連續的若干桶,設他吃了k桶。定義美味值為這k桶泡麵出現的口味中出現次數最多的口味的數量減去出現次數最少的口味的數量(對應口味出現次數必須>=1)

    它想知道它吃一次最大的美味值是多少。

【輸入】

    1行,一個整數n,表示有n桶泡麵。

    2行,n個小寫字母,表示泡麵的口味。

【輸出】

    只有11個整數,表示吃一次的最大美味值。

【輸出輸出樣例】

chi1.in

chi1.out

9

abbaaabab

3

chi2.in

chi2.out

13

cccaaccbcbbbc

5

樣例說明

紅色表示被吃掉的一段
樣例1abbaaabab
樣例2cccaaccbcbbbc
【資料範圍】
對於30%的資料,1<=n<=100
對於60%的資料,1<=n<=10000
對於100%的資料,1 <= n <= 1000000


對每一種口味求字首和,sum[i][j]表示到第i位為止第j種口味出現了多少次。

1、O(26*n*n)

列舉吃的區間的左端點l和右端點r,再列舉每一種口味k,得到出現的口味中出現次數最多的口味的數量max{sum[r][k]-sum[l-1][k]}和出現次數最少的口味的數量min{sum[r][k]-sum[l-1][k],ans=max{sum[r][j]-sum[l-1][j]}-min{sum[r][k]-sum[l-1][k]}。

2、O(26*26*n)

假設某兩種口味分別是出現次數最多的口味j和出現次數最少的口味k,列舉它們,然後再列舉吃的區間的右端點r,用min表示!!這兩種口味的泡麵都出現時!!sum[i][j]-sum[i][k](1<=i<=r)的最小值,ans=max{ans,sum[r][j]-sum[r][k]-min}。

3、O(26*n)

列舉吃的區間的右端點r,用min[j][k]表示sum[i][j]-sum[i][k]的最小值(1<=i<=r,0<=j,k<26),Min[j][k]表示sum[i][j]-sum[i][k]的最小值(1<=i<=k最後出現的位置,0<=j,k<26)。

設j為第r位的口味,k為其他25種口味,從第r-1位到第r位只有出現次數最多的口味為j時才會使答案更大,所以ans=max{ans,sum[r][j]-sum[r][k]-Min[j][k]}。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=1e6+5;
int n,ans;
char s[maxn];
int sum[maxn][26],tempmin[26][26],curmin[26][26];
int main(){
	int i,j,k;
	scanf("%d%s",&n,s+1);
	for(i=1;i<=n;i++)
		for (j=0;j<26;j++)
			sum[i][j]=sum[i-1][j]+(j==s[i]-'a');
	for(i=1;i<=n;i++){
		j=s[i]-'a';
		for(k=0;k<26;k++){
			int t=sum[i][j]-sum[i][k];
			if(sum[i][k])ans=max(ans,t-curmin[j][k]);
			curmin[k][j]=tempmin[k][j];
			tempmin[k][j]=min(tempmin[k][j],-t);
		}
	}
	printf("%d",ans);
}

最後再說兩句:

做題的時候一定要計算一下大致消耗的記憶體,不要超記憶體了,不要亂開LL!