20分特判,一个puts("1")一个快速幂,不讲。

50%算法:

上次就讲了,可是应该还是有像 xuefen某 或 Dybal某 一样没听的。

用a×inv(b)%mod来表示分数的时候,这个分数值可加可乘(有空证明)

像是一个dp题啊。

初状态是1方案数为1,然后做乘法转移不就好了嘛?

设dp[i][j]表示进行了i次操作后所得的值为j

dp[i][j*a[k]%mod]+=dp[i-1][j];

复杂度O(mod2×m)

 #include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<vector>
#include<string>
#include<cstring>
#define int long long
#define m(a) memset(a,0,sizeof(a))
#define AA cout<<"Alita"<<endl
using namespace std;
const int mod=1e9+;
int ans,S,n,m,p,v[],a[],f[][],g[][],tmp[][];
int poww(int x,int y,int z)
{
int sum=;
while(y)
{
if(y&) sum=sum*x%z;
y>>=;
x=x*x%z;
}
return sum;
}
void X_g()
{
memset(tmp,,sizeof(tmp));
for(int i=;i<p;i++)
{
for(int j=;j<p;j++)
{
(tmp[][i]+=g[][j]*f[j][i]%mod)%=mod;
}
}
for(int i=;i<p;i++)
{
g[][i]=tmp[][i];
}
}
void X_f()
{
memset(tmp,,sizeof(tmp));
for(int i=;i<p;i++)
{
for(int j=;j<p;j++)
{
for(int k=;k<p;k++)
{
(tmp[i][j]+=f[i][k]*f[k][j]%mod)%=mod;
}
}
}
for(int i=;i<p;i++)
{
for(int j=;j<p;j++)
{
f[i][j]=tmp[i][j];
}
}
}
void work(int y)
{
while(y)
{
if(y&) X_g();
y>>=;
X_f();
}
}
signed main()
{
//freopen("1.in","r",stdin);
//freopen("1.out","w",stdout);
scanf("%lld%lld%lld",&n,&m,&p);
if(p==){puts("");return ;}
for(int i=;i<=n;i++) scanf("%lld",&a[i]);
if(n==){printf("%lld",poww(a[],m,p));return ;}
S=poww(n,mod-,mod);
for(int i=;i<=n;i++)
{
(v[a[i]%p]+=S)%=mod;
}
g[][]=;
for(int j=;j<p;j++)
{
for(int k=;k<p;k++)
{
f[j][j*k%p]+=v[k];
}
}
work(m);
for(int i=;i<p;i++)
{
(ans+=g[][i]*i%mod)%=mod;
}
printf("%lld",ans);
return ;
}

Dybala的50分代码(已征得同意后转载)

因为我直接没想这个这么暴力的dp,看到题目的m已经破1e8了那么要么复杂度与之无关要么把它log掉

理论80%算法:

这题m又不是一个计算参数,复杂度不太可能与之无关,尝试log?

带log的dp。。。矩阵快速幂啊!

可以发现dp的第二维并不大,是mod级别,矩阵乘mod3貌似勉强可以接受

O(mod3×log2m)的复杂度。我也不知道题解为什么说能得80分。

略微一算,知道复杂度已经略微超过极限,卡常?没有用,还是50分。

个人认为把矩阵快速幂和暴力dp的得分设置成一样不太道德。

到现在还有人不会用矩阵快速幂优化dp啊。。。这。。。我该怎么讲?

ans矩阵用来更新答案,base矩阵是快速幂的基底。

普通快速幂是这个样子的:for(;t;t>>=1,base=base*base){if(t&1)ans*=base;base*=base;}

换成矩阵也是一样的啊:for(;t;t>>=1){if(t&1)mult_ans();mult_base();}

只不过是两个矩阵乘法函数而已。

提醒:包括用重载运算符的矩阵乘,传参时要带上‘&’符号,不然有时会TLE/RE

base矩阵的构造也很简单,对于每一个可能被选到的数a[i],枚举乘之前的数j。

base[j][j*a[i]%p]+=1;(这里是求解总方案书数,如果是概率打法要把1改成概率)

