noip模拟4

T1

考试的时候直接打的概率期望+快速幂,样例一下就过了以为挺稳
往下看,发现有个原根,看了半天不明白,虽然知道肯定要用,然而并不会用
交了两遍,照着测试点特征又写了写,觉得20应该稳了
然而0分,交的第一遍十分,再交了一遍,莫名其妙成0分了QAQ
20分做法:
\(n==1\) 时,直接快速幂。
\(mod==2\) 时,直接输出1,题目给了 \(a_{i}\) 的取值范围 \(1\le a_{i}< mod\),所以此时a的值都为1

50分做法:
考虑dp,设 \(dp_{i,j}\) 表示当前操作到i次,x的值为j时的概率,\(cnt_{a_{i}}\)\(a_{i}\) 在这个n个数中出现的次数,用 \(cnt_{0}\) 表示n个数中有多少个不同的数
直接暴力转移就行

\[dp\left(i,k*a_{j}\bmod p\right) =\sum_{j=1}^{cnt_{0}}\sum_{k=1}^{p-1} dp\left(i-1,k\right)\times cnt\left(a_{j}\right) \]

100分做法:
剩下的50分,要用到原根然而我并不会,所以去找了其他题解看

倍增优化dp+二进制拆分m,对于i状态,是仅由i-1推过来的,所以可以使用滚动数组优化,状态转移同上记得清空数组

Code
#include<cstdio>
#define MAX 1001
#define re register
#define int long long
namespace OMA
{
   int dp[2][MAX];
   int sta=1,stb=1;
   int cnt[2][MAX];
   const int p=1e9+7;
   inline int read()
   {
     int s=0,w=1; char ch=getchar();
     while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
     while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
     return s*w;
   }
   inline int quickpow(int a,int b,int mod)
   {
     int ans = 1;
     while(b)
     {
       if(b&1)
       { ans = ans*a%mod; }
       a = a*a%mod;
       b >>= 1;
     }
     return ans;
   }
   signed main()
   {
     int n,m,mod;
     n=read(),m=read(),mod=read();
     if(n==1)
     { printf("%lld\n",quickpow(read(),m,mod)%p); return 0; }
     if(mod==2)
     { printf("1\n"); return 0; }
     for(re int i=1; i<=n; i++)
     { cnt[0][read()]++; }
     dp[0][1] = 1;
     /*for(re int i=1; i<=m; i++)
     {
       for(re int j=1; j<=cnt[0]; j++)
       {
         for(re int k=1; k<=mod-1; k++)
         { dp[i][k*num[j]%mod] = (dp[i][k*num[j]%mod]+dp[i-1][k]*cnt[num[j]])%p; }
       }
     }*/
     int a = 0,b = quickpow(quickpow(n,m,p),p-2,p);
     while(m)
     {
       if(m&1)
       {
         for(re int i=1; i<=mod-1; i++)
         { dp[sta][i] = 0; }
         for(re int i=1; i<=mod-1; i++)
         {
           for(re int j=1; j<=mod-1; j++)
           { dp[sta][i*j%mod] = (dp[sta][i*j%mod]+dp[sta^1][j]*cnt[stb^1][i])%p; }
         }
         sta ^= 1;
       }
       for(re int i=1; i<=mod-1; i++)
       { cnt[stb][i] = 0; }
       for(re int i=1; i<=mod-1; i++)
       {
         for(re int j=1; j<=mod-1; j++)
         { cnt[stb][i*j%mod] = (cnt[stb][i*j%mod]+cnt[stb^1][i]*cnt[stb^1][j])%p; }
       }
       stb ^= 1,m >>= 1;
     }
     for(re int i=1; i<=mod-1; i++)
     { a = (a+dp[sta^1][i]*i)%p; }
     printf("%lld\n",a*b%p);
     return 0;
   }
}
signed main()
{ return OMA::main(); }

配合O2食用更佳

T2

咕咕咕
高斯消元可拿40

题目中有一个数据是链,所以从这里入手,首先不难发现,在链上的时候,一个点左侧对其 \(b\) 值的贡献即为该点左侧边的 \(a\) 值之和,同理,右侧也是。不太懂的,可以自己手模一下。

所以,我们设 \(pre_{i}\) 表示前缀和,\(suf_{i}\) 表示后缀和,则有

\[b_{i}=\sum_{j=1}^{i-1}pre_{j}+\sum_{j=i+1}^{n}suf_{j} \]

对与链上每一个点,我们都可以列出这样的式子,然后你会发现相邻两项有许多重复的地方,所以将相邻两项做差,便可以得到一个式子:

\[b_{i}-b_{i-1}=pre_{i-1}+suf_{i} \]

到此,我们便可以计算出 \(b\) 的值,求前缀和,后缀和即可

那要求 \(a\) 的时候呢?

