牛客 周赛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)\)

posted @ 2025-06-04 18:27  才瓯  阅读(10)  评论(0)    收藏  举报