EA 的练习赛 题解
EternalAlexander · · 个人记录
EA 的练习赛 简要题解
suffix
本题是签到题。
难度:普及-
前置知识:快速幂。
Subtask 1
暴力
Subtask 2
注意到,对于任意
Subtask 3
快速幂即可。
#include <bits/stdc++.h>
#define ll long long
int n;
const int mod=998244353;
int qpow(int a,int b){
if (b==0)return 1;
ll d=qpow(a,b>>1);d=d*d%mod;
if (b&1)d=d*a%mod;
return d;
}
int main(){
scanf("%d",&n);
printf("%d",(ll)26*qpow(25,n-1)%mod);
return 0;
}
p.s.
事实上,后面几乎所有题都会用到快速幂,这题算是教程。
senpai
本题是第二个签到题。
难度:普及/提高-
前置知识:乘法逆元。
Subtask 1
如果
这个子任务优美地考察了选手使用 if 语句的能力。
Subtask 2
爆搜。使用链表实现会较为容易。
Subtask 3
状压 dp,令
Subtask 4
用前面的暴力打个表,发现答案为
Subtask 5
前面的部分分和正解都没什么关系,但我也不知道能给什么部分分了。
考虑对每个容器分别计算答案,令
一个重要的发现是,计算容器
考虑到,如果
可以开始 dp。当计算容器
另外两段的总长度可以用
复杂度
#include <bits/stdc++.h>
#define ll long long
const int mod=998244353;
int n,p[55],nxt[55],pre[55],f[55][55][55],fac[55];
int qpow(int a,int b){
if (b==0)return 1;
ll d=qpow(a,b>>1);d=(ll)d*d%mod;
if (b&1)d=d*a%mod;
return d;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d",&p[i]);
for(int i=1;i<=n;++i){
pre[i]=0;nxt[i]=n+1;
for(int j=1;j<i;++j)if(p[j]>p[i])pre[i]=j;
for(int j=n;j>i;j--)if(p[j]>p[i])nxt[i]=j;
}
for(int i=1;i<=n;++i){
std::memset(f,0,sizeof(f));
f[0][i-pre[i]-1][nxt[i]-i-1]=1;
int ans=0,sum=1;
for(int j=1;j<n;++j){
sum=(ll)sum*(n-j)%mod;
int inv=qpow(sum,mod-2);
for(int k=0;k<=n;++k)
for(int l=0;1+k+l<=n-j;++l){
f[j][k][l]=(ll)f[j-1][k][l+1]*(l+1+(nxt[i]!=n+1))%mod;
f[j][k][l]=(f[j][k][l]+(ll)f[j-1][k+1][l]*(k+1+(pre[i]!=0))%mod)%mod;
f[j][k][l]=
(f[j][k][l]+(ll)f[j-1][k][l]*
(n-1-k-l-j-(pre[i]!=0&&nxt[i]!=n+1))%mod)%mod;
ans=(ans+(ll)f[j][k][l]*inv%mod)%mod;
}
}printf("%d ",(ll)ans);
}
return 0;
}
p.s
其他四个都是很久以前出的,这个题是为了降低一点难度差距这次临时造的。
upd.据某人说是原题?可能是我做题少了,没见过。
silksong
本题是简单题。
难度:普及+/提高
前置知识:斜率优化。
Subtask 1
爆搜,注意搜是有技巧的,太暴力可能不是很跑得过去。
Subtask 2
考虑唯一的这条弦
Subtask 3
显然切割
Subtask 4
合理范围内的多项式复杂度算法应该都可以通过。
Subtask 5
首先观察到,对于两条弦
上面两步的目的是使每一次切割割掉的弦的编号连续,然后就可以考虑按照弦 dp 了。
Subtask 6
对上式斜率优化。注意斜率优化细节,有可能
#include <bits/stdc++.h>
#define maxn 500108
#define ll long long
struct node {
int u,v;
}L[maxn],T[maxn];
int cmp(node a,node b){return a.u==b.u?a.v>b.v:a.u<b.u;}
int n,m,a[maxn],b[maxn],tl,fr,re;
ll x[maxn],y[maxn],f[maxn];
void push(ll x1,ll y1){
if (x1==x[fr]){y1=std::min(y[fr],y1);fr++;}
while (re>fr&&(y[fr]-y1)*(x[fr+1]-x[fr])>(y[fr+1]-y[fr])*(x[fr]-x1))fr++;
x[--fr]=x1;y[fr]=y1;
}
void update(ll k){
while (re>fr&&(y[re-1]-y[re])<k*(x[re-1]-x[re]))re--;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
for(int i=1;i<=n;++i)scanf("%d",&b[i]);
for(int i=2;i<=n;++i)a[i]=std::min(a[i],a[i-1]);
for(int i=n-1;i>=1;i--)b[i]=std::min(b[i],b[i+1]);
for(int i=1;i<=m;++i)scanf("%d%d",&L[i].u,&L[i].v);
std::sort(L+1,L+m+1,cmp);
for(int i=1,r=0;i<=m;++i){
if (L[i].v<=r)continue;
r=L[i].v;T[++tl]=L[i];
}fr=re=500003;x[fr]=a[T[1].u-1];y[fr]=0;
for(int i=1;i<=tl;++i){
update(-b[T[i].v+1]);
f[i]=b[T[i].v+1]*x[re]+y[re];
for(int j=fr;j<=re;++j)
f[i]=std::min(f[i],b[T[i].v+1]*x[j]+y[j]);
push(a[T[i+1].u-1],f[i]);
}printf("%lld",f[tl]);
return 0;
}
p.s.
这是本场唯一一个最优化问题。
search
本题是第二个简单题。
难度:提高+/省选-
前置知识:长链剖分,线段树。
upd:可以只使用树状数组实现,下面这个解法写麻烦了。
Subtask 1
考虑计算出所有方案中访问到的节点数总和再除掉总方案数。枚举所有可能情况,按照题意模拟。
Subtask 4
一条链显然那个最优性剪枝没有用,输出
Subtask 5
可以 dp,
Subtask 2
合理的多项式复杂度算法都可以通过。
Subtask 3
令
还是计算所有方案中访问到的节点数总和再除掉总方案数,我们可以对每个节点分别统计它会被计算多少次。考虑一个节点
这个做法可以同时通过
Subtask 6
沿用
尽管线段树合并,暴力遍历短儿子线段树,二分出节点数,算逆元等等有很多
#include <bits/stdc++.h>
#define maxn 300005
#define ll long long
const int mod=998244353;
int n,fa[maxn],h[maxn],root[maxn],tag[maxn<<5],tl,ans,fac[maxn],sum=1,depth[maxn],H[maxn],
son[maxn],vis[maxn],ifac[maxn];
std::vector<int>ch[maxn],rk[maxn];
int qpow(int a,int b){
int ans=1;
for(ll d=a;b;b>>=1,d=d*d%mod)
if (b&1)ans=d*ans%mod;
return ans;
}
int inv(int x){return qpow(x,mod-2);}
int dgr(int u){return ch[u].size();}
int cmp(int a,int b){return vis[a]||vis[b]?vis[a]<vis[b]:h[a]<h[b];}
int binom(int n,int m){return (ll)fac[n]*ifac[m]%mod*ifac[n-m]%mod;}
int calc(int n,int l){return (ll)binom(n,l+1)*fac[n-l-1]%mod*fac[l]%mod;}
int find(int u,int lim){
int l=1,r=dgr(u)-1,ans=0;
while (l<=r){
int mid=(l+r)>>1;
if (h[ch[u][mid-1]]<lim){ans=mid;l=mid+1;}
else r=mid-1;
}
return ans;
}
namespace ds {
int ch[maxn<<5][2];
void pushdown(int rt){
if (!ch[rt][0]&&!ch[rt][1])return;
if (ch[rt][0])tag[ch[rt][0]]=(ll)tag[ch[rt][0]]*tag[rt]%mod;
if (ch[rt][1])tag[ch[rt][1]]=(ll)tag[ch[rt][1]]*tag[rt]%mod;
tag[rt]=1;
}
int insert(int l,int r,int p,int rt){
if (!rt){rt=++tl;tag[rt]=(l!=r);}
if (l==r){
tag[rt]=(tag[rt]+sum)%mod;
return rt;
}int mid=(l+r)>>1;
pushdown(rt);
if (p<=mid)ch[rt][0]=insert(l,mid,p,ch[rt][0]);
else ch[rt][1]=insert(mid+1,r,p,ch[rt][1]);
return rt;
}
int merge(int u,int v){
if (!u||!v)return u+v;
pushdown(u);pushdown(v);
ch[u][0]=merge(ch[u][0],ch[v][0]);
ch[u][1]=merge(ch[u][1],ch[v][1]);
if (!ch[u][0]&&!ch[u][1])tag[u]=(tag[u]+tag[v])%mod;
return u;
}
void multiply(int l,int r,int L,int R,int v,int rt){
if (l>R||r<L||!rt||l>r)return;
if (l<=L&&R<=r){tag[rt]=(ll)tag[rt]*v%mod;return;}
pushdown(rt);
multiply(l,r,L,(L+R)>>1,v,ch[rt][0]);
multiply(l,r,((L+R)>>1)+1,R,v,ch[rt][1]);
}
void dfs1(int rt,int u,int v,int l,int r){
if (!rt)return;
if (l==r){
int L=l-depth[u];
tag[rt]=(ll)tag[rt]*calc(dgr(u),find(u,L)-(h[v]<L)+(h[son[u]]<L))%mod;
return;
}dfs1(ch[rt][0],u,v,l,(l+r)>>1);
dfs1(ch[rt][1],u,v,((l+r)>>1)+1,r);
}void sum(int l,int r,int rt){
if (!rt)return;
if (l==r)
ans=(ans+tag[rt])%mod;
pushdown(rt);
sum(l,(l+r)>>1,ch[rt][0]);sum(((l+r)>>1)+1,r,ch[rt][1]);
}
}
void prework(int u){
h[u]=1e9;H[u]=1;
for(int i=0;i<dgr(u);++i) {
int v=ch[u][i];
prework(v);
h[u]=std::min(h[u],h[v]+1);
H[u]=std::max(H[u],H[v]+1);
if (H[v]>H[son[u]])son[u]=v;
}if (h[u]>n)h[u]=1;
vis[son[u]]=1;
}
void dfs(int u){
depth[u]=depth[fa[u]]+1;
for(int i=0;i<dgr(u);++i)dfs(ch[u][i]);
int len=dgr(u);
if (len){
ds::multiply(1,len==1?n:h[ch[u][0]]+depth[u],1,n,fac[len],root[son[u]]);
for(int i=0;i<len-1;++i){
ds::multiply(h[ch[u][i]]+depth[u]+1,
i==len-2?n:h[ch[u][i+1]]+depth[u],1,n,calc(len,i+1),root[son[u]]);
ds::dfs1(root[ch[u][i]],u,ch[u][i],1,n);
}
for(int i=0;i<dgr(u);++i)
root[u]=ds::merge(root[u],root[ch[u][i]]);
tag[root[u]]=(ll)tag[root[u]]*inv(fac[len])%mod;
}
root[u]=ds::insert(1,n,depth[u],root[u]);
}
int main(){
scanf("%d",&n);
fac[0]=ifac[0]=1;
for(int i=1;i<=n;++i){
fac[i]=(ll)i*fac[i-1]%mod;
ifac[i]=(ll)inv(i)*ifac[i-1]%mod;
}
for(int i=2;i<=n;++i){
scanf("%d",&fa[i]);
ch[fa[i]].push_back(i);
}prework(1);
for(int i=1;i<=n;++i){
std::sort(ch[i].begin(),ch[i].end(),cmp);
if (son[i])assert(ch[i][dgr(i)-1]==son[i]);
sum=(ll)sum*fac[dgr(i)]%mod;
}
dfs(1);
ds::sum(1,n,root[1]);
printf("%d",(ll)ans*inv(sum)%mod);
return 0;
}
p.s.
尽管多一个
sacrificial
本题是第三个简单题。
难度:提高+/省选-
前置知识:prufer 序列。
upd:似乎可以插值。
Subtask 1
手动把所有可能的情况画出来,发现答案是
写个爆搜也行。
Subtask 2
众所周知
Subtask 4
枚举树的形态然后计算。
可以同时通过
Subtask 3
考虑一下假如我们知道了每个点的权值是什么,应该如何计算合法的树的形态数。
从小到大考虑每一种权值并将其加入树中,答案是每种权值的合法方案数的乘积。
令
求解
简单推导一下,
(求助,怎么在 luogu 打多行公式啊...)
枚举权值序列,直接计算即可。
另一种计算
在这个数据范围下,通过矩阵树定理等方式计算
Subtask 5
如果你的复杂度稍高或者不知道如何处理巨大的值域可以的得到这一档分。
Subtask 6
考虑计算恰好有
令
为了建立一个一一映射的关系,我们加入一个排序第二关键字为节点编号。令
对于特定的
#include <bits/stdc++.h>
#define maxn 1505
#define ll long long
const int mod=998244353;
int n,m,C[maxn][maxn],pw[maxn*2][maxn],g[maxn][maxn],f[maxn][maxn],ans,fac[maxn];
int qpow(int a,int b){
if (b==0)return 1;
ll d=qpow(a,b>>1);d=(ll)d*d%mod;
if (b&1)d=d*a%mod;
return d;
}
int binom(int n,int m){
int ans=qpow(fac[m],mod-2);
for(int j=n-m+1;j<=n;j++)ans=1ll*ans*j%mod;
return ans;
}
int main(){
scanf("%d%d",&n,&m);
fac[0]=1;for(int i=1;i<=n;++i)fac[i]=(ll)fac[i-1]*i%mod;
C[0][0]=1;
for(int i=1;i<=n;++i){
C[i][0]=1;
for(int j=1;j<=n;++j)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
for(int i=1;i<=n*2;++i){
pw[i][0]=1;
for(int j=1;j<=n;++j)pw[i][j]=(ll)pw[i][j-1]*i%mod;
}
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
g[i][j]=(ll)j*pw[i+j][i-1]%mod;
for(int i=1;i<=n;++i)g[i][0]=pw[i][i-1];
f[0][0]=1;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
for(int k=0+i-1;k<j;++k)
f[i][j]=(f[i][j]+(ll)f[i-1][k]*g[j-k][k]%mod*C[n-k][j-k]%mod)%mod;
for(int i=1;i<=n&&i<=m;++i)ans=(ans+(ll)binom(m,i)*f[i][n]%mod)%mod;
printf("%d",ans);
}
总结
本着不卡时间不卡空间的原则,出题人把每题的时限都开到了 std 的十倍以上,空间都开到了 luogu 规定的上限。
本次比赛难度较低,题意清晰,解法套路,给出题人点赞!