可以发现,式子的右边即为一条链上的 \(a\) 值之和,我们记为 \(sum\),则对于 \(sum\)

\[sum=pre_{i-1}+suf_{i} \]

所以 \(pre_{i-1}=sum-suf_{i}\) 代入到 \(b\) 值做差的式子中可得到

\[b_{i}-b_{i-1}=sum-2\times suf_{i} \]

对于上边那个式子,其前后两项会有重复的地方,如 \(b_{2}-b_{1}\)\(b_{3}-b_{2}\),所以将 \(i\in [2,n]\) 的式子相加,便可以得到:

\[b_{n}-b_{1}=\left(n-1\right)\times sum-2\times \sum_{i=2}^{n}suf_{i} \]

这个式子后边那一坨不就是 \(2\times b_{1}\) 吗,所以

\[b_{n}-b_{1}=(n-1)\times sum-2\times b_{1} \]

移项可得

\[sum=\frac{b_{n}+b_{1}}{n-1} \]

再将该式子带回到b做差的式子便可以得到:

\[suf_{i}=\frac{sum-b_{i}+b_{i-1}}{2} \]

则可以求出 \(a_{i}\)\(a_{i}=suf_{i}-suf_{i+1}\) 同样 \(i\in [2,n]\),那 \(a_{1}\) 呢,由 \(b_{2}-b_{1}=pre_{1}-suf{2}\) 便可以求出,\(pre_{1}\) 不就是 \(a_{1}\) 吗。
上边是链的情况,正解就跟这个差不多了
所以为什么不直接说正解呢,因为我菜

首先,这是个树上的问题,按题目所说考虑两种情况:

\(t=0\) 时,按照刚刚链的情况可推出,点 \(i\)\(b_{1}\) 的贡献即为 \(a_{i}\times (depth_{i}-1)\)\(dfs\) 就可以求出 \(b_{1}\),我们用 \(size_{i}\) 表示以i为根节点的子树的 \(a\) 的和,再由链的 \(b_{i}-b_{i-1}=sum-2\times suf_{i}\) 可得知,

\[b_{u}=b_{fa}+size_{1}-2\times size_{u} \]

不明白的话,可以去画个图,自己列一下式子。
算了,还是说一下吧
将上边那个式子拆开可能会更好理解

\[b_{u}=b_{fa}+(size_{1}-size_{u})-size_{u} \]

\(+(size_{1}-size_{u})\) 因为子树以外的节点到儿子的距离增加了1,\(-size_{u}\) 是因为子树上的点到儿子的距离减少了1。

同样可通过 \(dfs\) 来求出 \(b_{i}\)

\(t=1\) 时,将上面的式子移项可得

\[b_{u}-b_{fa}=size_{1}-2\times size_{u} \]

又有

\[b_{1}=\sum_{i=2}^{n}size_{i} \]

同样将第一个式子求和,再与第二个式子做差,便可以求出 \(size_{1}\),再通过 \(dfs\) 回代,便可以求出所有的 \(a_{i}\)

终于填完了

Code
#include<cstdio>
#define MAX 100001
#define re register
#define int long long
namespace OMA
{
   int T,t,n;
   struct node
   {
     int next;
     int to;
   }edge[MAX<<1];
   int size[MAX];
   int a[MAX],b[MAX];
   int cnt=1,head[MAX];
   inline void add(int u,int v)
   {
     edge[++cnt].next = head[u];
     edge[cnt].to = v;
     head[u] = cnt;
   }
   inline int read()
   {
     int s=0,w=1; char ch=getchar();
     while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
     while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
     return s*w;
   }
   inline void clear()
   {
     cnt = 1;
     for(re int i=0; i<=n; i++)
     { a[i] = b[i] = head[i] = size[i] =  0,edge[i] = edge[i+n] = (node){0,0}; }
   }
   inline void dfs1(int u,int fa,int deep)
   {
     b[1] += a[u]*deep;
     for(re int i=head[u]; i; i=edge[i].next)
     {
       int v = edge[i].to;
       if(v!=fa)
       { dfs1(v,u,deep+1),size[u] += size[v]; }
     }
   }
   inline void dfs2(int u,int fa)
   {
     if(u!=1)
     { b[u] = b[fa]+size[0]-size[u]*2; }
     for(re int i=head[u]; i; i=edge[i].next)
     {
       int v = edge[i].to;
       if(v!=fa)
       { dfs2(v,u); }
     }
   }
   inline void dfs3(int u,int fa)
   {
     size[0] += (u!=1)?b[fa]-b[u]:0;
     for(re int i=head[u]; i; i=edge[i].next)
     {
       int v = edge[i].to;
       if(v!=fa)
       { dfs3(v,u); }
     }
   }
   inline void dfs4(int u,int fa)
   {
     for(re int i=head[u]; i; i=edge[i].next)
     {
       int v = edge[i].to;
       if(v!=fa)
       { dfs4(v,u); size[u] += size[v]; }
     }
     size[u] += (u!=1)?(a[u] = (b[fa]-b[u]+size[0]-size[u]*2)>>1):0;
   }
   signed main()
   {
     T = read();
     for(T; T; T--)
     {
       n=read();
       clear();
       for(re int i=1; i<=n-1; i++)
       {
         int u = read(),v = read();
         add(u,v),add(v,u);
       }
       t=read();
       if(t)
       {
         for(re int i=1; i<=n; i++)
         { b[i] = read(); }
         dfs3(1,0),size[0] = (b[1]*2-size[0])/(n-1),dfs4(1,0),a[1] = size[0];
         for(re int i=2; i<=n; i++)
         { a[1] -= a[i]; }
         for(re int i=1; i<=n; i++)
         { printf("%lld ",a[i]); }
         printf("\n");
       }
       if(!t)
       {
         for(re int i=1; i<=n; i++)
         { size[0] += size[i] = a[i] = read(); }
         dfs1(1,0,0),dfs2(1,0);
         for(re int i=1; i<=n; i++)
         { printf("%lld ",b[i]); }
         printf("\n");
       }
     }
     return 0;
   }
}
signed main()
{ return OMA::main(); }

