点我看题

G - Sequence in mod P

稍微观察一下就会发现,进行x次操作后的结果是\(A^xS+(1+\cdots +A^{x-1})B\)。如果没有右边那一坨关于B的东西,那我们要求的就是满足\(A^x \equiv \frac GS\)的最小的x(离散对数)。有一个叫BSGS的东西是专门干这个的

考虑能不能把这个式子直接化成\(A^x \equiv B\)的形式。先把答案=0、A=0和A=1的情况特判掉,因为会影响后面推式子。

\[\begin{align}
A^xS+(1+\cdots+A^{x-1})B&\equiv G\\
A^xS+\frac{A^x-1}{A-1}B&\equiv G(等比数列求和)\\
A^xS+\frac{A^x}{A-1}B&\equiv G+\frac1{A-1}B\\
A^x\cdot(S+\frac B{A-1})&\equiv G+\frac1{A-1}B\\
A^x&\equiv \frac{G+\frac B{A-1}}{S+\frac B{A-1}}
\end{align}
\]

进行上面的最后一步之前判断\(S+\frac B{A-1}\)是否为0,如果是0的话答案就是1,因为之前已经特判过答案为0的情况了;否则就直接跑BSGS就行了。

边界很多,写的时候注意一点。

时间复杂度\(O(t\sqrt P)\)。

点击查看代码
  1. #include <bits/stdc++.h>
  2. #define rep(i,n) for(int i=0;i<n;++i)
  3. #define repn(i,n) for(int i=1;i<=n;++i)
  4. #define LL long long
  5. #define pii pair <int,int>
  6. #define pb push_back
  7. #define fi first
  8. #define se second
  9. #define mpr make_pair
  10. #define MOD P
  11. using namespace std;
  12. LL t,P,A,B,S,G,lim;
  13. map <LL,LL> mp;
  14. LL qpow(LL x,LL a)
  15. {
  16. LL res=x,ret=1;
  17. while(a>0)
  18. {
  19. if((a&1)==1) ret=ret*res%MOD;
  20. a>>=1;
  21. res=res*res%MOD;
  22. }
  23. return ret;
  24. }
  25. int main()
  26. {
  27. cin>>t;
  28. rep(tn,t)
  29. {
  30. cin>>P>>A>>B>>S>>G;
  31. if(S==G)
  32. {
  33. puts("0");
  34. continue;
  35. }
  36. if(A==0)
  37. {
  38. if(B==G) puts("1");
  39. else puts("-1");
  40. continue;
  41. }
  42. if(A==1)
  43. {
  44. if(B==0)
  45. {
  46. puts("-1");
  47. continue;
  48. }
  49. LL tmp=(G-S+P)%P;(tmp*=qpow(B,P-2))%=P;
  50. cout<<tmp<<endl;
  51. continue;
  52. }
  53. LL right=(G+B*qpow(A-1,P-2))%P,mul=(S+B*qpow(A-1,P-2))%P;
  54. if(mul==0)
  55. {
  56. if(right==0)
  57. {
  58. puts("1");
  59. continue;
  60. }
  61. puts("-1");
  62. continue;
  63. }
  64. (right*=qpow(mul,P-2))%=P;
  65. //A^ans = right
  66. //bsgs
  67. lim=ceil(sqrt((double)P));
  68. mp.clear();
  69. LL cur=1;
  70. rep(i,lim)
  71. {
  72. LL res=right*cur%P;
  73. if(mp.find(res)==mp.end()) mp[res]=i;
  74. else mp[res]=max(mp[res],(LL)i);
  75. (cur*=A)%=P;
  76. }
  77. LL bas=1;rep(i,lim) (bas*=A)%=MOD;
  78. cur=bas;
  79. LL ans=1e18;
  80. repn(a,lim)
  81. {
  82. if(mp.find(cur)!=mp.end())
  83. {
  84. LL curres=(LL)a*lim-mp[cur];
  85. if(curres<0) curres+=P-1;
  86. curres%=(P-1);
  87. ans=min(ans,curres);
  88. }
  89. (cur*=bas)%=P;
  90. }
  91. if(ans==1e18) puts("-1");
  92. else cout<<ans<<endl;
  93. }
  94. return 0;
  95. }

Ex - add 1

官方题解精简版,供懒人食用。

注意到输入的a数组是单调不降的。令当前所有counter的值分别为\(c_1,c_2 \cdots c_n\)(下标从1开始)。定义一个\(c_1 \cdots c_n\)的状态的"值"为\(max_{i=1}^n\{max(0,a_i-c_i)\}\)。容易发现:这个值在\([0,a_n]\)中,值为0的时候我们就达到目标了,并且一次操作只能让这个值最多减少1。考虑一个值为k的状态(k>0)经过恰好1次操作会怎么变化。首先由于\(a_1=0,a_n>0\),所以我们肯定可以找到唯一的r,满足\(a_r<k\leq a_{r+1}\)。现在开始进行操作,我们随机选择一个i把\(c_i\)归零。有两种情况:

  • \(i \leq r\)。\(a_i<k\),所以任意一个\(c_i\)被归零都不会导致状态的新值达到或超过k。所以状态的值减一。
  • \(i>r\)。很显然状态的值会变成\(a_i\)。