就是能从a转移到b的话,base[a][b]就有值,是方案的话就是1,算概率的话就是概率。

ans矩阵可以弄成一维数组,能略快一些。这题初始为1,那么ans[1]=1就好了。

注意:代码中的p是原题中的mod,而代码中的mod是1e9+7。我是概率打法

 #include<cstdio>
#define int long long
#define mod 1000000007ll
int n,m,p,base[][],ans[],res[][],a[],ANS;
int pow(int bbase,int t,int modd,int aans=){
for(;t;t>>=,bbase=bbase*bbase%modd)if(t&)aans=aans*bbase%modd;
return aans;
}
void mult_base(){
for(int i=;i<p;++i)for(int j=;j<p;++j)for(int k=;k<p;++k)(res[i][j]+=base[i][k]*base[k][j])%=mod;
for(int i=;i<p;++i)for(int j=;j<p;++j)base[i][j]=res[i][j],res[i][j]=;
}
void mult_ans(){
for(int i=;i<p;++i)for(int j=;j<p;++j)(res[][j]+=ans[i]*base[i][j])%=mod;
for(int i=;i<p;++i)ans[i]=res[][i],res[][i]=;
}
signed main(){
scanf("%lld%lld%lld",&n,&m,&p);
if(n==){
scanf("%lld",&a[]);
printf("%lld\n",pow(a[],m,p));return ;
}
ans[]=;const int P=pow(n,mod-,mod);//概率,即1/n
for(int i=;i<=n;++i)scanf("%lld",&a[i]),a[i]%=p;
for(int i=;i<=n;++i)for(int j=;j<p;++j)(base[j][j*a[i]%p]+=P)%=mod;
for(;m;m>>=){if(m&)mult_ans();mult_base();}
for(int i=;i<p;++i)(ANS+=ans[i]*i)%=mod;
printf("%lld\n",ANS);
}

卡死常数还是50分

100%算法:

想象一下如果题目改一下,你是随机选择数加上它而不是乘,该怎么做?

原始dp方程:dp[i][(j+a[k])%mod]+=dp[i-1][j];

同理,构造矩阵。

base[i][(i+a[j])%mod]=1;//或者概率

写几个不太大的样例,把矩阵写出来,你会发现,它是一个循环矩阵。

这个加法类型的转移矩阵基本上都是循环矩阵(对于不同的被转移数,加数都一样)

回到这道题。

首先从复杂度上考虑,也许可以猜出来这是个循环矩阵。

可是那个诡异的乘法形式并不是循环的,虽然它对于不同的被转移数,乘数都一样。

如果它也是一个加法,该多好啊。

没办法,它是个乘法,我们要接受这个现实。

但是,这并不能阻碍我们把它强制弄成加法。

看看孙金宁老师的叮嘱:啊,什么原根,啊,几次方,啊什么取到所有值。。。(困)

次方?乘法?加法?欧拉定理?

xa × xb=xa+b

诶,这里有加法了!这样可以把乘法换成加法。

动用一下欧拉定理:

xa × xb=x(a+b)%(mod-1)

如果题目的所有输入都是x的几次方,就很好做了。

考虑:刚开始一个数是a0,你可以从a4,a7,a23里面随机选数把它乘起来,值对mod取模,得到ans,求最后ans的期望

这个问题就相当于:考虑:刚开始一个数是0,你可以从4,7,23里面随机选数把它加起来,值对mod-1取模,求最后aans%mod的期望

好做好做!就是普通的加法dp,循环矩阵肝它!

但是。。。这个a是多少呢?

继续听孙金宁的数学课。原根啥啥啥的。。。

原根?啥?原根?哦。好吧。原根就原根吧。

如果你比我聪明,你可能会直接发现,既然原根的次方值能够取遍1~mod-1的所有数,那么这些数就都可以用原根唯一确定的表示出来。那么就可以把题目的所有数都用原根表示,然后当成加法dp来做。你就A了。

如果你没我聪明,那么就相当与你和我一样聪明。

那么如果咱们一样聪明,诶,那好啊,咱们都想不到。既然它一直在念叨原根,那就试试拿原根表示呗。

那么首先我们需要求出原根。用题目给出的那个充要条件很好求。

