19级HPU算法协会公开课第二期: 【基础算法2】 题解


比赛链接
比赛密码: HPUACM

A - Pseudoprime numbers

英文题*1:Fermat’s theorem states that for any prime number p and for any integer a > 1, ap=a(mod p).

费马定理指出,对于任意素数p和任意整数a>1,ap=a(mod p)。也就是说,如果我们把a提升到ap ,除以p,余数就是a。p的一些(但不是很多)非素数,称为“基于a的伪素数”(base-a pseudoprimes),有a的这个性质。(而有些被称为是Carmichael数的,对所有a都是base-a pseudoprimes)。
给定2<p≤1000000000 和 1<a<p,确定p是否是“基于a的伪素数”(base-a pseudoprimes)。
Input
输入包含几个测试用例,后跟一行包含“0 0”。每个测试用例由一行包含p和a的代码组成。
Output
对于每个测试用例,如果p是一个base-a pseudoprimes,则输出“yes”;否则输出“no”。
Sample Input
3 2
10 3
341 2
341 3
1105 2
1105 3
0 0
Sample Output
no
no
yes
no
yes
yes

分析

这题目说的花里胡哨的,其实的意思就是看p满不满足两个条件:①apmod p=a;②p不是素数。如果满足这两个条件就是题目说的base-a pseudoprimes,不满足其中一条或者全都不满足就不是。

对于条件①,可以选择快速幂的方法。如果先算ap的话,极有可能产生溢出的现象,而每次求都求一次余,那么每次计算出来的都是小于p的余数,这样就减少了溢出的可能性。

  • 快速幂模板
    typedef long long ll ;
    ll poww ( ll x , ll y , ll mod ) 
    {
    	ll ans = 1;
    	while ( y )
    	{
    		if( y&1 ) ans =( ans * x ) % mod ;
    		x =( x * x ) % mod ;
    		y >>= 1;
    	}
    	return ans ;
    }
    
    对于条件②,素数就是除了1和它本身外没有其他因子的数,并且1也不是素数。因此只需要看i∈[2, x \sqrt{x} x ]时,x是否能与i整除(取余等于0)。
  • 素数模板
    #include<cmath>
    typedef long long ll ;
    bool check_Prime(ll x)
    {
    	for(int i=2;i<int(sqrt(x)+1);i++)
    	{
    		if(x%i==0)	return false;
    	}
    	if(x != 1)	return true;
    	else	return false;	//1不是素数
    }
    
    ↑写这个模板的时候呆了一下,忘记写#include<cmath>了,然后就CE辽。一定要记得加上这个库嗷!

看那个p的最大情况是1e9,所以这题定义时选择使用long long

代码
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;

long long P,A;

bool check_Prime(long long x)	//看看是不是素数,是返回true,不是返回false
{
	for(int i=2;i<int(sqrt(x)+1);i++)
	{
		if(x%i==0)	return false;
	}
	if(x != 1)	return true;
	else	return false;
}

long long poww(long long a,long long b,long long mod)	//快速幂
{
	long long ans=1;
	while(b)
	{
		if(b&1)	ans=(ans*a)%mod;
		a=(a*a)%mod;
		b>>=1;
	}
	return ans;
}

int main()
{
	while(~scanf("%lld%lld",&P,&A))
	{
		if(P==0 && A==0)	break;
		if(!check_Prime(P))	//如果不是素数,满足条件2
		{
			if(poww(A,P,P)==A)	//满足条件1
			{
				printf("yes\n");
			}
			else	printf("no\n");	//不满足条件1
		}
		else	printf("no\n");	//是素数的话就不满足条件2了
	}
}

B - Raising Modulo Numbers

英文题*2:People are different.

每个玩家选择两个数字Ai和Bi,并将它们写在纸条上。其他人看不到数字。在一个特定的时刻,所有的玩家向其他人展示他们的数字。目标是确定所有玩家包括自己的所有表达式之和,并在除以给定的M后确定余数。胜者是首先确定正确结果的人。根据玩家的经验,选择更高的数字可能会增加难度。
你应该写一个程序,计算结果,并能够找出谁赢得了比赛。
(这里翻译看不懂也问题不大,看后面就星)
Input
输入包括Z个赋值。它们的数目由出现在输入第一行的单个正整数Z给出。然后是一些赋值。每个赋值以包含整数 M(1<=M<=45000) 的行开始。总数将除以这个数字。下一行包含玩家数 H(1<=H<=45000) 。接下来就是H行。在每一行上,正好有两个数字Ai和Bi被空格隔开。两个数字不能同时等于零。
Output
对于每组赋值,只有一行输出。在这一行,有一个数字,表示表达式 (A1B1+A2B2+…+AHBH)mod M 的结果
Sample Input
3
16
4
2 3
3 4
4 5
5 6
36123
1
2374859 3029382
17
1
3 18132
Sample Output
2
13195
13