所以可以发现所有值相等的状态都是"等价"的,至少它们达到目标的期望步数一定是相等的。令\(dp_i\)表示值为i的状态达到目标的期望步数。\(dp_0\)=0,我们要求\(dp_{a_n}\)。转移比较显然,是这样的:

\[\begin{align}
dp_i&=\frac 1n(r_i \cdot dp_{i-1}+\sum_{j=r_i+1}^n dp_{a_j})+1\\
dp_{i-1}&=\frac 1{r_i} (n(dp_i-1)-\sum_{j=r_i+1}^n dp_{a_j})
\end{align}
\]

其中\(r_i\)就是上面说的每个状态值对应的r。

其实这样已经可做了,但是毕竟我们要求的是\(dp_{a_n}\),但这个式子是从后往前转移的,但我们要求\(dp_{a_n}\),不太方便,所以可以转化一下:令\(f_i=dp_{a_n}-dp_i\),把\(dp_i=dp_{a_n}-f_i\)带入上面的式子,并再把式子两边同时用\(dp_{a_n}\)减。

\[\begin{align}
dp_{a_n}-dp_{i-1}&=dp_{a_n}-\frac 1{r_i}(n \cdot (dp_{a_n}-f_i-1)-dp_{a_n}\cdot(n-r_i)+\sum_{j=r_i+1}^nf_{a_j}) \ \ \ \ \ \ 右边的dp_{a_n}都消掉了\\
f_{i-1} &= \frac 1{r_i}(n \cdot(f_i+1)-\sum_{j=r_i+1}^nf_{a_j})
\end{align}
\]

发现可以把\([0,a_n]\)这个区间分成n-1段处理,分别是\([a_0,a_1-1],[a_1,a_2-1]\cdots\)每段内部的dp转移都相同(因为\(r_{i+1}\)相同)。所以对每一段分别矩阵快速幂转移就可以了。官方题解还有复杂度更低一点的优化,但是这样已经可以过了,稍微有一点点卡常而已。

时间复杂度\(O(2^3\cdot nlog值域)\)。

点击查看代码
  1. #include <bits/stdc++.h>
  2. #define rep(i,n) for(int i=0;i<n;++i)
  3. #define repn(i,n) for(int i=1;i<=n;++i)
  4. #define LL long long
  5. #define pii pair <int,int>
  6. #define pb push_back
  7. #define fi first
  8. #define se second
  9. #define mpr make_pair
  10. using namespace std;
  11. const LL MOD=998244353;
  12. LL qpow(LL x,LL a)
  13. {
  14. LL res=x,ret=1;
  15. while(a>0)
  16. {
  17. if((a&1)==1) ret=ret*res%MOD;
  18. a>>=1;
  19. res=res*res%MOD;
  20. }
  21. return ret;
  22. }
  23. LL n,a[200010],y[200010];
  24. vector <vector <LL> > pw[70];
  25. vector <vector <LL> > mul(vector <vector <LL> > a,vector <vector <LL> > b)
  26. {
  27. vector <vector <LL> > ret={{0,0},{0,0}};
  28. rep(i,2) rep(j,2) rep(k,2) (ret[i][j]+=a[i][k]*b[k][j])%=MOD;
  29. return ret;
  30. }
  31. int main()
  32. {
  33. cin>>n;
  34. repn(i,n) scanf("%lld",&a[i]);
  35. y[n]=0;
  36. LL sum=0;
  37. for(int i=n-1;i>0;--i)
  38. {
  39. if(a[i]==a[i+1])
  40. {
  41. y[i]=y[i+1];
  42. (sum+=y[i])%=MOD;
  43. continue;
  44. }
  45. LL r=i,add=(n*qpow(r,MOD-2)+(MOD-sum*qpow(r,MOD-2)%MOD))%MOD;
  46. pw[0]={{n*qpow(r,MOD-2)%MOD,1},{0,1}};
  47. vector <vector <LL> > res(0);
  48. LL dist=a[i+1]-a[i],cc=0;
  49. while(dist>0)
  50. {
  51. if(dist&1)
  52. {
  53. if(res.empty()) res=pw[cc];
  54. else res=mul(res,pw[cc]);
  55. }
  56. dist>>=1;++cc;
  57. if(dist==0) break;
  58. pw[cc]=mul(pw[cc-1],pw[cc-1]);
  59. }
  60. y[i]=(res[0][0]*y[i+1]+res[0][1]*add)%MOD;
  61. (sum+=y[i])%=MOD;
  62. }
  63. cout<<y[1]<<endl;
  64. return 0;
  65. }

