【NOIP模拟试题10.17】题解

1.暴走的猴子(walk.pas/c/cpp)

【题目描述】

从前有一个森林,森林里生活着一群猴子,这里猴子有个恶趣味——暴走。现在给你这个森林里的树木描述,你能计算出这只猴子在暴走k步后会蹦达到哪里吗(友情提示:由于你上周帮助猎人写程序打死了猴子父亲,所以今天猴子特别不爽,故意暴走了很多很多步来为难你,从而导致了k非常的大,做好心里准备噢~)

【输入数据】

第一行两个数nm表示树木数和询问次数

接下来n行,第i个数一个数ai表示这只猴子当前在第i棵树的话,下一步会走到第ai棵树

接下来m行,每行两个数t,k,询问如果当前猴子在第t棵树,k步之后它会到第几棵树

【输出数据】

m行为每次询问的结果

【样例输入】

3 2                                                   

2

3

2

1 2

2 4

【样例输出】

3

2

【数据范围】

共十个测试点,每个测试点数据规模如下所示

1.n=10^2,m=n,k<=10^2

2.n=10^3,m=n,k<=10^3

3.n=10^4,m=1,k<=10^9

4.n=10^5,m=1,k<=10^9

5.n=10^5,m=1,k<=10^12

6.n=10^5,m=1,k<=10^15

7.n=10^5,m=1,k<=10^18

8.n=10^5,m=n,k<=10^12

9.n=10^5,m=n,k<=10^15

10.n=10^5,m=n,k<=10^18

【时限】

1s

因为任何数都可以分为2的几次方相加(eg:17=2^0+2^3+2^3),所以这道题直接倍增就能优化。

还有几个特别重要的需要注意的地方!代码里都注释了我这里就不再写一次啦,啦啦啦啦啦

欢迎代码君上场▔▽▔

#include<cstdio>//动归加优化 
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=100000+10;
 
int n,m;
int a[maxn];
int f[maxn][100];//f[i][j]从i开始跳2^j步到达的位置 
int t;
long long k;//因为k的值很大,所以要用long long 
int main()
{
    //freopen("walk.in","r",stdin);
    //freopen("walk.out","w",stdout);
    scanf("%d%d",&n,&m);//输入树木数(n)和询问次数(m)
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
		f[i][0]=a[i];;//初始 
    }
    for(int j=1;j<=60;j++)//这里尤其要注意!!j的枚举应该是在外层,因为每移动一次改变的是步数 
    {//另外因为题目数据为10^18大约等于2^60,所以这里取60
    	for(int i=1;i<=n;i++)
    	{
    		f[i][j]=f[f[i][j-1]][j-1];
    	}
    } 
    for(int i=1;i<=m;i++)
    {
        int sum=0;
        scanf("%d%I64d",&t,&k);//注意是用l64d,如果用lld就无法读完数
        while(k>=1)//------------↓↓难点分割线↓↓------------------------------ 
        {
            if(k%2!=0) t=f[t][sum];
            k=k/2;
            sum++;
        }
        printf("%d\n",t);
    }
    return 0;
}


2.划分数列(seq.pas/c/cpp)

【题目描述】

给你一个有n个元素的数列,要求把它划分成k段,使每段元素和的最大值最小

【输入格式】

第一行两个正整数n,k

第二行为此数列ai

【输出格式】

一行一个数,为题目所求答案

【样例输入】

5 2

2 1 3 4 5

【样例输出】

9

【数据规模】

30%数据 n <= 30, k <= 10

100%数据 n <= 100000, k <= n, ai<= 10^9

150%数据 n <= 100000, k <= n, |ai|<= 10^9(附:这50分超越了noip难度,大家可以无视)

【时限】

1s

 记得以前做过一道dp的题和这道题差不多,有点不同的就是。。_(:з」∠)_数据太大了啊。。。

所以我们当然就要对它进行优化啦,这里我就用二分进行了优化。

热烈欢迎代码君└( ̄  ̄└)(┘ ̄  ̄)┘

---------------------------------------------代码君出场的分割线------------------------------------------------------------------------

#include<cstdio>//二分加dp 
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100000+10;
int n,k;
int a[maxn];
int sum;//对每段的和进行二分 
int l,r,mid;//二分:左端点(l),右端点(r),中间的点(mid). 
bool judge(int mid)//一个表判定的函数 
{
	int num;//记录当前总和 //s
	int i=0;//点
	int s=0;//记录划分的段数//num
	 
	while(true)
	{
	    num=0;
	    while(true)
	    {
	    	if(i>=n)//没有需要操作的点了 
	    	{
	    		s++;
	    		break;
	    	}
	    	if(num+a[i+1]<=mid)
   		    {
		    	num+=a[i+1];
	    		i++;
	    	}
	    	else
	    	{
			    s++;
			    break;
     		}
	    }
		if(i>=n) break;
	}
	if(s<=k)return true;//如果不够或刚好分m个部分,返回true 
	else return false;//如果多分了就返回false
}
int main()
{
	freopen("seq.in","r",stdin);
    freopen("seq.out","w",stdout);
	scanf("%d %d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		sum+=a[i];
	}
	l=1;
	r=sum;
	while(l<r)//即还没找到,继续二分
	{
		mid=l+(r-l)/2;//---------------↓↓重点理解分割线↓↓------------------- 
		if(judge(mid)) r=mid;
		else l=mid+1;
	} 
	printf("%d",r);
	return 0;
} 


3.无聊的游戏(boring.pas/c/cpp)

【题目描述】

有一个很无聊的游戏,就是——根据递推公式计算数列

没错,这道题就是这么无聊!

给你数列f(0)=1, f(n)=f(n-1)^2+1 (n > 0)

求出f(n)

(既然题目已经这么无聊了,那就不让大家写高精度了,取个模好了)

【输入格式】

一个整数n

【输出格式】

一行一个整数f(n),结果对1200007取模

【样例输入】

3

【样例输出】

26

【数据规模】

10%数据 1<=n<=10

30%数据 1<=n<=10^6

100%数据 1<=n<=10^9

【时限】

1s

 

 打表加模拟或者找循环节,只是找循环节的话还要再另写一个程序,这道题重点还是要找出其中的循环节,具体过程在代码中。


#include<cstdio>//t3 
#include<cstring>
#include<algorithm>
using namespace std;
const int mod=1200007;
/*
   这道题是用打表和模拟;
   循环节已找出:从803开始,每隔570为一个循环节 
*/
int n;
long long f[1500];//***最开始因为我定义成int 导致WA了很多次啊orz 
int main()
{
	//freopen("boring.in","r",stdin);
    //freopen("boring.out","w",stdout);
	scanf("%d",&n);
	f[0]=1;//这里的初始化很重要 
	for(int i=1;i<=1500;i++)
	{
		f[i]=((f[i-1]*f[i-1])%mod+1)%mod;//因为最后要对mod取余,所以如果每计算一次就取一次余,能够降低复杂度(注意!要双重mod)
		if(i==n)
		{
			printf("%d",f[n]);//此时已求出f[n]所以直接输出
			return 0; 
		}
	}
	n=(n-802)%570;
	if(n==0) n+=570;//如果n恰好为循环节节点 
	n+=802;
	printf("%d",f[n]);
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值