分析

这题看懂了题意就会发现和上一道题差不多,都是要用快速幂求模运算。而与上一题不同的地方是,对于每组数据,上一题只需要求单个apmod p就可以了,而这道题需要求(A1B1+A2B2+…+AHBH)mod M,有多个数据相加求模。这里就要直到模运算的一些性质:

  1. (a + b) % p = (a % p + b % p) % p
  2. (a – b) % p = (a % p – b % p) % p
  3. (a × b) % p = (a % p × b % p) % p
  4. (a ^ b) % p = ((a % p) ^ b) % p
    结合律:
  5. ((a+b) % p + c) % p = (a + (b+c) % p) % p
  6. ((a×b) % p × c) % p = (a × (b×c) % p) % p
    交换律:
  7. (a + b) % p = (b + a) % p
  8. (a × b) % p = (b × a) % p
    分配律:
  9. ((a +b)% p × c) % p = ((a × c) % p + (b × c) % p) % p

这道题就用了第1个性质。

代码
#include<cstdio>
using namespace std;

long long Z,M,H,sum;
long long A[50000],B[50000],u,v;

long long poww(long long a,long long b,long long mod)	//快速幂
{
	long long ans=1;
	while(b)
	{
		if(b&1)	ans=(ans*a)%mod;
		a=(a*a)%mod;
		b>>=1;
	}
	return ans;
}

int main()
{
	scanf("%lld",&Z);
	while(Z--)
	{
		scanf("%lld",&M);
		scanf("%lld",&H);
		sum=0;
		for(int i=0;i<H;i++)	//有H个玩家
		{
			scanf("%lld%lld",&u,&v);
			A[i]=u;
			B[i]=v;
		}
		/*对sum的赋值与输出*/
		for(int i=0;i<H;i++)
		{
			sum=sum+poww(A[i],B[i],M);
		}
		printf("%lld\n",sum%M);
	}
}

最后对sum的赋值与输出也可以写成:

		for(int i=0;i<H;i++)
		{
			sum=(sum+poww(A[i],B[i],M))%M;
		}
		printf("%lld\n",sum);

意义是一样的。

C - Key Set

英文题*3:soda has a set S with n integers {1,2,…,n}.

soda的集合S有n个整数{1,2,…,n}。如果集合中的整数和是偶数,则集合称为键集合(key set)。他想知道有多少非空的S子集是键集合(key set)。
Input
有多个测试用例。输入的第一行包含一个整数 T(1≤T≤105),表示测试用例的数量。对于每个测试用例:
第一行包含整数 n(1≤n≤109),即集合中的整数数。
Output
对于每个测试用例,输出模1000000007的键集合(key set)数。
Sample Input
4
1
2
3
4
Sample Output
0
1
3
7

分析

这题的意思是:当输入n后,计算{1,2,…,n}这个集合中有多少子集合,使得子集合里的元素是偶数。
举例子看下:
key set个数与n的关系是2n-1-1,但得出这个结论是举例子找出来的规律。个数与个数间相差2的幂次,因此推断出关系与2n-1有关,而个数与2的幂次都相差1,因此要-1,得到2n-1-1。(这里解释的可能不是很清楚,需要自己理解一下)

发现关系有ab这样的格式之后,再加上题目要求输出模1000000007的键集合(key set)数,我们就考虑使用快速幂来进行计算。要注意的是,在最后计算完毕后,应当把计算得出的值-1,才是我们找出的规律的值。

代码
#include<cstdio>
using namespace std;

int T;
long long N,sum;

long long poww(long long a,long long b,long long mod)	//快速幂
{
	long long ans=1;
	while(b)
	{
		if(b&1)	ans=(ans*a)%mod;
		a=(a*a)%mod;
		b>>=1;
	}
	return ans-1;	//要记得-1噢
}

int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%lld",&N);
		sum=poww(2,N-1,1000000007);
		printf("%lld\n",sum);
	}
}

D - Distribution money

英文题*4:AFA want to distribution her money to somebody.