[题解] Atcoder Beginner Contest ABC 270 G Ex 题解的更多相关文章

  1. [题解] Atcoder Beginner Contest ABC 265 Ex No-capture Lance Game DP,二维FFT

    题目 首先明确先手的棋子是往左走的,将其称为棋子1:后手的棋子是往右走的,将其称为棋子2. 如果有一些行满足1在2右边,也就是面对面,那其实就是一个nim,每一行都是一堆石子,数量是两个棋子之间的空格 ...

  2. 题解 AtCoder Beginner Contest 168

    小兔的话 欢迎大家在评论区留言哦~ AtCoder Beginner Contest 168 A - ∴ (Therefore) B - ... (Triple Dots) C - : (Colon) ...

  3. AtCoder Beginner Contest 178 E - Dist Max 题解(推公式)

    题目链接 题目大意 给你n个点(n<=2e5)要你求所有点中两个点最短的曼哈顿距离 曼哈顿距离定义为d(i,j)=|x1-x2|+|y1-y2|. 题目思路 想了很久也没有什么思路,其实就是一个 ...

  4. 【AtCoder Beginner Contest 181】A~F题解

    越学越菜系列 于2020.11.2,我绿了(错乱) A - Heavy Rotation 签到题,奇数Black,偶数White. code: #include<bits/stdc++.h> ...

  5. AtCoder Beginner Contest 053 ABCD题

    A - ABC/ARC Time limit : 2sec / Memory limit : 256MB Score : 100 points Problem Statement Smeke has ...

  6. KYOCERA Programming Contest 2021(AtCoder Beginner Contest 200) 题解

    KYOCERA Programming Contest 2021(AtCoder Beginner Contest 200) 题解 哦淦我已经菜到被ABC吊打了. A - Century 首先把当前年 ...

  7. AtCoder Beginner Contest 184 题解

    AtCoder Beginner Contest 184 题解 目录 AtCoder Beginner Contest 184 题解 A - Determinant B - Quizzes C - S ...

  8. AtCoder Beginner Contest 154 题解

    人生第一场 AtCoder,纪念一下 话说年后的 AtCoder 比赛怎么这么少啊(大雾 AtCoder Beginner Contest 154 题解 A - Remaining Balls We ...

  9. AtCoder Beginner Contest 177 题解

    AtCoder Beginner Contest 177 题解 目录 AtCoder Beginner Contest 177 题解 A - Don't be late B - Substring C ...

随机推荐

  1. 基于二进制安装Cloudera Manager集群

    一.环境准备 参考链接:https://www.cnblogs.com/zhangzhide/p/11108472.html 二.安装jdk(三台主机都要做) 下载jdk安装包并解压:tar xvf ...

  2. [apue] 文件中的空洞

    空洞的概念 linux 上普通文件的大小与占用空间是两个概念,前者表示文件中数据的长度,后者表示数据占用的磁盘空间,通常后者大于前者,因为需要一些额外的空间用来记录文件的某些统计信息或附加信息.以及切 ...

  3. 5.1SpringBoot整合Kafka(工具安装Kafka+Tools)

    1.工具安装Kafka 上一期我分享了安装zk,下一次我们把Kafka和可视化工具一起搞起来. 注意:这个时候ZK一定要启动成功. zk安装地址:https://www.cnblogs.com/dao ...

  4. redis学习之数据类型

    <?php //连接本地的 Redis 服务 $redis = new Redis(); $redis->connect('127.0.0.1', 6379); echo "Co ...

  5. PostgreSQL 备份

    # WAL日志: # 我们对数据库的增删改查创建之前先是将sql语句记录在WAL日志中, # 只有日志记录刷新到磁盘后,才能写入数据库文件. # 遵从这个过程,不需要在每个事务提交时都刷新数据页到数据 ...

  6. 网络编程、OSI七层协议

    目录 软件开发架构 1.什么是软件开发架构 2.软件开发架构 3.架构优劣势 4.架构发展趋势 网络编程简介 1.如何理解网络编程 2.网络编程的目的 3.网络编程的意义 4.网络编程的起源 5.网络 ...

  7. 3052 [USACO12MAR]摩天大楼里的奶牛Cows in a Skyscraper (状压DP,IDA*)

    状压DP: #include <iostream> #include <cstdio> #include <cstring> #include <algori ...

  8. Docker 08 部署Elasticsearch

    参考源 https://www.bilibili.com/video/BV1og4y1q7M4?spm_id_from=333.999.0.0 https://www.bilibili.com/vid ...

  9. Ceph创建一个新集群 报错: File "/usr/bin/ceph-deploy", line 18, in..........

    [root@ceph-node1 ceph]# ceph-deploy new node1 Traceback (most recent call last): File "/usr/bin ...

  10. java学习第一天.day05

    jvm的内存 栈:类方法使用后自动销毁,销毁的好处是释放内存 java方法执行时,在栈区执行 堆: 线程共享的一块内存区域      所有的对象实例以及 数组 都要在堆上分配      每次使用new ...