2025牛客暑期多校训练营4(E,G,I)
Echoes of 24
题意
给你一颗树和两种操作
- 选择 \(l\) 和 \(r\) 两个点,问这条路径上的点权通过加法或乘法能否凑出 \(\text{24}\) 。
- 选择一个点 \(x\) ,使这个点的点权变为 \(c\) 。
做法
考虑路径什么时候一定是不合法的。
- 当路径中最大值大于24
- 当路径中非 \(\text{1}\) 的个数出现了大于 \(12\) 次
- 当路径长度大于 \(24\) 并且非 \(\text{1}\) 的数的和大于了 \(24\)
考虑路径什么时候一定是合法的。
- 当路径的长度大于 \(\text{24}\) 并且非 \(\text{1}\) 的数的和小于等于 \(\text{24}\) 。(非 \(1\) 的个数和小于等于 \(24\) 我们就能用剩下的 \(1\) 凑出 \(24\))
所以我们要维护树上一条链的非 \(1\) 的个数,最大值和总和,用树链剖分就能轻松维护。然后此时长度大于 \(24\) 的链能直接判断出答案,长度小于 \(24\) 时我们把这条链单独拿出来跑个背包就能判断是否能凑出 \(24\) 了。
代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int MAXN=5e5+5;
struct Info{
int not1;
int sum;
int maxn;
Info(){
not1=sum=maxn=0;
}
Info(int x){
not1=(x!=1);
sum=x;
maxn=x;
}
};
Info operator+ (Info Ls,Info Rs){
Info res;
res.not1=Ls.not1+Rs.not1;
res.maxn=max(Ls.maxn,Rs.maxn);
res.sum=Ls.sum+Rs.sum;
return res;
}
int a[MAXN],dep[MAXN],fa[MAXN],son[MAXN],siz[MAXN],dfn[MAXN],top[MAXN];
vector<vector<int>> e(MAXN);
void dfs1(int u,int f){
dep[u]=dep[f]+1;
fa[u]=f;
siz[u]=1;
for(auto v:e[u]){
if(v==f) continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]]) son[u]=v;
}
}
int tot=0;
void dfs2(int u,int tp){
top[u]=tp;
dfn[u]=++tot;
if(son[u]) dfs2(son[u],tp);
for(auto v:e[u]){
if(v==fa[u] || v==son[u]) continue;
dfs2(v,v);
}
}
struct SegmentTree{
#define ls rt<<1
#define rs rt<<1|1
struct Tree{
int l,r;
Info info;
};
vector<Tree> tr;
int n;
SegmentTree(int n):n(n),tr((n+1)<<2){build(1,n,1);}
void update(int rt){
tr[rt].info=tr[ls].info+tr[rs].info;
}
void build(int l,int r,int rt){
tr[rt].l=l,tr[rt].r=r;
if(l==r) return;
int mid=l+r>>1;
build(l,mid,ls);
build(mid+1,r,rs);
update(rt);
}
void modify(int pos,int rt,int c){
if(tr[rt].l==tr[rt].r){
tr[rt].info=Info(c);
return;
}
int mid=tr[rt].l+tr[rt].r>>1;
if(pos<=mid) modify(pos,ls,c);
else modify(pos,rs,c);
update(rt);
}
Info query(int l,int r,int rt){
if(l<=tr[rt].l && tr[rt].r<=r) return tr[rt].info;
int mid=tr[rt].l+tr[rt].r>>1;
if(r<=mid) return query(l,r,ls);
else if(l>mid) return query(l,r,rs);
else return query(l,r,ls)+query(l,r,rs);
}
Info query_chain(int u,int v){
Info res;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
res=res+query(dfn[top[u]],dfn[u],1);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
res=res+query(dfn[u],dfn[v],1);
return res;
}
int LCA(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
return u;
}
};
void solve(){
int n,q;
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs1(1,0);
dfs2(1,1);
SegmentTree tr(n);
for(int i=1;i<=n;i++) tr.modify(dfn[i],1,a[i]);
Info tmp=tr.query_chain(2,2);
while(q--){
int op;cin>>op;
if(op==1){
int l,r;
cin>>l>>r;
Info tmp=tr.query_chain(l,r);
if(tmp.maxn>24){
cout<<0<<endl;
continue;
}
if(tmp.not1>12){
cout<<0<<endl;
continue;
}
int lca=tr.LCA(l,r);
int len=dep[l]+dep[r]-2*dep[lca]+1;
if(len>24){
if(tmp.sum-(len-tmp.not1)>24) cout<<0<<endl;
else cout<<1<<endl;
continue;
}
vector<int> pre,suf;
while(l!=lca){
pre.push_back(a[l]);
l=fa[l];
}
pre.push_back(a[lca]);
while(r!=lca){
suf.push_back(a[r]);
r=fa[r];
}
reverse(suf.begin(),suf.end());
for(auto x:suf) pre.push_back(x);
vector<vector<int>> dp(25,vector<int>(25));
dp[0][pre[0]]=1;
for(int i=1;i<pre.size();i++){
for(int j=0;j<=24;j++){
int sum=j+pre[i];
int mul=j*pre[i];
if(sum<=24) dp[i][sum]|=dp[i-1][j];
if(mul<=24) dp[i][mul]|=dp[i-1][j];
}
}
cout<<dp[pre.size()-1][24]<<endl;
}
else{
int x,c;
cin>>x>>c;
a[x]=c;
tr.modify(dfn[x],1,c);
}
}
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int t=1;
while(t--) solve();
return 0;
}
Ghost in the Parentheses
题意
给你一个长度为 \(n\) 的括号序列,每个位置都有 \(\frac{1}{2}\) 的概率变成 ? ,对于所有的变化之后的序列,问你能唯一确定原先括号序列的总概率是多少,答案对 \(998244353\) 取模。
思路
概率就是唯一确定原先括号序列的个数 \(cnt\) 除以所有可能的序列数量 \(tot\) ,最后答案就是 \(\frac{cnt}{tot}\) 。对于 \(tot=2^n\) ,对于 \(cnt\) 我们要先考虑什么时候这个括号序列是一个合法的情况。
- 首先对于我们选出的子序列,所有的左括号一定要在右括号的左边,如果不满足这个条件那么在左边的右括号可以和右边的左括号交换,交换之后还是一个合法的括号序列。例如:
(())(),如果我们选择 \(\text{1,3,5}\) 的括号构成子序列()(我们交换 \(3,5\) 这两个括号之后还是一个合法的序列。 - 其次我们要考虑从哪些位置把这个序列划分为两半,让左边只选左括号,右边只选右括号。先说结论:若当前位置之前的左括号的数量 \(=\) 右括号的数量 或者 左括号数量 \(=\) 右括号数量 \(+1\) ,则当前位置可以把整个序列划分为两半。我们假设左半部分左括号数量 \(>\) 右括号数量 \(+1\) ,那么我们可以用一个右半部分选择的右括号来替换掉我左半部分选择的左括号使得这个序列还是一个合法序列。
所以对于每个合法的位置选择的方案数 \(=\) 左边左括号的选法 \(\times\) 右边右括号的选法。但是这样会选重,所以要容斥一下去重。
代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int MAXN=1e6+6,mod=998244353;
int fac[MAXN],inv[MAXN];
int qpow(int a,int b){
int res=1;
while(b){
if(b&1) res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
void solve(){
string s;cin>>s;
int n=s.size();
vector<int> a(n+1);
for(int i=1;i<=n;i++) a[i]=s[i-1]-'(';
vector<int> suml(n+1),sumr(n+1),st;
vector<int> pos;
for(int i=1;i<=n;i++){
if(a[i]==0){
st.push_back(i);
if(st.size()==1) pos.push_back(i);
}
else{
st.pop_back();
if(st.size()==1 || st.size()==0) pos.push_back(i);
}
}
for(int i=1;i<=n;i++){
suml[i]=suml[i-1];
sumr[i]=sumr[i-1];
if(!a[i]) suml[i]++;
else sumr[i]++;
}
int ans=0;
for(int i=0;i<pos.size();i++){
int pre=qpow(2,suml[pos[i]]);
int suf=qpow(2,sumr[n]-sumr[pos[i]]);
int tmp=pre*suf%mod;
ans=(ans+tmp)%mod;
if(i!=pos.size()-1){
suf=qpow(2,sumr[n]-sumr[pos[i+1]]);
tmp=pre*suf%mod;
ans=(ans-tmp+mod)%mod;
}
}
ans=ans*qpow(qpow(2,n),mod-2)%mod;
cout<<ans<<endl;
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int t=1;
while(t--) solve();
return 0;
}
I, Box
题意
给你一个 \(n\times m\) 的二维平面,有若干个箱子和终点,每次可以选择一个箱子移动到相邻且没有障碍的位置上。问你是否存在一种操作序列使得所有箱子都移动到终点上。
思路
对于每个箱子都找一个没有箱子的终点,如果路径上有箱子阻挡了当前箱子移动,那就让挡路的箱子代替我当前的箱子移动,最后还是等价于把初始的箱子移动到目标点的。
代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
int dx[]={0,1,0,-1};
int dy[]={1,0,-1,0};
char op[]={'R','D','L','U'};
void solve(){
int n,m;
cin>>n>>m;
string mp[n+1];
for(int i=1;i<=n;i++){
cin>>mp[i];
mp[i]=' '+mp[i];
}
auto getid=[&](int x,int y){
return (x-1)*m+y;
};
auto getxy=[&](int id)->pair<int,int>{
int y=id%m;
if(y==0) y=m;
id-=y;
int x=id/m+1;
return {x,y};
};
struct node{
int x,y;
char op;
};
vector<node> ans;
auto bfs=[&](int sx,int sy){
vector<pair<int,int>> road;
vector<char> vis(n*m+1);
vector<int> pre(n*m+1);
queue<pair<int,int>> q;
q.push({sx,sy});
vis[getid(sx,sy)]=' ';
int flag=0;
int tx=0,ty=0;
while(q.size()){
auto [x,y]=q.front();
q.pop();
int now=getid(x,y);
for(int i=0;i<4;i++){
int nx=x+dx[i];
int ny=y+dy[i];
int nex=getid(nx,ny);
if(nx<1 || nx>n || ny<1 || ny>m || vis[nex] || mp[nx][ny]=='#') continue;
vis[nex]=op[i];
pre[nex]=now;
q.push({nx,ny});
if(mp[nx][ny]=='*'){
flag=1;
tx=nx,ty=ny;
}
if(flag) break;
}
if(flag) break;
}
if(!tx && !ty){
cout<<"-1"<<endl;
exit(0);
}
vector<char> tmp; //获得操作序列
while(tx!=sx || ty!=sy){
int now=getid(tx,ty);
tmp.push_back(vis[now]);
tx=getxy(pre[now]).first;
ty=getxy(pre[now]).second;
}
reverse(tmp.begin(),tmp.end());
vector<node> temp;
int x=sx,y=sy;
for(int i=0;i<tmp.size();i++){
int nx=x,ny=y;
if(tmp[i]=='R') ny++;
else if(tmp[i]=='L') ny--;
else if(tmp[i]=='U') nx--;
else if(tmp[i]=='D') nx++;
if(mp[nx][ny]=='@' || mp[nx][ny]=='!'){ //如果有箱子挡路
temp.push_back({x,y,tmp[i]});
}
else{
ans.push_back({x,y,tmp[i]});
while(temp.size()){
ans.push_back(temp.back());
temp.pop_back();
}
}
x=nx,y=ny;
}
mp[sx][sy]='.';
mp[x][y]='!';
};
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(mp[i][j]=='@') bfs(i,j);
}
}
cout<<ans.size()<<endl;
for(auto [x,y,opt]:ans){
cout<<x<<' '<<y<<' '<<opt<<endl;
}
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int t=1;
while(t--) solve();
return 0;
}

浙公网安备 33010602011771号