2025多校冲刺省选模拟赛2
\(T1\) A. aw \(10pts/20pts\)
-
部分分
- \(10 \sim 20pts\) :枚举每一种定向方案,略带卡常。
点击查看代码
const int p=998244353; struct node {int nxt,to; }e[200010]; int head[100010],dis[1010][1010],a[100010],b[100010],g[2][100010],cnt=0; bool vis[100010]; vector<int>tmp; queue<int>q; void add(int u,int v) {cnt++;e[cnt].nxt=head[u];e[cnt].to=v;head[u]=cnt; } int qadd(int a,int b) {return a+b>=p?a+b-p:a+b; } void bfs(int s) {memset(vis,0,sizeof(vis));q.push(s);vis[s]=1;while(q.empty()==0){int x=q.front();q.pop();for(int i=head[x];i!=0;i=e[i].nxt){if(vis[e[i].to]==0){dis[s][e[i].to]=dis[s][x]+1;q.push(e[i].to);vis[e[i].to]=1;}}} } int main() { #define Isaac #ifdef Isaacfreopen("aw.in","r",stdin);freopen("aw.out","w",stdout); #endifint n,ans=0,sum,i,j,s;scanf("%d",&n);for(i=1;i<=n;i++){scanf("%d",&a[i]);}for(i=0;i<n-1;i++){scanf("%d%d",&g[0][i],&g[1][i]);add(g[0][i],g[1][i]);add(g[1][i],g[0][i]);}for(i=1;i<=n;i++){bfs(i);}for(s=0;s<(1<<(n-1));s++){for(i=1;i<=n;i++){b[i]=a[i];}for(i=0;i<n-1;i++){b[g[(s>>i)&1][i]]=qadd(b[g[0][i]],b[g[1][i]]);b[g[((s>>i)&1)^1][i]]=0;}for(i=1;i<=n;i++){if(b[i]!=0){tmp.push_back(i);}}for(i=0;i<tmp.size();i++){for(j=i+1;j<tmp.size();j++){sum=1ll*b[tmp[i]]*b[tmp[j]]%p;ans=qadd(ans,1ll*sum*dis[tmp[i]][tmp[j]]%p);}}tmp.clear();}printf("%d\n",ans);return 0; }
-
正解
- 考虑将贡献均摊到每一条边上,顺序处理每条边 \((u,v)\) ,并计算其产生的贡献。
- 不妨将贡献定义为关于所有定向方案的期望,最后再乘以 \(2^{n-1}\) 。
- 设 \(p_{u,x}\) 表示当前 \(u\) 有 \(x\) 个
aw
的概率; \(c_{u},c_{v}\) 分别表示 \(u,v\) 两侧原来aw
的数量。 - 接下来考虑转移。
- 若定向为 \(u \to v\) ,概率为 \(\frac{1}{2}\) ,则产生的贡献为 \(\begin{aligned} & \frac{1}{2}\sum\limits_{x}p_{u,x}(c_{u}-x)(c_{v}+x) \\ &=\frac{1}{2}\sum\limits_{x}p_{u,x}(c_{u}c_{v}+(c_{u}-c_{v})x-x^{2}) \\ &=\frac{1}{2}(c_{u}c_{v}+(c_{u}-c_{v})\sum\limits_{x}p_{u,x}x-\sum\limits_{x}p_{u,x}x^{2}) \end{aligned}\) 。
- 若定向为 \(v \to u\) ,概率为 \(\frac{1}{2}\) ,则产生的贡献为 \(\frac{1}{2}(c_{u}c_{v}+(c_{v}-c_{u})\sum\limits_{x}p_{v,x}x-\sum\limits_{x}p_{v,x}x^{2})\) 。
- 同时维护 \(x\) 和 \(p_{u,x}\) 过于困难,但我们只关心 \(\sum\limits_{x}p_{u,x}x,\sum\limits_{x}p_{u,x}x^{2}\) ,即当前 \(u\) 含有
aw
个数的期望和含有aw
个数平方的期望,不妨设其分别为 \(f_{u},g_{u}\) 。 - 此时 \(u \to v\) 的贡献为 \(\frac{1}{2}(c_{u}c_{v}+(c_{u}-c_{v})f_{u}-g_{u})\) , \(v \to u\) 的贡献为 \(\frac{1}{2}(c_{u}c_{v}+(c_{v}-c_{u})f_{v}-g_{v})\) 。
- 问题来到了怎么维护 \(f,g\) 。发现在当前时刻发生改变的只有 \(u,v\) ,且
aw
的期望个数会均匀分布在这两个点上。 - 通过个数期望的由来 \(\begin{aligned} & \frac{1}{2}\sum\limits_{x,y}p_{u,x}p_{v,y}(x+y) \\ &=\frac{1}{2}\sum\limits_{x,y}p_{u,x}p_{v,y}x+p_{u,x}p_{v,y}y \\ &=\frac{1}{2}(f_{u}\sum\limits_{y}p_{v,y}+f_{v}\sum\limits_{x}p_{u,x}) \\ &=\frac{1}{2}(f_{u}+f_{v}) \end{aligned}\) ,易得 \(\begin{cases} f_{u}' \gets \frac{1}{2}(f_{u}+f_{v}) \\ f_{v}' \gets \frac{1}{2}(f_{u}+f_{v}) \end{cases}\) 。
- 类似地,有 \(\begin{aligned} & \frac{1}{2}\sum\limits_{x,y}p_{u,x}p_{v,y}(x+y)^{2} \\ &=\frac{1}{2}(g_{u}+g_{v}+2f_{u}f_{v}) \end{aligned}\) ,易得 \(\begin{cases} g_{u}' \gets \frac{1}{2}(g_{u}+g_{v}+2f_{u}f_{v}) \\ g_{v}' \gets \frac{1}{2}(g_{u}+g_{v}+2f_{u}f_{v}) \end{cases}\) 。
点击查看代码
const ll p=998244353; struct node {ll nxt,to; }e[200010]; ll head[100010],a[100010],u[100010],v[100010],siz[100010],dep[100010],f[100010],g[100010],cnt=0; void add(ll u,ll v) {cnt++;e[cnt].nxt=head[u];e[cnt].to=v;head[u]=cnt; } void dfs(ll x,ll fa) {siz[x]=a[x];dep[x]=dep[fa]+1;for(ll i=head[x];i!=0;i=e[i].nxt){if(e[i].to!=fa){dfs(e[i].to,x);siz[x]=(siz[x]+siz[e[i].to])%p;}} } ll qpow(ll a,ll b,ll p) {ll ans=1;while(b){if(b&1){ans=ans*a%p;}b>>=1;a=a*a%p;}return ans; } int main() { #define Isaac #ifdef Isaacfreopen("aw.in","r",stdin);freopen("aw.out","w",stdout); #endifll n,ans=0,inv=qpow(2,p-2,p),cu,cv,i;cin>>n;for(i=1;i<=n;i++){cin>>a[i];f[i]=a[i];g[i]=a[i]*a[i]%p;}for(i=1;i<=n-1;i++){cin>>u[i]>>v[i];add(u[i],v[i]);}dfs(1,0);for(i=1;i<=n-1;i++){cv=siz[v[i]];cu=(siz[1]-cv+p)%p;ans=(ans+(cu*cv%p+(cu-cv+p)%p*f[u[i]]%p-g[u[i]]+p)%p*inv%p)%p;ans=(ans+(cu*cv%p+(cv-cu+p)%p*f[v[i]]%p-g[v[i]]+p)%p*inv%p)%p;g[u[i]]=g[v[i]]=(g[u[i]]+g[v[i]]+2*f[u[i]]*f[v[i]]%p)*inv%p;f[u[i]]=f[v[i]]=(f[u[i]]+f[v[i]])*inv%p;}cout<<ans*qpow(2,n-1,p)%p<<endl;return 0; }
\(T2\) B. awa \(25pts/25pts\)
-
部分分
- \(25pts\) :枚举前缀匹配成功后再进行枚举后缀,匹配可以使用在 \(rk\) 数组上二分加速但没必要,枚举后缀提前预处理然后
bitset
加速继承。时间复杂度为 \(\frac{ |s|^{2} |t| }{2}\) 。
点击查看代码
const ll mod=1000003579,base=133331; int a[200010],b[200010],jc[200010]; char s[200010],t[200010]; bitset<1010>vis,f[200010]; void sx_hash(char s[],int len,int a[]) {for(int i=1;i<=len;i++){a[i]=(1ll*a[i-1]*base%mod+s[i])%mod;} } int ask_hash(int l,int r,int a[]) {return (a[r]-1ll*a[l-1]*jc[r-l+1]%mod+mod)%mod; } int main() { #define Isaac #ifdef Isaacfreopen("awa.in","r",stdin);freopen("awa.out","w",stdout); #endifint T,n,m,i,j,k;ll ans=0;scanf("%d",&T);jc[0]=1;for(i=1;i<=200000;i++){jc[i]=1ll*jc[i-1]*base%mod;}for(k=1;k<=T;k++){scanf("%s%s",s+1,t+1);n=strlen(s+1);m=strlen(t+1);ans=0;sx_hash(s,n,a);sx_hash(t,m,b);for(i=1;i<=m;i++){f[i].reset();for(j=1;j<=n&&j<=m-i+1;j++){f[i][j]=(ask_hash(n-j+1,n,a)==ask_hash(i,i+j-1,b));} }for(i=1;i<=n;i++){vis.reset();for(j=1;j+i-1<=m;j++){if(ask_hash(1,i,a)==ask_hash(j,j+i-1,b)&&j+i<=m){vis|=f[j+i];}}ans+=vis.count();}printf("%lld\n",ans);}return 0; }
- \(25pts\) :枚举前缀匹配成功后再进行枚举后缀,匹配可以使用在 \(rk\) 数组上二分加速但没必要,枚举后缀提前预处理然后
-
正解
- 设 \(n=|s|,m=|t|\) 。
- 考虑枚举 \(t\) 的断点 \(i\) 使得 \(s_{1 \sim x}\) 是 \(t_{1 \sim i}\) 的后缀,且 \(s_{y \sim n}\) 是 \(t_{i+1,m}\) 的前缀。
- 依据失配树相关知识, \(x\) 一定在 \(s\) 的正串失配树上某个点 \(x'\) 到根节点 \(0\) 的路径上,其中极深点 \(x'\) 对应 \(t\) 在匹配 \(s\) 时跑到 \(t_{i}\) 的匹配长度; \(y\) 同理,一定在 \(s\) 的反串失配树上某个点 \(y'\) 到根节点 \(0\) 的路径上。
- 考虑对于每个 \(x\) 计算合法 \(y\) 的数量,发现其等价于子树内所有 \(x'\) 对应的 \(y'\) 到根节点的链并大小,即所有 \(y'\) 和根节点形成的虚树大小。
- 线段树合并即可。写法同 luogu P5327 [ZJOI2019] 语言 ,略带卡常。
点击查看代码
struct node {int nxt,to; }e[200010]; int head[200010],dep[200010],dfn[200010],rk[200010],nxt[2][200010],len[2][200010],lg[200010],tot=0,cnt=0; char s[200010],t[200010]; void add(int u,int v) {cnt++;e[cnt].nxt=head[u];e[cnt].to=v;head[u]=cnt; } struct ST { int fminn[20][200010];int sx_min(int x,int y){return dfn[x]<dfn[y]?x:y;}void init(int n){for(int j=1;j<=lg[n];j++){for(int i=1;i+(1<<j)-1<=n;i++){fminn[j][i]=sx_min(fminn[j-1][i],fminn[j-1][i+(1<<(j-1))]);}}}int query(int l,int r){int t=lg[r-l+1];return sx_min(fminn[t][l],fminn[t][r-(1<<t)+1]);} }S; void dfs(int x,int fa) {tot++;dfn[x]=tot;rk[tot]=x;S.fminn[0][tot]=fa;dep[x]=dep[fa]+1;for(int i=head[x];i!=0;i=e[i].nxt){dfs(e[i].to,x);} } int lca(int u,int v) {if(dfn[u]>dfn[v]){swap(u,v);}return (u==v)?u:S.query(dfn[u]+1,dfn[v]); } void kmp0(char s[],int n,char t[],int m) {for(int i=2,j=nxt[0][1]=0;i<=n;i++){while(j>=1&&s[i]!=s[j+1]){j=nxt[0][j];}j+=(s[i]==s[j+1]);nxt[0][i]=j;}for(int i=1,j=0;i<=m;i++){while(j>=1&&t[i]!=s[j+1]){j=nxt[0][j];}j+=(t[i]==s[j+1]);len[0][i]=j;} } void kmp1(char s[],int n,char t[],int m) {for(int i=n-1,j=nxt[1][n]=n+1;i>=1;i--){while(j<=n&&s[i]!=s[j-1]){j=nxt[1][j];}j-=(s[i]==s[j-1]);nxt[1][i]=j;}for(int i=m,j=n+1;i>=1;i--){while(j<=n&&t[i]!=s[j-1]){j=nxt[1][j];}j-=(t[i]==s[j-1]);len[1][i]=j;} } struct SMT {int root[200010],rt_sum=0;struct SegmentTree{int ls,rs,maxx,minn,sum;}tree[200010<<6];#define lson(rt) (tree[rt].ls)#define rson(rt) (tree[rt].rs) int build_rt(){rt_sum++;lson(rt_sum)=rson(rt_sum)=0;return rt_sum;}void pushup(int rt){tree[rt].minn=(tree[lson(rt)].minn!=0?tree[lson(rt)].minn:tree[rson(rt)].minn);tree[rt].maxx=(tree[rson(rt)].maxx!=0?tree[rson(rt)].maxx:tree[lson(rt)].maxx);tree[rt].sum=tree[lson(rt)].sum+tree[rson(rt)].sum;if(tree[lson(rt)].maxx!=0&&tree[rson(rt)].minn!=0){tree[rt].sum-=dep[lca(rk[tree[lson(rt)].maxx],rk[tree[rson(rt)].minn])];}}void update(int &rt,int l,int r,int pos){rt=(rt==0)?build_rt():rt;if(l==r){tree[rt].minn=tree[rt].maxx=l;tree[rt].sum=dep[rk[l]];return;}int mid=(l+r)/2;if(pos<=mid){update(lson(rt),l,mid,pos);}else{update(rson(rt),mid+1,r,pos);}pushup(rt);}int merge(int rt1,int rt2,int l,int r){if(rt1==0||rt2==0){return rt1+rt2;}if(l==r){tree[rt1].minn=tree[rt1].maxx=l;tree[rt1].sum=dep[rk[l]];return rt1;}int mid=(l+r)/2;lson(rt1)=merge(lson(rt1),lson(rt2),l,mid);rson(rt1)=merge(rson(rt1),rson(rt2),mid+1,r);pushup(rt1);return rt1;}int query(int rt){return (tree[rt].minn!=0&&tree[rt].maxx!=0)?tree[rt].sum-dep[lca(rk[tree[rt].minn],rk[tree[rt].maxx])]:tree[rt].sum;} }T; int main() { #define Isaac #ifdef Isaacfreopen("awa.in","r",stdin);freopen("awa.out","w",stdout); #endifint testcase,n,m,i,j;ll ans=0;scanf("%d",&testcase);lg[1]=0;for(i=2;i<=200001;i++){lg[i]=lg[i/2]+1;}for(j=1;j<=testcase;j++){scanf("%s%s",s+1,t+1);n=strlen(s+1);m=strlen(t+1);kmp0(s,n,t,m);kmp1(s,n,t,m);for(i=1;i<=n;i++){add(nxt[1][i],i);} dfs(n+1,0);S.init(n+1);for(i=1;i<=m;i++){if(len[0][i]!=0&&len[1][i+1]!=0)//都成功匹配{T.update(T.root[len[0][i]],1,n+1,dfn[len[1][i+1]]);}T.update(T.root[i],1,n+1,1);//先把根节点插进来}for(i=n;i>=1;i--){ans+=T.query(T.root[i]);T.root[nxt[0][i]]=T.merge(T.root[nxt[0][i]],T.root[i],1,n+1);}printf("%lld\n",ans);fill(e+1,e+1+cnt,(node){0,0});fill(head+1,head+1+n+1,0);fill(T.root+0,T.root+1+max(n,m),0);fill(len[1]+1,len[1]+1+m+1,0);cnt=tot=T.rt_sum=ans=0;}return 0; }
\(T3\) C. awaw \(5pts/5pts\)
-
部分分
- \(5pts\) :模拟。
点击查看代码
ull m,cnt; bool vis[510][510]; void dfs(ull x,ull y) {cnt++;vis[x][y]=0;if(x+1<=m&&vis[x+1][y]==1){dfs(x+1,y);}if(x-1>=1&&vis[x-1][y]==1){dfs(x-1,y);}if(y+1<=m&&vis[x][y+1]==1){dfs(x,y+1);}if(y-1>=1&&vis[x][y-1]==1){dfs(x,y-1);} } int main() { #define Isaac #ifdef Isaacfreopen("awaw.in","r",stdin);freopen("awaw.out","w",stdout); #endifull t,n,w,h,a,b,ans=0,i,j,k,v;scanf("%llu",&t);for(v=1;v<=t;v++){scanf("%llu%llu%llu%llu",&n,&m,&w,&h);memset(vis,1,sizeof(vis));for(i=1;i<=n;i++){scanf("%llu%llu",&a,&b);for(j=a;j<=a+w-1;j++){for(k=b;k<=b+h-1;k++){vis[j][k]=0;}}}ans=0;for(i=1;i<=m;i++){for(j=1;j<=m;j++){if(vis[i][j]==1){cnt=0;dfs(i,j);ans+=cnt*cnt;}}}printf("%llu\n",ans);}return 0; }
-
正解
总结
- \(T1\) 想到将贡献均摊到每一条边上后以为只能计算包含它的路径的贡献,没想到能将需要执行的方案进一步转化成期望求解。
- 半场都在死磕 \(T2\) 。
后记
-
开幕雷击。
-
\(T1\) 的下发样例 \(3 \sim 6\) 发生了循环移位,赛时过了很长时间后才有人反馈这个问题并及时得到解决。
-
\(T3\) 下发了快读模板。
点击查看代码
namespace IO {char buf[1 << 21], *p1 = buf, *p2 = buf;#define gc (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)inline int read() {int x = 0;char s = gc;while(!isdigit(s)) s = gc;while(isdigit(s)) x = x * 10 + s - '0', s = gc;return x;} } using namespace IO;