T1 随(rand)
一道不合常理的出现在T1的巨难题。
期望$dp$+矩阵乘法+原根优化。
看到题面的期望,首先可以想到1、2点的特判:
- 当$p==2$时,因为对于任何$a_i$都有$0<a_i<p$,所以该情况下我们只需要输出1即可。
- 当$n==1$时,我们的选择对象只能是一个数,而我们要选$m$次,因此只需要快速幂输出$a_1^m$即可,这里要注意p与mod的使用。
接下来我们讨论正解思路。
先思考该题的暴力做法:设$f_{ij}$表示经过$i$次操作后$x$变化为$j$的概率,我们可以想到$dp$式:
$f_{i j imes a_k\%p}+=f_{i-1 j} imes invn$
可是这样我们发现复杂度为$O(nmp)$,这显然是不可以接受的,因此我们先要考虑优化$dp$,降低复杂度。
对于题目中已经给出的特殊性质$a_i$都比较小,且相同的$a_i$对答案的最终贡献是相同的,我们可以想到用桶来记录某一个相同的$a_i$的个数,于是
$f_{i j imes k\%p}+=f_{i-1 j} imes b_k imes invn$
这样我们就可以把复杂度降低到$O(mp^2)$,然后我们就可以开心的$dp$了,并且拿到了50分的好成绩。
可是尽管$dp$已经很精简(至少我是这么认为,毕竟脑小),但是对于$mleq1e9$的数据范围来说,这仍然不能完美的解决这道美妙的题,$dp$都推到这里了不推了多可惜我们该考虑一下优化$dp$,优化的方向就是把$m$转化成$logm$或直接干掉,观察式子,我们发现这个式子可以转化成矩阵乘法的形式(这个发现很奇妙,我现在还不知道怎么看出来的),那么考虑一下构造矩阵。
显然在矩阵乘法中第$i$维已经没什么用了。原式中的$f_i$只与能到达它的$f_j$和它本身有关,所以在构造矩阵时我们可以依据这个转移状态。
因为$invn$每次都要乘进去,且需要乘$m$次,且$dp$式是Σ的运算,所以我们可以把$n$直接求其$m$次方的逆元,省去一定时间。
(下面主要是说给我自己听的,毕竟我不会构造矩阵)
转移与它自己有关,因此在它自己的对应的位上数值$+1$,也与转移到它的$f_j$有关,且与这些$f_j$的数量也有关,因此在$f_j$对应的位上$+{b_j}$,便的到了新的 构建的 可以快速幂的常数矩阵。
接下来就是快乐的矩阵快速幂取模了。
快速幂之后只需要将每一位上的得到概率$/%p$求和,再将得到的结果乘上对应的数值,取模(注意取模的对象),复杂度$O(p^3logm)$,就可以快乐的得到80分,(但是$OJ$我只得到了20分,可能是打错了吧)。
接下来是正解。
复杂度仍然大到飞起,继续找规律(到这里只能颓题解了)。
用到了原根,至于这是个什么东西,请大家善用搜索引擎。
利用原根性质,我们求出了$p$的原根$root$,并且可以用$root$的$n$次方的形式对$p$取模得到小于$p$的任何正整数,我们用次方来来代替上面原数,思路大致不变,就是数的个数转化为这个数是原根的几次方的个数,这时不难发现,原本的乘法转化成了加法,而在构造矩阵的同时,矩阵也由不规则矩阵成为了一个循环矩阵(因为乘变加的原因,构造时类似与对角线的样子),所以我们只需要算出第一行的$m$次方就可以得到整个矩阵的结果,特别注意快速幂基底时定义时$res_0==1$
这道题就差不多解决了。
小弟不才。
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #define int long long 5 #define HZOI using namespace std 6 HZOI; 7 const int mod=1e9+7; 8 const int MAXN=1e6+3; 9 int n,m,p,ans; 10 int invn,root; 11 int dp[1003],a[MAXN],b[MAXN],c[MAXN],jz[1003][1003],res[1003],wc[1003]; 12 inline void Work(); 13 inline int Pow(int ,int ,int ); 14 inline void JzPow(int [1003],int ); 15 inline void Cheng(int [1003],int [1003]); 16 inline void Check(int [1003]); 17 inline int read(); 18 signed main() 19 { 20 n=read(),m=read(),p=read(); 21 Work(); 22 if (p==2) {puts("1");return 0;} 23 for (int i=1; i<=n; ++i) 24 a[i]=read(),++b[a[i]]; 25 for (int i=0; i<p; ++i) //从0开始 26 c[i]=b[Pow(root,i,p)]; 27 if (n==1) 28 {printf("%lld",Pow(a[1],m,p));return 0;} 29 --p; 30 invn=Pow(n,mod-2,mod); 31 for (int i=0; i<p; ++i) 32 wc[i]=c[i]*invn%mod; 33 JzPow(wc,m); 34 for (int i=0; i<p; ++i) ans=(ans+Pow(root,i,p+1)*res[i]+mod)%mod; 35 printf("%lld ",(ans+mod)%mod); 36 } 37 inline void JzPow(int a[1003],int M) 38 { 39 res[0]=1; //不应该出错 40 while (M) 41 { 42 if (M&1) Cheng(res,a); 43 Cheng(a,a); 44 M>>=1; 45 } 46 } 47 inline void Cheng(int x[1003],int y[1003]) 48 { 49 int z[1003]; 50 memset(z,0,sizeof(z)); 51 for (int i=0; i<p; ++i) 52 for (int j=0; j<p; ++j) 53 z[(i+j+p)%p]=(z[(i+j+p)%p]+x[i]*y[j]+mod)%mod; 54 for (int i=0; i<p; ++i) 55 x[i]=(z[i]+mod)%mod; 56 } 57 inline void Work() 58 { 59 for (int i=1; i<p; ++i) 60 { 61 for (int j=1; j<p; ++j) 62 if (j!=p-1 and Pow(i,j,p)==1) 63 break; 64 else if (j==p-1 and Pow(i,j,p)==1) 65 {root=i;break;} 66 if (root) break; 67 } 68 } 69 inline int Pow(int x,int y,int M) 70 { 71 int ans=1; 72 while (y) 73 { 74 if (y&1) ans=ans*x%M; 75 x=x*x%M; 76 y>>=1; 77 } 78 return (ans+M)%M; 79 } 80 inline int read() 81 { 82 int res=0; char ch=getchar(); 83 while (ch<'0' or ch>'9') ch=getchar(); 84 while (ch>='0' and ch<='9') res=(res<<3)+(res<<1)+(ch^48),ch=getchar(); 85 return res; 86 }
T2 单(single)
这道题考试的时候没仔细想,也没有搞测试点,蒙到了10分。
考得思想,前缀和后缀和思想,代换思想。
先说$O(n)$求$b_i$的做法。
维护某一个点的前缀和后缀,在转移时就会有它某一方向的所有$a_i$$-1$,另一方向所有$a_i$$+1$,这就是前缀后缀的用处了。
维护$b$数组一个道理只是需要多次转换化简。
注意在树上统计前后缀时,通过移项出的式子进行更新答案,树的总$a_i$值很重要。
具体见代码吧,写博客的时间比较晚了。
小弟不才。
1 #include<cstdio> 2 #include<cstring> 3 #include<cmath> 4 #include<iostream> 5 #define int long long 6 #define HZOI using namespace std 7 HZOI; 8 const int MAXN=1e5+3; 9 int T,n,opt; 10 int tt,first[MAXN],vv[MAXN<<2],nx[MAXN<<2]; 11 int a[MAXN],b[MAXN],sc[MAXN]; 12 inline void Add(int ,int ); 13 inline int read(); 14 int Dfs00(int ,int ); 15 void Dfs01(int ,int ); 16 void Dfs10(int ,int ); 17 int Dfs11(int ,int ); 18 signed main() 19 { 20 T=read(); 21 while (T--) 22 { 23 tt=0,memset(first,0,sizeof(first)); 24 memset(a,0,sizeof(a)); 25 memset(b,0,sizeof(b)); 26 memset(sc,0,sizeof(sc)); 27 n=read(); 28 for (int i=1,x,y; i<n; ++i) 29 { 30 x=read(); y=read(); 31 Add(x,y); Add(y,x); 32 } 33 opt=read(); 34 if (opt==0) 35 { 36 for (int i=1; i<=n; ++i) a[i]=read(); 37 sc[1]=Dfs00(1,0); 38 Dfs01(1,0); 39 for (int i=1; i<=n; ++i) printf("%lld ",b[i]); 40 puts(""); 41 } 42 else 43 { 44 for (int i=1; i<=n; ++i) b[i]=read(); 45 Dfs10(1,0); 46 sc[1]=(sc[1]+2*b[1])/(n-1); 47 a[1]=sc[1]; 48 Dfs11(1,0); 49 for (int i=1; i<=n; ++i) printf("%lld ",a[i]); 50 puts(""); 51 } 52 } 53 } 54 int Dfs11(int k,int father) 55 { 56 for (int i=first[k]; i; i=nx[i]) 57 if (vv[i]!=father) 58 { 59 a[vv[i]]+=(b[k]-b[vv[i]]+sc[1]); 60 if (k^1) a[k]-=(b[k]-b[vv[i]]+sc[1]); 61 if (k^1) sc[k]+=Dfs11(vv[i],k); 62 else a[k]-=Dfs11(vv[i],k); 63 } 64 if (k^1) {a[k]>>=1; sc[k]+=a[k];} 65 return sc[k]; 66 } 67 void Dfs10(int k,int father) 68 { 69 for (int i=first[k]; i; i=nx[i]) 70 if (vv[i]!=father) 71 { 72 sc[1]+=b[vv[i]]-b[k]; 73 Dfs10(vv[i],k); 74 } 75 } 76 void Dfs01(int k,int father) 77 { 78 for (int i=first[k]; i; i=nx[i]) 79 if (vv[i]!=father) 80 { 81 b[vv[i]]=b[k]-2*sc[vv[i]]+sc[1]; 82 Dfs01(vv[i],k); 83 } 84 } 85 int Dfs00(int k,int father) 86 { 87 sc[k]=a[k]; 88 for (int i=first[k]; i; i=nx[i]) 89 if (vv[i]!=father) 90 sc[k]+=Dfs00(vv[i],k); 91 if (k^1) b[1]+=sc[k]; 92 return sc[k]; 93 } 94 inline void Add(int u,int v) 95 { 96 vv[++tt]=v; nx[tt]=first[u]; first[u]=tt; 97 } 98 inline int read() 99 { 100 int res=0; char ch=getchar(); 101 while (ch<'0' or ch>'9') ch=getchar(); 102 while (ch>='0' and ch<='9') res=(res<<3)+(res<<1)+(ch^48),ch=getchar(); 103 return res; 104 }
T3 题(problem)
几乎原题。35分。
情况为0和1的时候都是原题,但由于取模没有好好取,导致无缘无故少了15分,很难受。
其实情况为3也很好想,其实就是比1稍微难一点,两个$Catalan$相乘,再乘上组合数,就解决了。
其实2情况是没想到的,需要用到一个$dp_i$,定义为走了$i$步回到原点的方案数,知道了$dp$,公式也就不难想了,四个方向,每一个方向都是一个$Catalan$,乘起来就可以了,只不过这里需要注意,这样写可能会有重复情况,需要稍微更改一下$Catalan$的公式,结果为
$dp_i=sum limits_{j=0}^{i-1}4 dp_j C_{i-j-2}^{frac{i-j-2}{2}} C_{i-j-2}^{frac{i-j-4}{2}}$
式子可能确实有些奇怪,不过仔细想想,它确实排除了不同的$i$有相同效果的影响。
小弟不才。
1 #include<cstdio> 2 #include<iostream> 3 #include<cstdlib> 4 #define int long long 5 #define HZOI using namespace std 6 HZOI; 7 const int MAXN=1e6+3; 8 const int mod=1e9+7; 9 int n,typ; 10 int inv[MAXN],F[MAXN],Finv[MAXN]; 11 int dp[MAXN]; 12 inline void Init(); 13 inline int Comb(int ,int ); 14 signed main() 15 { 16 Init(); 17 scanf("%lld%lld",&n,&typ); 18 if (!typ) 19 { 20 int ans=0; 21 for (int a=0; a<=n/2; ++a) 22 ans=(Comb(n,a)%mod*Comb(n-a,a)%mod*Comb(n-2*a,(n/2)-a)%mod+ans+mod)%mod; 23 printf("%lld",(ans+mod)%mod); 24 } 25 else if (typ==1) 26 printf("%lld",((Comb(n,n/2)-Comb(n,(n/2)-1))+mod)%mod); 27 else if (typ==2) 28 { 29 dp[0]=1; 30 for (int i=2; i<=n; i+=2) 31 { 32 for (int j=0; j<i; j+=2) 33 dp[i]=(dp[i]+4*dp[j]%mod*(Comb((i-j-2),(i-j)/2-1)-Comb(i-j-2,(i-j)/2-2))%mod+mod)%mod; 34 } 35 printf("%lld",(dp[n]+mod)%mod); 36 } 37 else 38 { 39 int ans=0; 40 for (int a=0; a<=n/2; ++a) 41 ans=(ans+Comb(n,a*2)%mod*(Comb(2*a,a)-Comb(2*a,a-1))%mod*(Comb(n-2*a,(n/2)-a)-Comb(n-2*a,(n/2)-a-1))%mod+mod)%mod; 42 printf("%lld",(ans+mod)%mod); 43 } 44 } 45 inline int Comb(int x,int y) 46 { 47 return F[x]%mod*Finv[y]%mod*Finv[x-y]%mod; 48 } 49 inline void Init() 50 { 51 inv[1]=1; 52 for (int i=2; i<=100000; ++i) 53 inv[i]=(mod-mod/i)*1ll*inv[mod%i]%mod; 54 Finv[0]=F[0]=1; 55 for (int i=1; i<=100000; ++i) 56 F[i]=F[i-1]%mod*1ll*i%mod, 57 Finv[i]=Finv[i-1]%mod*1ll*inv[i]%mod; 58 }
永不放弃。