写在前面
需要学会的前置技能:
(快速幂+二次剩余+逆元+扩展欧几里得) 或者 (矩阵快速幂+二次剩余)
笔者是一名十八线蒟蒻
,如果文章有误,请在评论区下留言,我会尽快处理,非常感谢!
原题题面
For a series
:
We have some queries. For each query
, the answer
is the value
modulo
.
Moreover, the input data is given in the form of encryption, only the number of queries
and the first query
are given. For the others, the query
is defined as the xor of the previous
and the square of the previous answer
.For example, if the first query
is
, the answer
is
, then the second query
is
.
Finally, you don’t need to output all the answers for every query, you just need to output the xor of each query’s answer
Input:
The input contains two integers,
representing the number of queries and
representing the first query.
Output:
An integer representing the final answer.
输入
17 473844410
输出
903193081
题面分析
已知
,
求
,其中
因为异或没有快速或者简便算法,所以我们把重心放在求
上。
本文将介绍两种方法供读者选择:
1.二阶线性数列递推+快速幂优化
2.矩阵快速幂+广义斐波那契循环节
二阶线性数列递推
首先我们直接给不喜欢看证明过程的读者写结论。
对于已知
的二阶线性数列
我们先计算方程
的解,
如果该方程有二实根
,则
------------①
如果该方程只有一实根
,则
------------②
所以我们设
,代入得到
下面给出这种方法的证明,可以不需要掌握,建议有一定数学基础的读者观看。
证明
出于个人偏好,笔者把
记为
。
为了化简该方程,我们努力构造一种
,使得原式可以化简为
,从而将二阶递推转化为一阶线性递推。
那我们展开
,得到
那就得到
那我们很容易就会想到韦达定理,所以我们得到一个方程:
。
这个叫做这个数列的 特征方程 ,我们可以用求根公式解出答案。
1.当方程只有一实根时(记为
):
首先我们可以简单地求出
,那显然
,
所以我们得到
再利用和刚才的方法一样,求它的特征方程解出来即可,②式得证。
2.当方程有两实根时(记为
)
我们有
,同上的方法求解即可,①式得证。
二次剩余
你可能会问:
这个式子有无理数和分数存在,会丢失精度,为什么我们要求出它?
所以,我们需要引入二次剩余。
通俗来说,就是已知
,求
的解,具体内容本文不赘述。
我们可以令
,代入方程得到其中一个解
。
这个数特殊在哪里呢?
注意
也就是说
,我们记为
所以
就是在模998244353的情况下,等价于
的数字!!!
这是我们今天解出这题的第一个关键点!
有了这个数字,我们可以得到
其中,
是2的逆元。利用费马小定理求解。
同理求出
的逆元---->
,记为
所以也可以得到
因此
。
n次幂的部分我们可以用快速幂求解,复杂度为
。
优化快速幂
按理来说,这题到这里也就结束了,但因为本题
最大可以是
,
的复杂度会超时!
那应该怎么改呢?
我们来考虑考虑优化快速幂。
我们原本使用的快速幂其实是基于2进制的快速幂。
举个例子,求解
时,我们有
(拆成2进制上的权值)
所以
。
这样计算不仅快捷,在模
时还会避免溢出。
尽管它已经是如此优秀,但依旧达不到我们的要求。
因此我们提出对快速幂的优化-------->k进制快速幂。
k进制快速幂的原理与普通快速幂相同,就是先把一个数拆成k进制上的权值,再相乘求和。
对于一些题而言,
已经可以应付大部分,甚至大数的情况了,但我们此处对其要求更高,于是可以考虑
。
笔者在写题时考虑到
可能为
,就设
,这样权值位只有四种:
。
那以
中的
的优化为例,我们有
。
其中
这四个数可以用普通快速幂提前处理出来。
同理也是可以优化
。
这样复杂度就从原来的
降到
左右了,即使
,复杂度大约也在
不到。当然
可以取更大的或者稍小一些的数字,有兴趣的读者可以试一试。
需要注意的是,在求
的时候可能会产生循环节,因此把每一次的结果都要记忆化。 好像群里大佬说只有最小循环节期望只有几万…
事实上,这个方法可以再优化:利用欧拉定理降幂,可以把n降到
以下后,再用上述方法取
求解,就只有两个权值:
和
了。复杂度可以降到原来的
左右。这部分留给读者自己去探索。
AC代码(305ms)
//代码很丑,大家见谅TAT
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
map<long long,long long>P;
long long qsm(long long a,long long b,long long c)//普通快速幂
{
long long ans=1,base=a;
while(b!=0)
{
if(b&1)
ans=ans*base%c;
base=base*base%c;
b>>=1;
}
return ans%c;
}
long long inv17=524399943;//根号17
long long ans1=262199973,ans11=133071688,ans12=296255346,ans13=253151293;
long long ans2=736044383,ans21=19600266,ans22=683624219,ans23=530734902;
//都是预处理用的数字
long long f1(long long ans,long long n)//优化快速幂
{
long long x0=n/281474976710656LL;
long long x1=x1=(n%281474976710656LL)/4294967296LL;
long long x2=(n%4294967296LL)/65536LL;
long long x3=n%65536LL;
long long sum=(((((qsm(ans12,x1,mod)%mod)*qsm(ans11,x2,mod)%mod)%mod)*qsm(ans1,x3,mod)%mod)*qsm(ans13,x0,mod)%mod)%mod;
return sum;
}
long long f2(long long ans,long long n)
{
long long x0=n/281474976710656LL;
long long x1=x1=(n%281474976710656LL)/4294967296LL;
long long x2=(n%4294967296LL)/65536LL;
long long x3=n%65536LL;
long long sum=((((qsm(ans22,x1%mod,mod)%mod)*(qsm(ans21,x2,mod)%mod)%mod)*qsm(ans2,x3,mod)%mod)*qsm(ans23,x0,mod)%mod)%mod;
return sum;
}
long long f(long long n)//求F(n)
{
if(P.find(n)!=P.end())
return P[n];
long long ans=(f1(ans1,n)-f2(ans2,n)+mod)%mod;
ans=ans*qsm(inv17,mod-2,mod)%mod;
P[n]=ans%mod;
return ans%mod;
}
int main()
{
long long q,n;
scanf("%lld%lld",&q,&n);
long long ans=f(n),sum=f(n);
for(int i=1;i<=q-1;i++)
{
n=n^(ans*ans);
ans=f(n);
sum=sum^ans;
}
printf("%lld\n",sum);
}
真是道一言难尽的题…
但在赛后听了群里大佬们的做法之后,我仔细想了想,发现 这个做法缺乏一般性。
了解二次剩余的读者可以发现,对于
,
不一定有解!,这也就意味着今天如果
下的系数如果被设计到没有二次剩余(比如改成
,根据上述方法做的话,你会发现根号
无解!),该方法便无法使用!
那咋整呢?那咋整呢?那咋整呢?
这里引出第二种方法(通用):广义斐波那契循环节
广义斐波那契循环节
对于数列
,我们称其为广义斐波那契数列。
在数学上可以证明:
是存在循环节的。这里不提供证明,可以说下结论。
记
,若
是模
的二次剩余时,枚举
的所有因子,找到最小的因子
,使得
就是所求的循环节。
若
不是模
的二次剩余,则枚举
的所有因子,方法同上。
找出循环节
之后(笔者之前算出循环节为
(即
),
。因为这个循环节还是很大,所以要再用矩阵快速幂去得到解循环节以内的内容。
当然也可以用类似优化快速幂的方法去优化矩阵快速幂。
这里给出我队友的代码。
AC代码(683ms)
#pragma warning(disable:4996)
#include<bits/stdc++.h>
using namespace std;
#define per(a,b,c) for(int a=b;a<c;++a)
#define rep(a,b,c) for(int a=b;a>c;--a)
#define ll long long
const int maxn(2e2 + 10);
const int mod(998244353);
const int G(3);
const int xunhuanjie(499122176);
#define MAXN maxn
ll a[3][3],b[3][3],c[3][3],dabiao[100000][3][3];
inline void qmatrix(ll m) {
ll d[3][3];
per(i, 1, 3) per(j, 1, 3) b[i][j] = a[i][j];
c[1][1] = 1;
c[1][2] = 0;
c[2][1] = 0;
c[2][2] = 1;
while(m){
if (m & 1) {
d[1][1] = c[1][1] * b[1][1] + c[1][2] * b[2][1];
d[1][2] = c[1][1] * b[1][2] + c[1][2] * b[2][2];
d[2][1] = c[2][1] * b[1][1] + c[2][2] * b[2][1];
d[2][2] = c[2][1] * b[1][2] + c[2][2] * b[2][2];
per(i, 1, 3)per(j, 1, 3)c[i][j] = d[i][j] % mod;
per(i,1, 3)per(j,1, 3)c[i][j] = d[i][j] % mod;
}
d[1][1] = b[1][1] * b[1][1] + b[1][2] * b[2][1];
d[1][2] = b[1][1] * b[1][2] + b[1][2] * b[2][2];
d[2][1] = b[2][1] * b[1][1] + b[2][2] * b[2][1];
d[2][2] = b[2][1] * b[1][2] + b[2][2] * b[2][2];
per(i,1, 3)per(j,1, 3)b[i][j] = d[i][j] % mod;
m >>= 1;
}
}
inline void tenmatrix(ll m) {
ll d[3][3];
per(i,1, 3) per(j,1, 3) b[i][j] = a[i][j];
c[1][1] = 1;
c[1][2] = 0;
c[2][1] = 0;
c[2][2] = 1;
int i = 0;
while (m) {
ll tmp = m % 100;
m /= 100;
if (tmp == 0) {
i += 100; continue;
}
d[1][1] = c[1][1] * dabiao[i+tmp][1][1] + c[1][2] * dabiao[i+tmp][2][1];
d[1][2] = c[1][1] * dabiao[i+tmp][1][2] + c[1][2] * dabiao[i+tmp][2][2];
d[2][1] = c[2][1] * dabiao[i+tmp][1][1] + c[2][2] * dabiao[i+tmp][2][1];
d[2][2] = c[2][1] * dabiao[i+tmp][1][2] + c[2][2] * dabiao[i+tmp][2][2];
per(k, 1, 3)per(j,1, 3)c[k][j] = d[k][j] % mod;
i += 100;
}
}
ll Q, N,ten[20];
int main() {
#ifndef ONLINE_JUDGE
freopen("./Acm.in", "r", stdin);
freopen("./Acm.out", "w", stdout);
#endif // !ONLINE_JUDGE
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
a[1][1] = 3;
a[1][2] = 2;
a[2][1] = 1;
a[2][2] = 0;
ten[0] = 1;
per(i, 1, 20)ten[i] = ten[i - 1] * 100 % xunhuanjie;
per(i, 1, 1001) {
if (i % 100 == 0) {
continue;
}
else {
qmatrix((ten[i/100] * (i % 100))%xunhuanjie);
per(k, 1, 3)per(j,1, 3)dabiao[i][k][j] = c[k][j];
}
}
cin >> Q >> N;
ll ans, finans;
tenmatrix((N-1)%xunhuanjie);
ans = c[1][1] % mod;
finans = ans;
//cout << ans << endl;
per(i, 2, Q+1) {
N = N ^ (ans * ans);
tenmatrix((N-1)%xunhuanjie);
//N = M; qmu(N - 1);
ans = c[1][1]%mod;
finans ^= ans;
//cout << ans << endl;
}
cout << finans << "\n";
}
后记
感谢一波今天的银川网络赛,让我享受赛前起不了床,赛时交不了题,赛后莫名AC的待遇。
祝主办方 提乾日日涉经,令堂天天下葬。
今天和A题斗智斗勇两个小时,赛后一看防AK的TAT…
DrGilbert 2019.9.8