for(int i=;i<p;i++){
int now=,j;
for(j=;j<p-;++j)
if(al[now]==-)//像它描述里说的一样,不能出现重复的,因为它要在mod-1次里取到mod-1个不同值,故不能重复
al[now]=j,//记录下now这个值唯一确定的对应着i的几次幂
qpow[j]=now,//记录下来i的j次幂是几,以后会用到
now=now*i%p;//now是i的j次幂,j++,更新
else break;
if(j==p-){g=i;break;}//如果j成功的走完了,没有被中途break掉,那么i就是原根。
else for(int i=;i<p;++i)al[i]=-;//清空
}

然而我这么求是用了它说的第一条,这样的话可以顺便求出qpow和al数组里面的值。

al数组不仅是用来标记是否出现过的。如果它出现过,还记录了它对应的是原根g的几次幂。

接下来,我们就拥有了1~mod-1里面每个值和g的几次幂间的双向映射。

那么就把原问题映射成加法dp,循环矩阵干他,再映射回来统计答案即可。

 #include<cstdio>
#define int long long
#define mod 1000000007ll
int n,m,p,base[],ans[],res[],a[],ANS;
int al[],g,qpow[];
int pow(int bbase,int t,int modd,int aans=){
for(;t;t>>=,bbase=bbase*bbase%modd)if(t&)aans=aans*bbase%modd;
return aans;
}
void mult_base(){
for(int i=;i<p;++i)for(int j=;j<p;++j)(res[j]+=base[i]*base[(j-i+p)%p])%=mod;
for(int i=;i<p;++i)base[i]=res[i],res[i]=;
}
void mult_ans(){
for(int i=;i<p;++i)for(int j=;j<p;++j)(res[j]+=ans[i]*base[(j-i+p)%p])%=mod;
for(int i=;i<p;++i)ans[i]=res[i],res[i]=;
}
signed main(){
scanf("%lld%lld%lld",&n,&m,&p);
ans[]=;const int P=pow(n,mod-,mod);
for(int i=;i<=;++i)al[i]=-;
for(int i=;i<p;i++){
int now=,j;
for(j=;j<p-;++j)
if(al[now]==-)al[now]=j,qpow[j]=now,now=now*i%p;
else break;
if(j==p-){g=i;break;}
else for(int i=;i<p;++i)al[i]=-;
}p--;
for(int i=;i<=n;++i)scanf("%lld",&a[i]),a[i]=al[a[i]];
for(int i=;i<=n;++i)(base[a[i]]+=P)%=mod;
for(;m;m>>=){if(m&)mult_ans();mult_base();}
for(int i=;i<p;++i)(ANS+=ans[i]*qpow[i])%=mod;
printf("%lld\n",ANS);
}

于倬浩:然后你就愉快的拿到了100分的好成绩

随(rand):原根,循环矩阵,dp的更多相关文章

  1. 2018.09.27 bzoj2510: 弱题(概率dp+循环矩阵优化)

    传送门 简单概率dp. 显然每次转移的式子可以用一个矩阵表示出来: 这个是循环矩阵. 因此只用维护第一行快速幂一波就行了. 代码: #include<bits/stdc++.h> #def ...

  2. bzoj 2510: 弱题 概率期望dp+循环矩阵

    题目: Description 有M个球,一开始每个球均有一个初始标号,标号范围为1-N且为整数,标号为i的球有ai个,并保证Σai = M. 每次操作等概率取出一个球(即取出每个球的概率均为1/M) ...

  3. [bzoj2510]弱题 (循环矩阵优化dp)

    Description 有M个球,一开始每个球均有一个初始标号,标号范围为1-N且为整数,标号为i的球有ai个,并保证Σai = M. 每次操作等概率取出一个球(即取出每个球的概率均为1/M),若这个 ...

  4. 【循环矩阵乘优化DP】BZOJ 2510 弱题

    题目大意 有 \(M\) 个球,一开始每个球均有一个初始标号,标号范围为 \(1\) - \(N\) 且为整数,标号为 \(i\) 的球有 \(a_i\) 个,并保证 \(\sum a_i = M\) ...

  5. Luogu3702 SDOI2017 序列计数 矩阵DP

    传送门 不考虑质数的条件,可以考虑到一个很明显的$DP:$设$f_{i,j}$表示选$i$个数,和$mod\ p=j$的方案数,显然是可以矩阵优化$DP$的. 而且转移矩阵是循环矩阵,所以可以只用第一 ...

  6. BZOJ 4204 && BZOJ 2510 循环矩阵

    n^3logn非常显然.所以要用一种因为这个矩阵是一个循环矩阵,所以只要知道第一行就可以知道所有行了. C[i][j]=C[i-1][j-1]; #include <iostream> # ...

  7. LA 3704 (矩阵快速幂 循环矩阵) Cellular Automaton

    将这n个格子看做一个向量,每次操作都是一次线性组合,即vn+1 = Avn,所求答案为Akv0 A是一个n*n的矩阵,比如当n=5,d=1的时候: 不难发现,A是个循环矩阵,也就是将某一行所有元素统一 ...

  8. bzoj 2510: 弱题 循环矩阵

    2510: 弱题 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 124  Solved: 61[Submit][Status][Discuss] De ...

  9. UVA 1386 - Cellular Automaton(循环矩阵)

    UVA 1386 - Cellular Automaton option=com_onlinejudge&Itemid=8&page=show_problem&category ...