AFA想把她的钱分配给一些人。她把她的钱分成n份部分。一想得到钱的人可以得到不止一个部分。但是如果一个人的钱比其他人的总和还多,他应该受到惩罚。每个得到一部分钱的人会在那部分写上他的ID。
Input
有多种情况。
对于每种情况,第一行中都有一个整数 n(1<=n<=1000)。
在第二行中,有n个整数a1,a2…an(0<=ai<10000)ai是第i个人的ID。
Output
输出应该受到惩罚的人的ID。
如果没有人应该受到惩罚,输出-1。
Sample Input
3
1 1 2
4
2 1 4 3
Sample Output
1
-1

分析

这题我没用教的二分/快速幂做,用的是最普通的做法。一个人得到的数量比其他人的数量还多就受到惩罚,那么就需要统计一下每个人得到了多少数量(a[t]),并把它与其他人的数量(N-a[t])进行比较。如果数量达到惩罚的标准,就记录下这个人的ID。
比较这个人的数量是否比其他人的数量还多时,可以写成if(a[i]>N-a[i]),也可以写成if(a[i]>N/2),这两个是一样的。(前一个可能好理解一点而已)

使用memset(a,0,sizeof(a))的时候要记得加上头文件<cstring><memory.h>

代码
#include<cstdio>
#include<cstring>
using namespace std;

int N,a[10010],t,cnt;

int main()
{
	while(~scanf("%d",&N))
	{
		memset(a,0,sizeof(a));	//初始化数组
		cnt=-1;	//如果没找到的话就一直是-1
		for(int i=0;i<N;i++)	//记录每个人得到了多少
		{
			scanf("%d",&t);
			a[t]++;
		}
		for(int i=0;i<10001;i++)
		{
			if(a[i]>N-a[i])	//这里可以写成if(a[i]>N/2)
			{
				cnt=i;	//记录需要受到惩罚的人的ID号
				break;
			}
		}
		printf("%d\n",cnt);
	}
}

E - Rightmost Digit

英文题*5:Given a positive integer N, you should output the most right digit of N^N.

给定一个正整数N,你应该输出N^N的最右边的数字。
Input
输入包含几个测试用例。输入的第一行是一个整数T,它是测试用例的数量。接下来是T个测试用例。
每个测试用例都包含一个正整数 N(1<=N<=100000000)。
Output
对于每个测试用例,您应该输出N^N的最右边的数字。
Sample Input
2
3
4
Sample Output
7
6
Hint
在第一种情况下, 3 × 3 × 3 = 27, 所以最右边的数字是7。
在第二种情况下,4 × 4 × 4 × 4 = 256, 所以最右边的数字是6。

分析

求NN最右边的数字,分为两步:①求NN的值;②求NN最右边的数字。
为了实现这两种要求,可以使用快速幂。由于要求最右边的数字,即求个位上的数字,就需要把mod的值定为10(对10求余就是个位上的数字)。

代码
#include<cstdio>
using namespace std;

int T;
long long N;

long long poww(long long x,long long y,long long mod)	//快速幂
{
	long long ans=1;
	while(y)
	{
		if(y&1)	ans=(ans*x)%mod;
		x=(x*x)%mod;
		y>>=1;
	}
	return ans;
}

int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%lld",&N);
		printf("%lld\n",poww(N,N,10));
	}
}

F - 人见人爱A^B

求A^B的最后三位数表示的整数。
说明:A^B的含义是“A的B次方”
Input
输入数据包含多个测试实例,每个实例占一行,由两个正整数A和B组成(1<=A,B<=10000),如果A=0, B=0,则表示输入数据的结束,不做处理。
Output
对于每个测试实例,请输出A^B的最后三位表示的整数,每个输出占一行。
Sample Input
2 3
12 6
6789 10000
0 0
Sample Output
8
984
1

分析

这题正好是龙佬上课的时候让我们写的题。这一题也和上一题一样,只是上一题求的是NN最右边的数字,而这题求的是AB最后三位数表示的整数。但步骤是相通的:①求AB的值;②AB最后三位数表示的整数。
为了实现这两种要求,可以使用快速幂。由于要求最后三位数表示的整数,就需要把mod的值定为1000。

代码
#include<cstdio>
using namespace std;

int A,B,cnt;

int poww(int x,int y,int mod)	//快速幂
{
	int ans=1;
	while(y)
	{
		if(y&1)	ans=(ans*x)%mod;
		x=(x*x)%mod;
		y>>=1;
	}
	return ans;
}

int main()
{
	while(~scanf("%d%d",&A,&B))
	{
		if(A==0 && B==0)	break;
		cnt=poww(A,B,1000);
		printf("%d\n",cnt);
	}
}

