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个数中有多少个不同的数
直接暴力转移就行
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\) 的值,求前缀和,后缀和即可
那要求 \(a\) 的时候呢?
可以发现,式子的右边即为一条链上的 \(a\) 值之和,我们记为 \(sum\),则对于 \(sum\) 有
所以 \(pre_{i-1}=sum-suf_{i}\) 代入到 \(b\) 值做差的式子中可得到
对于上边那个式子,其前后两项会有重复的地方,如 \(b_{2}-b_{1}\) 和 \(b_{3}-b_{2}\),所以将 \(i\in [2,n]\) 的式子相加,便可以得到:
这个式子后边那一坨不就是 \(2\times b_{1}\) 吗,所以
移项可得
再将该式子带回到b做差的式子便可以得到:
则可以求出 \(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}\) 可得知,
不明白的话,可以去画个图,自己列一下式子。
算了,还是说一下吧
将上边那个式子拆开可能会更好理解
\(+(size_{1}-size_{u})\) 因为子树以外的节点到儿子的距离增加了1,\(-size_{u}\) 是因为子树上的点到儿子的距离减少了1。
同样可通过 \(dfs\) 来求出 \(b_{i}\)。
\(t=1\) 时,将上面的式子移项可得
又有
同样将第一个式子求和,再与第二个式子做差,便可以求出 \(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轴同理,则
当 \(typ==1\) 时,答案即为卡特兰数,\(Catalan(n)=C_{n\times 2}^{n}-C_{n\times 2}^{n-1}\),则
当 \(typ==2\) 时,设 \(dp_{i}\) 表示走了i步,回到原点的方案数,\(dp_{0}=1\),则
当 \(typ==3\) 时
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
咕咕咕