随机推荐

  1. [UWP] 自定义一个ItemsPanel

    在做游民星空的搜索页面的时候,需要展示搜索热点词,返回的是一个string数组的形式,然后以一种错落的方式显示,每一个Item的大小都和热点词长度一致,然后一行放不下之后就换行,描述的不太直观,直接看 ...

  2. Solidity 编程实例--投票

    Voting 投票 思路是为每张选票创建一个合约,每个投票选项提供一个短名称.合约创建者作为会长将会给每个投票参与人各自的地址投票权. 地址后面的人们可以选择自己投票或者委托信任的代表人替他们投票.在 ...

  3. 常用css总结

    个人博客: https://chenjiahao.xyz 1.让网站快速变灰 html { filter: grayscale(100%);//IE浏览器 -webkit-filter: graysc ...

  4. Redis info 说明

    背景 前面几篇文章介绍完了Redis相关的一些说明,现在看看如何查看Redis的一些性能指标和统计信息,也可以看官网说明. INFO [section] INFO命令返回有关服务器的信息和统计信息,带 ...

  5. Hyper-V 下linux虚拟机静态IP上网配置的两种方式(2)

    工作需要,搭建linux环境,网上搜了两种Hyper-V配置linux静态IP及上网的方式,记录一下,方便查阅,如下设置网络共享方式: win10下使用hyper-v在本机安装linux虚拟机后,网络 ...

  6. Microsoft Word 2019 mac破解版下载

    Microsoft Word 2019 Mac版是大名鼎鼎的Office办公软件组件之一,能帮助你进行文字排版,可方便的进行创作项目.作业.信件.博客.剧本.笔记.评论文章或简历. Microsoft ...

  7. thinkphp5底层基类封装、内部类函数

    记录下thinkphp5自定义底层基类.内部类函数使用笔记 大部分笔记来自tp手册. 底层常用代码的封装 在控制器中基类的起着至关重要的作用,整个项目的代码安全,复杂程度,易读性都要看你项目的基类架构 ...

  8. ASP.NET Core在 .NET Core 3.1 Preview 1中的更新

    .NET Core 3.1 Preview 1现在可用.此版本主要侧重于错误修复,但同时也包含一些新功能. 这是此版本的ASP.NET Core的新增功能: 对Razor components的部分类 ...

  9. i春秋DMZ大型靶场实验(三)内网转发DMZ2

    更具实验文件知道存在源码泄露  下载源码进行源码审计 发现admin账号 查看user.php 发现mysql 账号 端口 对登录后源码进行审计 发现上传文件的两处漏洞 对 fiel name 可以 ...

  10. 1.7.3.1版本ride乱码的解决方法

    现象: 解决方式: 修改文件\Python36\Lib\site-packages\robotide\contrib\testrunner\testrunner.py 将latin1修改为mbcs 然 ...