牛客 周赛67 20250525
牛客 周赛67 20250525
https://ac.nowcoder.com/acm/contest/95016
A:
题目大意:对给定的字符串排序,需要满足小写字母在前,数字在中间,大写字母最后的顺序,且对于相同类型的字符不要改变他们之间的相对位置
void solve(){
int n;
cin>>n;
string s;
cin>>s;
vector<char> a,b,c;
for (auto it:s){
if (it>='a'&&it<='z') a.push_back(it);
if (it>='0'&&it<='9') b.push_back(it);
if (it>='A'&&it<='Z') c.push_back(it);
}
for (auto it:a) cout<<it;
for (auto it:b) cout<<it;
for (auto it:c) cout<<it;
}
用 vector
分类存再输出
B:
题目大意:给定 \(a,b,c,d\) 求满足 \(k/b<c/d\) 的 \(k\) 最大整数解,输出 \(a-k\)
void solve(){
int n;
cin>>n;
for (int i=1;i<=n;i++){
LL a,b,c,d;
cin>>a>>b>>c>>d;
LL k=c*b;
if (k%d) cout<<a-k/d<<' ';
else cout<<a-k/d+1<<' ';
}
}
公式变换 \(k<c*b/d\) 需要注意的是如果 \(c*b\) 能被 \(d\) 整除,则 \(k\) 需要减一,因为求的是严格小于的最大整数解
C:
题目大意:给定长 \(n\) 的字符串,形如 \(A+B=C\) 的加法运算式,给定 \(C\) 计算有多少不重复且满足等式的字符串
void solve(){
int n,c;
cin>>n>>c;
int dc=log10(c)+1;
n=n-dc-2;
int ans=0;
for (int i=0;i<=c;i++){
int dx=log10(i)+1,dy=log10(c-i)+1;
if (i==0) dx=1;
if (i==c) dy=1;
if (dx+dy==n) ans++;
}
cout<<ans;
}
利用 log10()
函数计算位数,减掉 \(C\) 和运算符的位数后,剩下的就是 \(A,B\) 所占的位数,遍历一遍记录答案即可
特别的当 \(A,B\) 为 \(0\) 时,利用对数计算会出错,所以特判为 \(1\)
D:
题目大意:
void solve(){
int n,k;
cin>>n>>k;
if (k>n) cout<<"NO";
else if (k==n){
cout<<"YES"<<endl;
for (int i=0;i<n;i++) cout<<1<<' ';
}else{
cout<<"YES"<<endl;
vector<int> ans(n);
for (int i=0;i<n;i++) ans[i]=i%2;
int cnt=n-1;
for (int i=0;i<n;i++){
if (cnt<=k) break;//当cnt<=k 时中断-2操作
if (ans[i]==1){
ans[i]=0;
cnt-=2;
}
}
if (cnt<k) ans[0]=1;//如果cnt<k 则需要补1
for (auto it:ans) cout<<it<<' ';
}
}
如果数组中所有元素都相同,那么极大不同区间的长度为 \(1\) ,则最大数量为数组长度 \(n\) ,所以当 \(k>n\) 时无解
\(k<n\) 时有以下构造:\(1,0,1,\cdots\) 这样交错排列的数组的极大不同区间个数为 \(10,01\) 区间的个数
如果需要减少区间个数可以做以下修改, \(0,1,0\) 改为 \(0,0,0\) 这样会导致两个区间消失,即对答案的贡献为 \(-2\)
记初始数组中的极大不同区间数量为 \(x\) ,为了使得 \(x=k\) ,可以做上述修改,设修改次数为 \(y\)
修改后的区间数量为 \(x-2y\) ,如果 \(k-x\) 不能被 \(2\) 整除,则需要额外修改头元素为 \(1\) ,构成一个单独的 \(10\) 区间,从而使答案增加 \(1\)
E:
题目大意:
int dp[20][200];
int dfs(int pos,bool limup,bool limdm,int sum,int a[],int b[]){
if (pos==0) return sum;
if (!limup&&!limdm&&dp[pos][sum]!=-1) return dp[pos][sum];
int res=0;
int kup=limup?a[pos]:9;
int kdm=limdm?b[pos]:0;
for (int i=kdm;i<=kup;i++){
res=max(res,dfs(pos-1,limup&&i==kup,limdm&&i==kdm,sum+i,a,b));
}
if (!limup&&!limdm) dp[pos][sum]=res;
return res;
}
int work(LL l,LL r){
int idx=0,a[20]={0},b[20]={0};
memset(dp,-1,sizeof dp);
while(l){
b[++idx]=l%10;
l/=10;
}
idx=0;
while(r){
a[++idx]=r%10;
r/=10;
}
return dfs(idx,1,1,0,a,b);
}
void solve(){
LL l,r,ll,rr;
cin>>l>>r>>ll>>rr;
cout<<work(l+ll,r+rr)<<endl;
}
在 \([l_1,r_1],[l_2,r_2]\) 中分别选取两个数相加求和等价于在 \([l_1+l_2,r_1+r_2]\) 中选取一个数
记忆化搜索实现的数位DP,传递的限制需要设置两个边界其余套路就行
F:
题目大意:
struct edge{
int v,w;
};
vector<edge> e[100010];
int n;
int dep[100010],mxdep[100010];
int dfn[100010],idx;
vector<int> st[100010];
int l[100010],r[100010];
LL val[100010];
vector<vector<vector<LL>>> stb;
void num(int x,int p){
dfn[x]=++idx;
l[x]=idx;
for (auto [v,w]:e[x]){
if (v==p) continue;
num(v,x);
}
r[x]=idx;
}
void dfs(int x,int p){
for (auto [v,w]:e[x]){
if (v==p) continue;
val[dfn[v]]=val[dfn[x]]+w;
dep[dfn[v]]=dep[dfn[x]]+1;
st[dep[dfn[v]]].push_back(dfn[v]);
dfs(v,x);
mxdep[dfn[x]]=max(mxdep[dfn[x]],mxdep[dfn[v]]+1);
}
}
void build(int dp){
if (st[dp].empty()) return;
stb[dp].resize(st[dp].size()+1);
int mxlog=log2(st[dp].size());
for (int i=0;i<st[dp].size();i++){
stb[dp][i+1].resize(mxlog+1);
stb[dp][i+1][0]=val[st[dp][i]];
}
for (int j=1;j<=mxlog;j++)
for (int i=1;i+(1<<j)-1<=st[dp].size();i++)
stb[dp][i][j]=max(stb[dp][i][j-1],stb[dp][i+(1<<(j-1))][j-1]);
}
void solve(){
cin>>n;
for (int i=1;i<n;i++){
int u,v,w;
cin>>u>>v>>w;
e[u].push_back({v,w});
e[v].push_back({u,w});
}
num(1,0);
dfs(1,0);
stb.resize(mxdep[1]+1);
for (int i=0;i<=mxdep[1];i++) build(i);
int q;
cin>>q;
while (q--){
int s,d;
cin>>s>>d;
int dv=dep[dfn[s]]+d;
if(d>mxdep[dfn[s]]){
cout<<-1<<endl;
continue;
}
auto lp=upper_bound(st[dv].begin(),st[dv].end(),l[s]);
auto rp=upper_bound(st[dv].begin(),st[dv].end(),r[s])-1;
int ll=lp-st[dv].begin()+1;
int rr=rp-st[dv].begin()+1;
int k=log2(rr-ll+1);
cout<<max(stb[dv][ll][k],stb[dv][rr-(1<<k)+1][k])-val[dfn[s]]<<endl;
}
}
int main()
{
cintie;
solve();
return 0;
}
题意很明确,数据范围较大所以无法通过动态规划进行预处理,考虑数据结构解决
对于某个节点 \(u\) 到他某个子节点 \(v\) 的路径权值可以被表示为 \(v\) 到根节点的路径权值减去 \(u\) 到根节点的路径权值
这一部分的权值处理可以通过一次 DFS 处理出来,现在需要考虑如何处理询问,即如何选出 \(u\) 的某个子节点使得路径权值最大
先判断特殊情况,当向下走的深度 \(d\) 大于 \(u\) 节点子树的最大深度时显然无解
void num(int x,int p){
dfn[x]=++idx;
l[x]=idx;
for (auto [v,w]:e[x]){
if (v==p) continue;
num(v,x);
}
r[x]=idx;
}
第一步预处理出每一个节点的 DFN 序和子树的左右 DFN 边界,必要性后续会提及,规定下文的节点表示节点映射的 DFN 序
void dfs(int x,int p){
for (auto [v,w]:e[x]){
if (v==p) continue;
val[dfn[v]]=val[dfn[x]]+w;
dep[dfn[v]]=dep[dfn[x]]+1;
st[dep[dfn[v]]].push_back(dfn[v]);
dfs(v,x);
mxdep[dfn[x]]=max(mxdep[dfn[x]],mxdep[dfn[v]]+1);
}
}
DFS 过程中将同一深度的节点存进 st
数组内,以及处理节点到根节点的权值 \(val\) 和节点子树的最大深度
因为要计算某个节点 \(s\) 向下 \(d\) 深度的最大路径权值,可以利用 ST 表对询问进行 \(O(1)\) 回答
stb.resize(mxdep[1]+1);
for (int i=0;i<=mxdep[1];i++) build(i);
void build(int dp){
if (st[dp].empty()) return;
stb[dp].resize(st[dp].size()+1);
int mxlog=log2(st[dp].size());
for (int i=0;i<st[dp].size();i++){
stb[dp][i+1].resize(mxlog+1);
stb[dp][i+1][0]=val[st[dp][i]];
}//倍增处理st表
for (int j=1;j<=mxlog;j++)
for (int i=1;i+(1<<j)-1<=st[dp].size();i++)
stb[dp][i][j]=max(stb[dp][i][j-1],stb[dp][i+(1<<(j-1))][j-1]);//预处理区间查询
}
对每个深度内的节点都预处理出区间内到根节点的最大路径权值(该深度存在点才预处理)
stb[i][j][k]
表示深度为 \(i\) 的节点集合,区间为 \([j,k]\) 内的点到根节点的最大路径长度
最后开始处理询问:
auto lp=upper_bound(st[dv].begin(),st[dv].end(),l[s]);//在深度dv的节点内可达节点的左端点
auto rp=upper_bound(st[dv].begin(),st[dv].end(),r[s])-1;//右端点
首先二分计算出询问节点 \(s\) 在深度为 \(dv=dep_s+d\) 内可达的节点左右区间
使用二分的前提是满足单调性,因为第一步就对所有节点都预处理了一次 DFN 序,所以根据 DFN 序的特性, st
内的所有点都以 DFN 序的大小从小到大排序
我们在 \(O(\log n)\) 的时间复杂度内,计算出了在 \(dv\) 深度下,\(s\) 可达的节点有哪些,且这些节点编号构成一个连续区间!
int ll=lp-st[dv].begin()+1;//左区间
int rr=rp-st[dv].begin()+1;//右区间
int k=log2(rr-ll+1);
cout<<max(stb[dv][ll][k],stb[dv][rr-(1<<k)+1][k])-val[dfn[s]]<<endl;//st表回答
之后再利用 ST 表 \(O(1)\) 回答,总时间复杂度为 \(O(q\log n)\)