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!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值