题意:
给一个1-n的序列,问有多少中排列方式,使这个序列中存在一个点,它的两边分别满足升升,升降,降降,降生,n的范围是1e18, 答案对p取模,范围是1e18.
解题思路:
一个点能满足左边的数(包括自己)单调递增,自己右边的数(包括自己)单调递减,那么这个数只能是最大值,反之只能是最小值,而我们要求的是就是除开最值之外的排布方案,对于最大值来说,n-1个数每个数只有两种可能,要么排在最大值左边,要么排在最大值右边,所以方案数是2的n-1次,而最小值也是2的n-1次,两种情况加在一起是2的n次.但是考虑一个数两边同时是增或同时是减的情况,我们有没有必要单独算呢?
答案是不用,因为在求增减,减增的时候,剩下的n-1一个数如果都排布在最值的一侧,就能得到升升序列和降降序列了,但是这两种序列在求升降,降升序列的时候都被计算了一次,所以最后答案需要-2,公式就是2的n次-2.
到这里题目的解法都得到了,但是在代码实现上我们来分析,首先2的n次取模肯定要用快速幂,而快速幂里用到乘法,题目给的数据范围是1e18,两个1e18的数相乘不就要爆longlong了,所以我们需要用的一个新的技能(对于弱来说),快速乘,具体原理和快速幂是相同的.就是把乘法化为加法,然后只加二进制中位数为一对应的数进行加快速度.具体见代码
代码:
#include <bits/stdc++.h>
using namespace std;
long long qmutip(long long m, long long n, long long mod)
{
long long res=0;
while(n>0)
{
if(n&1)
res=(res+m)%mod;
m=(m+m)%mod;
n>>=1;
}
return res%mod;
}
long long quickmod(long long a, long long k, long long mod)
{
long long res=1;
while(k>0)
{
if(k&1)
res=qmutip(res, a, mod);
a=qmutip(a,a, mod);
k>>=1;
}
return res%mod;
}
int main()
{
long long n, p;
while(~scanf("%lld %lld", &n, &p))
{
printf("%lld\n", (quickmod(2, n, p)-2+p)%p);
}
}