G - Trailing Zeroes (III)

英文题*6:You task is to find minimal natural number N, so that N! contains exactly Q zeroes on the trail in decimal notation.

你的任务是找到最小的自然数N,使得N!在十进制表示法中,尾部正好包含Q个零。如你所知N!=1×2×…×N。例如,5!=120,120在尾部包含一个零。
Input
输入以整数 T(≤10000)开始,表示测试用例的数量。
每种情况在一行中包含一个整数 Q(1≤Q≤108)。
Output
对于每个案例,打印案例编号和N。如果找不到解决方案,则打印“不可能”。
Sample Input
3
1
2
5
Sample Output
Case 1: 5
Case 2: 10
Case 3: impossible

分析

刚开始看这道题我人都傻了,百度翻译翻译的是“轨迹上”有Q个零,我还在纳闷“轨迹上”是啥意思,然后用土方法把1~10的阶乘都列出来了之后,发现5!=120,6!=720,7!=5040,8!=40320,9!=362880,10!=3628800。如果是整体有Q个0的话,那么Q=2的时候N应该等于7而不是10了。因此猜出题目的意思应该是:求出阶乘的末尾有Q个0的最小的N
可能都怪英语不好……事后发现tail是尾部的意思,可题目上写的不是trail吗??整个人傻住

理解题意是一个问题,还有一个问题就是如何求阶乘末尾0的个数。某数末尾有0,就说明该数是另一个数与10相乘得到的,也就是说是另一个数×2×5得到的。也就是说,某数末尾有几个0,是与某数的因子中有几个2和几个5有关。举个例子:
10!=1×2×3×4×5×6×7×8×9×10,可以发现10!的因子中有1(2)+2(4)+1(6)+3(8)+1(10)=8个2,以及1(5)+1(10)=2个5,8个2和2个5相乘得到的数中末尾有2个零,而10!=3628800末尾也有2个零。而且因为是阶乘,所以2的个数必然比5的个数多,因此只需要考虑有几个5就星(因为2的个数铁定够和5相乘,当然想统计2的个数也行,只需要取统计出的两个值中最小的那个值就星辽,也就是取min(count2,count5))。

代码
#include<cstdio>
using namespace std;

int T,N;
long long Q,l,r,mid,ans;

long long search(long long x)
{
	long long cnt=0;
	while(x)
	{
		cnt+=(x/5);
		x/=5;
	}
	return cnt;
}

int main()
{
	scanf("%d",&T);
	N=1;
	while(T--)	//有T组测试用例
	{
		scanf("%lld",&Q);
		l=1,r=1e10+8;	//r的值需要开大一点
		mid=(l+r)/2;
		while(l<r)
		{
			mid=(l+r)/2;
			if(search(mid)>=Q)	//>可能好理解一点,=Q还要r=mid是因为现在碰到的数不一定是最小的满足条件的数,因此还需要再找
			{
				ans=mid;
				r=mid;	//r=mid是因为mid可能是满足条件的值,不能跳过
			}
			else	l=mid+1;	//l=mid+1是因为mid不满足条件,直接把左端点移到mid后面一个就星
		}
		printf("Case %d: ",N);
		N++;
		if(search(ans)==Q)
		{
			printf("%lld\n",ans);
		}
		else	printf("impossible\n");
	}
}

H - Pie

英文题*7:My birthday is coming up and traditionally I’m serving pie.

我的生日快到了,按照传统我要做馅饼。不仅仅是一个馅饼,不,我有N个馅饼,口味各异,大小各异。还有F个朋友要来参加我的聚会,他们每人得到一块馅饼。这应该是一块馅饼,而不是几小块,因为看起来很乱。不过,这一块可以是一整块馅饼。
我的朋友们都很容易生气,如果他们中的一个得到了比其他人更大的一块,他们就会开始抱怨。因此,他们所有人都应该得到同样大小(但不一定是同样形状)的馅饼,即使这会导致一些馅饼被糟蹋(这比糟蹋派对要好)。当然,我也想要留一块馅饼给自己,而且那块也应该是同样大小的。
我们能得到的最大尺寸是多少?所有的馅饼都是圆柱形的,它们都有相同的高度1,但是馅饼的半径可以不同。
Input
一行正整数:测试用例的数量。对于每个测试用例:
—一行有两个整数N和F,其中1<=N,F<=10000:馅饼的数目和朋友的数目。
—一行有N个整数ri(1<=ri<=10000),代表pies的半径。
Output
对于每个测试用例,输出一行尽可能大的体积V,这样我和我的朋友都可以得到一个V大小的饼片。答案应该是一个浮点数,绝对误差不超过10^(-3)。
Sample Input
3
3 3
4 3 3
1 24
5
10 5
1 4 2 3 4 5 6 5 4 2
Sample Output
25.1327
3.1416
50.2655