T3

时间分配不均,考试的时候最后俩题没看

写这题要先去看一下卡特兰数然而考试之前我还没学QAQ

大概就去看了看,OI WiKi

40分做法:
\(dp_{i,j,k}\) 表示,走了i步,走到了 \(\left(j,k\right)\) 位置上的方案数,转移没啥好说的

100分做法:
\(typ==0\) 时,x轴有n/2向上,n/2向下,y轴同理,则

\[ans=\sum_{i=0,i+=2}^{n}C_{n}^{i}\times C_{i}^{i/2}\times C_{n-i}^{(n-i)/2}=C_{n}^{n/2}\times C_{n}^{n/2} \]

\(typ==1\) 时,答案即为卡特兰数,\(Catalan(n)=C_{n\times 2}^{n}-C_{n\times 2}^{n-1}\),则

\[ans=Catalan(n/2) \]

\(typ==2\) 时,设 \(dp_{i}\) 表示走了i步,回到原点的方案数,\(dp_{0}=1\),则

\[dp\left(i\right)=\sum_{j=2,j+=2}^{i}4\times dp\left(i-j\right)\times Catalan(j/2-1) \]

\(typ==3\)

\[ans=\sum_{i=0,i+=2}^{n}C_{n}^{i}\times Catalan\left(i/2\right)\times Catalan((n-i)/2) \]

Code
#include<cstdio>
#define MAX 100001
#define re register
#define int long long
namespace OMA
{
   int ans;
   int dp[MAX]={1};
   int c[MAX],inv[MAX];
   const int p=1e9+7;
   inline int quickpow(int a,int b)
   {
     int ans = 1;
     while(b)
     {
       if(b&1)
       { ans = ans*a%p; }
       a = a*a%p;
       b >>= 1;
     }
     return ans;
   }
   inline int C(int n,int m)
   { return c[n]*inv[m]%p*inv[n-m]%p; }
   inline int Catalan(int n)
   { return C(n*2,n)%p-C(2*n,n-1)%p; }
   void begin(int n)
   {
     c[0] = inv[0] = 1;
     for(re int i=1; i<=n; i++)
     { c[i] = c[i-1]*i%p; }
     inv[n] = quickpow(c[n],p-2);
     for(re int i=n-1; i>=1; i--)
     { inv[i] = inv[i+1]*(i+1)%p; }
   }
   signed main()
   {
     int n,typ;
     scanf("%lld%lld",&n,&typ);
     begin(n);
     if(typ==0)
     { printf("%lld\n",(C(n,n>>1)%p*C(n,n>>1)%p+p)%p); }
     if(typ==1)
     { printf("%lld\n",(Catalan(n>>1)%p+p)%p); }
     if(typ==2)
     {
       for(re int i=2; i<=n; i+=2)
       {
         for(re int j=2; j<=i; j+=2)
         { dp[i] = (dp[i]+4*dp[i-j]*Catalan((j>>1)-1))%p; }
       }
       printf("%lld\n",(dp[n]%p+p)%p);
     }
     if(typ==3)
     {
       for(re int i=0; i<=n; i+=2)
       { ans = (ans+C(n,i)%p*Catalan(i>>1)%p*Catalan((n-i)>>1)%p)%p; }
       printf("%lld\n",(ans%p+p)%p);
     }
     return 0;
   }
}
signed main()
{ return OMA::main(); }

T4

咕咕咕

posted @ 2021-06-06 18:43  -OMA-  阅读(117)  评论(1编辑  收藏  举报
浏览器标题切换
浏览器标题切换end