训练日志8 (7.27)

T1 随(rand)

  一道不合常理的出现在T1的巨难题。

  期望$dp$+矩阵乘法+原根优化。

  看到题面的期望,首先可以想到1、2点的特判:

  1. 当$p==2$时,因为对于任何$a_i$都有$0<a_i<p$,所以该情况下我们只需要输出1即可。
  2. 当$n==1$时,我们的选择对象只能是一个数,而我们要选$m$次,因此只需要快速幂输出$a_1^m$即可,这里要注意p与mod的使用

  接下来我们讨论正解思路。

  先思考该题的暴力做法:设$f_{ij}$表示经过$i$次操作后$x$变化为$j$的概率,我们可以想到$dp$式:

      $f_{i j\times a_k\%p}+=f_{i-1 j}\times invn$

  可是这样我们发现复杂度为$O(nmp)$,这显然是不可以接受的,因此我们先要考虑优化$dp$,降低复杂度。

  对于题目中已经给出的特殊性质$a_i$都比较小,且相同的$a_i$对答案的最终贡献是相同的,我们可以想到用来记录某一个相同的$a_i$的个数,于是

      $f_{i j\times k\%p}+=f_{i-1 j}\times b_k\times invn$

这样我们就可以把复杂度降低到$O(mp^2)$,然后我们就可以开心的$dp$了,并且拿到了50分的好成绩。

  可是尽管$dp$已经很精简(至少我是这么认为,毕竟脑小),但是对于$m\leq1e9$的数据范围来说,这仍然不能完美的解决这道美妙的题,$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\n",(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 }
随(rand)

 

 

 

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 }
单(single)

 

 

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 }
题(problem)

  

 

  永不放弃。

 

转载于:https://www.cnblogs.com/LH-Xuanluo/p/11261773.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值