分析

写题5min,debug2h(…•˘_˘•…)这题一直错主要是平常都不怎么注意的精度问题:①Pi的精度不够高,要比平常要求的小数点后的数字多好几位,不然就会WA;②自定义的Answer_search函数中的while(r-l>1e-5),r与l相差的精度也要高,刚开始写1e-4就又双叒叕WA了……由于精度问题与答案错误有很大的差别,于是改bug改了个爽(不

题意大致是要公平地分饼。有N个饼,F个朋友,那么加上自己就一共有F+1个人分饼。每个饼都是高为1的圆柱体,每个人得到的饼尺寸要一致(体积相同),但形状可以不同。分饼时,不能是全部的体积之和÷总人数,因为这样就不满足题目中的“This should be one piece of one pie, not several small pieces since that looks messy”了。应该利用二分法,用二分法取得的中间数mid,看看体积大于等于mid的馅饼与mid相除,得到的个数相加是否大于等于F+1。若是,则找找还有没有体积更大的情况;若不是,则找找有没有体积小点的能满足条件的情况。

这里判断是否满足情况自定义的函数check中,用到了lower bound函数,lower bound(起始地址,结束地址,要查找的数值) 返回的 是大于或等于要查找的数值的第一个元素位置。直接从体积大于等于mid的馅饼开始找,在一定程度上能减少时间的消耗。不过这里憨了一下:刚开始写的时候把i=lower_bound(V,V+N,x)-V写成了i=lower_bound(V,V+N,x)-V[0],这两个是有很大区别的啊!V[0]指的是数组V中的第一个元素,而lower_bound返回的是地址,这两个在一起用肯定是不对滴!应该把V[0]改成V,V代表的是数组V的首地址(也就是数组V第一个元素的地址)。首地址-大于等于要查找的数值的第一个元素的地址,就是找出来的元素的下标。

代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#define Pi 3.1415926535897
using namespace std;

int T,N,F,h=1,R[10010];
double l,r,ave,V[10010];

bool check(double x)
{
	int cnt=0;
	for(int i=lower_bound(V,V+N,x)-V;i<N;i++)	
	{
		cnt=cnt+(int)(V[i]/x);	//第i个馅饼按照x的尺寸去切,最多能分的人数(取整)   
	}
	if(cnt>=F+1)	return true;
	else	return false;
}

double Answer_search(double l,double r)
{
	double ans;
	double mid=(l+r)/2.0; 
	while(r-l>1e-5)
	{
		mid=(l+r)/2.0;
		if(check(mid))	//mid满足条件
		{
			ans=mid;
			l=mid;	//看看有没有更大的满足条件的体积
		}
		else	r=mid;	//mid太大了
	}
	return ans;
}

int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&N,&F);
		memset(V,0,sizeof(V));	//初始化数组
		for(int i=0;i<N;i++)	//给数组V赋值
		{
			scanf("%d",&R[i]);	
			V[i]=Pi*R[i]*R[i]*h;
		}
		if(N==1)	//如果只有一块饼就只能凑合凑合就一坨人分一块吃
		{
			ave=V[0]/(F+1.0);
			printf("%.4lf\n",ave);
		}
		else	//如果不止一块
		{
			sort(V,V+N);	//对每块馅饼的体积从小到大排序
			l=0.0,r=V[N-1];	//l是下界,每个人都分不到;r是上界,每个人都得到整个馅饼(而且这个馅饼是所有馅饼中体积最大的)
			ave=Answer_search(l,r);
			printf("%.4lf\n",ave);
		}
	}
}

I - Can you solve this equation?

英文题*8:Now,given the equation 8x4 + 7x3 + 2x2 + 3x + 6 == Y,can you find its solution between 0 and 100;

现在,假设方程8x4+7x3+2x2+3x+6==Y,你能在0到100之间找到它的解吗?现在请试试你的运气。
Input
输入的第一行包含一个整数 T(1<=T<=100),表示测试用例的数量。接着T行,每行有一个实数 Y( fabs(Y)<=1e10 );
Output
对于每个测试用例,您应该只输出一个实数(精确到小数点后4位),这是方程的解;或者,如果0到100之间的方程没有解,就输出“No solution!”
Sample Input
2
100
-4
Sample Output
1.6152
No solution!

分析

要判断0到100之间的方程有没有解,就要先看看方程的情况。设f(x)=8x4+7x3+2x2+3x+6,x∈[0,100]时,f(x)恒>0。f(x)对x求导后,f’(x)=32x3+21x2+4x+3,x∈[0,100]时,f’(x)恒>0。因此可以推断出:f(x)=8x4+7x3+2x2+3x+6在x∈[0,100]这个区间上单调递增。
因为函数f(x)在x∈[0,100]这个区间上是单调递增的,那么如果Y<f(0)或Y>f(100),就说明f(x)==Y在x∈[0,100]这个区间上没有对应的x的解。

由于满足使用二分法的三个条件:①答案区间上下限确定(x∈[0,100]);②检验某值是否可行是个简单活(判断值的大小);③可行解满足区间单调性。因此此题我们使用二分法

因为一些不可避免的误差问题,判断相等的时候我们不用直接的“==”,而是判断两数相减是否小于一个极小值(1e-10)。

代码
#include<cstdio>
#include<algorithm>
using namespace std;

int T,Y;

double f(double a)
{
	return 8*a*a*a*a+7*a*a*a+2*a*a+3*a+6.0;
}

double check(double a,double b,int c)
{
	double mid=(a+b)/2.0;
	while(b-a>=1e-10)
	{	
		mid=(a+b)/2.0;
		if(fabs(f(mid)-c)<1e-10)	//f(mid)与c(近似)相等
			break;
		if(f(mid)>c)	//f(mid)比c大,要把右端点缩小
			b=mid;
		if(f(mid)<c)	//f(mid)比c小,要把左端点放大
			a=mid;
	}
	return mid;
}

int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&Y);
		if(Y<f(0) || Y>f(100))	//没有解
			printf("No solution!\n");
		else	//有解
			printf("%.4lf\n",check(0,100,Y));
	}
}

J - Subsequence

英文题*9:A sequence of N positive integers (10 < N < 100 000), each of them less than or equal 10000, and a positive integer S (S < 100 000 000) are given.

给出了N个正整数(10<N<100000)的序列,每个正整数小于或等于10000,以及一个正整数 S(S<100000 000) 。编写一个程序,找出序列中连续元素的子序列的最小长度,其总和大于或等于S。
Input
第一行是测试用例的数量。对于每个测试用例,程序必须从第一行读取数字N和S,用间隔隔开。序列号在测试用例的第二行给出,用间隔隔开。输入将在文件结束时结束。
Output
对于每种情况,程序必须在输出的单独行上打印结果文件。如果没有答案,打印0。
Sample Input
2
10 15
5 1 3 5 10 7 4 9 2 8
5 11
1 2 3 4 5
Sample Output
2
3

分析

这里用到了尺取法。计算区间[l,r]的区间和sum,如果sum<S,就把右端点r往后挪一个位置,直到sum>=S,或r超出了区间的范围(没有找到/没有找到更好的)为止。跳出while循环后如果sum<S,说明左端点l已经挪得太后面了(l太大了)——没有找到更好的,或者是没有找到,此时就应当跳出整个大循环(for循环)准备输出结果了。而如果跳出while循环后sum>=S,就说明这可能是更好的情况,将此时的长度与之前储存的最小的长度len进行比较,将二者中较小的那个储存进len里记录下来,然后将左端点l后移,查找是否有更好的情况。

代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#define INF 100010
using namespace std;

long long T,N,S,a[100010];
long long l,r,sum,len;

int main()
{
	scanf("%lld",&T);
	while(T--)	//有T组测试用例
	{
		scanf("%lld%lld",&N,&S);
		memset(a,0,sizeof(a));	//初始化数组
		for(int i=0;i<N;i++)
			scanf("%lld",&a[i]);
		for(l=0,r=0,sum=0,len=INF;l<=r;)
		{
			while(sum<S && r<N)	//当区间和大于等于S,或右端点已经超过了区间范围时结束循环
			{
				sum=sum+a[r];	//区间[l,r]的和
				r++;
			}
			if(sum<S)	//此时是左端点l太大时
				break;
			len=min(len,r-l);	//当前遇到的子序列的最小长度
			sum=sum-a[l];	//左端点往后挪一挪看看行不行
			l++;
		}
		if(len!=INF)	//如果找到了答案
			printf("%lld\n",len);
		else	//如果没有找到答案
			printf("0\n");
	}
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值