ICPC 2023 区域赛南京站题解 + 重现赛游记

重现赛传送门

前言 / 废话篇

image-20250904172806441

说实话,这场重现赛非常的简单难。

我(\(\color{orange}\text{He_XY}\)),和 \(\color{red}\text{UKBwyx}\) 大佬, \(\color{orange} \text{lucky_clover}\)大佬和 大佬,组队打了洛谷这场重现赛。

我们队拿到了 \(\text{luogu rank3}\),快为几位大佬鼓掌(bushi)。

由于图片过长,可以自行左转去 \(\text{luogu}\)排名反正我们 rank 3 就在第一页。不想左转的可以直行看表格。

名次 参赛者 总分 A B C D E
# \(3\) \(\color{orange}\text{He_XY}\)
& \(\color{red}\text{UKBwyx}\)
& \(\color{orange} \text{lucky_clover}\)
8
(\(16\):\(35\))
\(\bf{\color{green}{+}}\)
(\(01\):\(37\))
\(\bf{\color{green}{+}}\)
(\(00\):\(43\))
\(\bf{\color{green}{+}}\)
(\(04\):\(17\))
F G H I J K L M
\(\bf{\color{green}{+}}\)
(\(01\):\(16\))
\(\bf{\color{green}{+1}}\)
(\(00\):\(46\))
\(\bf{\color{green}{+}}\)
(\(00\):\(12\))
\(\bf{\color{green}{+5}}\)
(\(02\):\(15\))
\(\bf{\color{green}{+}}\)
(\(03\):\(25\))

本篇题解按照本队通过顺序编写

I - 计数器 / Counter

这题被我 \(12\text{min}\) 切掉了 QwQ

题意

本题多测。

有一个计数器,上面显示一个数字,初始为 \(0\),有两个键:c+,分别表示清零和 \(+1\)

现已知进行了 \(n\) 次操作,有 \(m\) 条已知信息,每条信息表示第 \(a_i\) 次操作后计数器上的数为 \(b_i\)

问是否存在一种操作方式满足所有已知条件。是输出 Yes,否输出 No

数据范围:\(1\le a_i\le n\le10^9,1\le m\le 10^5,0\le b_i\le 10^9\)

思路

场上唯一一道签到题。

首先按操作次序(\(a_i\))排序 ,然后判断相邻两个是否能够相互达到要求即可。

要求(对于每个 \(i\) 满足其一即可):

  • 若上次清空,\(a_i - a_{i-1}-1\ge b_i\)
  • 若上次不清空,\(a_i-a_{i-1}=b_i-b_{i-1}\)

若存在一个 \(i\) 的两个条件都不符合,就直接判定答案为 No,否则答案为 Yes

C++ 代码

#include<bits/stdc++.h>
using namespace std;
pair<int,int> v[100005];
void solve(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++) cin>>v[i].first>>v[i].second;
	sort(v+1,v+1+m);
	for(int i=1;i<=m;i++){
		if(v[i].first-v[i-1].first-1<v[i].second &&
           v[i].first-v[i-1].first!=v[i].second-v[i-1].second){
            //这两种只要满足一个即可,两个都是否定就直接输出No
			cout<<"No\n";
			return;
		}
	}
	cout<<"Yes\n";
}
int main(){int T;cin>>T; while(T--) solve(); return 0;}

C - 原根 / Primitive Root

题目

给定质数 \(P\) 和非负整数 \(m\),求有多少非负整数 \(g\) 满足 \(g \leq m\)\(g \oplus (P-1) \equiv 1 \pmod{P}\)

数据范围:\(2\le P \le 10^{18},0\le m\le 10^{18}\)

思路

首先引入一个不等式:\(a-b\le a\oplus b\le a+b\),证明应该非常好证吧……

由于 \(P\ge 2\),可以把原等式化简:\(g\oplus (P-1)=kP+1\),将异或号移到右边,得到 \(g=(kP+1)\oplus(P-1)\),所以 \((kP+1)-(P-1)\le g\le (kP+1)+(P-1)\),化简得 \((k-1)P+2\le g \le (k+1)P\)

所以 \(m<(k-1)P+2\)\(m>(k+1)P\) 这两部分 \(m\) 的值和答案无关,所以只要计算 \(m\)\(g\) 的上下界对答案的贡献即可。

\((k-1)P+2\le m \le (k+1)P\),所以 \(\dfrac{m}{p}-1 \le k\le \dfrac{m-2}{p}+1\),所以只要枚举这之间的 \(k\) 即可计算答案。

C++ 代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
	int T;
    cin>>T;
    while(T--){
    	ll p,m; cin>>p>>m;
        ll k=m/p+2;
        ll ans=k+1;
        for(ll i=k;i>=max(k-4,0ll);i--){
            if(( (i*p+1) ^ (p-1) )>m) ans--;
        }
        cout<<ans<<endl;
    }
    return 0;
}

G - 背包 / Knapsack

题意

你一共有 \(W\) 元。有 \(n\) 枚,第 \(i\) 个售价为 \(w_i\) 元,美丽度为 \(v_i\)。你可以提前免费拿走任意 \(k\) 个宝石,然后在剩下的宝石中买不超过 \(W\) 元的宝石,使得免费得到的和买的宝石的 总美丽度 最大,输出这个最大值。

注:每个宝石只能拿一次,且无需花完所有的钱。

数据范围:\(1\le n\le 5\cdot 10^3,1\le W\le 10^4,0\le k\le n\)

思路

这题也被我切了捏 QwQ*2。

首先把这些物品按照售价 \(w_i\) 升序排序,因为先把最贵的拿走一定不会更劣。设排完序的数组为 \(a\)

然后枚举分界点 \(i\)\(i\) 及以前的所有宝石都花钱买,从 \(i\) 以后的选择 \(v_i\) 最大的 \(k\) 个拿走,答案取 \(\max\)

注意还要考虑只赠送不花钱买的情况,需要单独再计算一次答案,取 \(\max\)

考虑一下 \(i\) 及以前的所有宝石都花钱买的做法:背包,最好使用一维背包,每次将第 \(i\) 个元素插入背包的 \(dp\) 数组。

再考虑 \(i\) 以后的做法:在最开始讲数组再复制一份,按照 \(v_i\) 降序排序,称这个排完序的数组为 \(b\)(注意,一定要记录原下标 \(id\))。当分界点枚举到 \(i\) 时,把 \(used_{a[i].id}\) 标为 \(1\),表示已用过,随后从前往后遍历 \(b\),取前 \(k\)\(used_{b[i].id}\)\(0\) 的即可。

C++ 代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=200005;
struct Knapsack{
	int w,v,id;
}a[maxn],b[maxn];
bool used[maxn];
void solve(){
	int n,W,k;
	cin>>n>>W>>k;
	for(int i=1;i<=n;i++){
		int x,y; cin>>x>>y;
		a[i]=b[i]={x,y,i};
		used[i]=0;
	}
	sort(a+1,a+1+n,[&](Knapsack x,Knapsack y){//a数组按照w_i升序排序
		return x.w<y.w;
	});
	sort(b+1,b+1+n,[&](Knapsack x,Knapsack y){//b数组按照v_i降序排序
		return x.v>y.v;
	});
	vector<int> f(W+1,0);//背包dp数组
	int res=0;
	for(int i=1;i<=n;i++){
		int mx=0;
		for(int j=W;j>=a[i].w;j--) f[j]=max(f[j],f[j-a[i].w]+a[i].v);
		for(int j=W;j>=0;j--) mx=max(mx,f[j]);
		used[a[i].id]=1;
		int ans=0,kd=0;//kd是一个计数器,记录已经计入答案的宝石个数
		if(n-i>=k){
			for(int j=1;j<=n;j++){
				if(kd==k) break;
				if(!used[b[j].id]){
					ans+=b[j].v;
					kd++;
				}
			}
		}
		res=max(res,ans+mx);//mx表示花钱买的最大价值,ans表示免费赠送的最大价值
	}
	int ans=0;
	for(int j=1;j<=k;j++) ans+=b[j].v;
    res=max(res,ans);
	cout<<res<<endl;
}
signed main(){solve(); return 0;}

F - 等价重写 / Equivalent Rewriting

题意

有一个初始全为 \(0\)、长度为 \(m\) 的序列 \(A\)。需依次执行 \(n\) 个操作,每个操作会将序列中若干指定位置的元素值改为该操作的序号 \(i\)。操作结束后得到结果序列 \(R\)

要求找到一个与原操作顺序不同的排列(即重新排列这 \(n\) 个操作的执行顺序),使得按新顺序执行操作后得到的结果序列仍为 \(R\)。若存在这样的排列则输出 Yes 及排列;否则输出 No

思路

发现题目这样问你,相当于问你:若 \(i\)\(j\) 有相同元素就连边,问是否存在唯一的拓扑序,也就是是否是一条链。直接隐式建图判链即可。

如果存在 \(i\)\(i+1\) 没有连边,就可以将 \(i\)\(i+1\) 交换输出

C++ 代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m;
int p[N];
vector<int> a[N];
bool exi[N];
int lst[N];
void solve(){
	for(int i=1;i<=m;i++) lst[i]=0;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>p[i];
		a[i].resize(p[i]);
		for(int &j:a[i]) cin>>j,lst[j]=i;
	}
	for(int i=1;i<n;i++){
		for(int j:a[i]) exi[j]=1;
		bool ok=1;
		for(int j:a[i+1]){
			if(lst[j]==i+1&&exi[j]){
				ok=0; break;
			}
		}
		for(int j:a[i]) exi[j]=0;
		if(ok){
			cout<<"Yes\n";
			for(int j=1;j<=i-1;j++) cout<<j<<' ';
			cout<<i+1<<' '<<i;
			for(int j=i+2;j<=n;j++) cout<<' '<<j;
			cout<<'\n';
			return ;
		}
	}
	cout<<"No\n";
}
int main(){int T; cin>>T; while(T--) solve();return 0;}

A - 酷,昨日四次重现 / Cool, It's Yesterday Four Times More

题意

给定一个 \(n\)\(m\) 列的网格,每个格子要么是洞(O)要么是空地(.)。每个空地上有一只袋鼠。通过输入操作序列(由 UDLR 组成)控制所有袋鼠同时移动。如果袋鼠移动后到达洞或网格外,则被移除。问有多少只袋鼠可能成为赢家(存在操作序列使得最终只剩该袋鼠)。

思路

所有袋鼠同时执行相同的操作序列。因此,袋鼠之间的相对位置在移动过程中保持不变。袋鼠所在的空地可能形成多个连通分量(通过上下左右相邻连接)。不同连通分量的袋鼠可能独立处理。对于某个连通分量中的袋鼠,如果该连通分量无法通过整体平移(保持相对位置)到另一个位置且所有点仍落在空地内,则该连通分量中的每只袋鼠都可能成为赢家。

首先对于每个未被访问的空地,\(\text{DFS}\) 找到所在连通分量的所有点,并记录这些点相对于起点的偏移量。对于每个连通分量,检查是否存在另一个起点(不同于当前起点)使得整个连通分量平移后所有点仍落在空地内。如果不存在这样的起点,则该连通分量中的所有袋鼠都可能成为赢家。最后将所有满足条件的连通分量的大小相加,得到最终答案。

C++ 代码

#include<bits/stdc++.h>
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
using namespace std;
long long t,n,m,sx,sy,fx[4]= {0,1,0,-1},fy[4]= {1,0,-1,0};
string a[1005];
bool vis[1005][1005];
vector<pii>v;
void f(int x,int y) {
    if(x<=0||y<=0||x>n||y>m||vis[x][y]||a[x][y]!='.')return;
    vis[x][y]=1;
    v.pb(mp(x-sx,y-sy));
    for(int i=0; i<4; i++) {
        int tx=x+fx[i],ty=y+fy[i];
        f(tx,ty);
    }
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>t;
    while(t--) {
        cin>>n>>m;
        for(int i=1; i<=n; i++) {
            cin>>a[i];
            a[i]=" "+a[i];
            for(int j=1; j<=m; j++) {
                vis[i][j]=0;
            }
        }
        long long ans=0;
        for(int i=1; i<=n; i++) {
            for(int j=1; j<=m; j++) {
                if(a[i][j]=='.'&&!vis[i][j]) {
                    v.clear();
                    sx=i;
                    sy=j;
                    f(i,j);
                    bool can1=1;
                    for(int k=1; k<=n; k++) {
                        for(int l=1; l<=m; l++) {
                            if(k==i&&l==j)continue;
                            bool can=1;
                            for(int o=0; o<v.size(); o++) {
                                int x=k+v[o].first,y=l+v[o].second;
                                if(x<=0||y<=0||x>n||y>m||a[x][y]!='.') {
                                    can=0;
                                    break;
                                }
                            }
                            if(can)can1=0;
                        }
                    }
                    if(can1)ans+=v.size();
                }
            }
        }
        cout<<ans<<'\n';
    }
    return 0;
}

L - 电梯 / Elevator

题意

\(n\) 组包裹需要配送。第 \(i\) 组有 \(c_i\) 个包裹,每个包裹重量 \(w_i\)\(1\)\(2\)),需要送到第 \(f_i\) 层。电梯每趟运送的总重量不能超过 \(k\)\(k\) 为偶数),消耗的电能为该趟包裹中最高楼层。求配送所有包裹所需的最少电能。

思路

每趟消耗的电能取决于该趟中包裹的最高楼层,因此应尽量避免低楼层包裹单独运输,而是将它们与高楼层包裹合并运输,从而减少总运输趟数而不增加电能消耗(因为合并后电能消耗仍为高楼层)。由于 \(k\) 可能很大,需要高效地组合包裹,尽可能填满电梯的容量。

将包裹组按楼层 \(f_i\) 从高到低排序,同一楼层的组按重量 \(w_i\) 从大到小排序。这样优先处理高楼层和重量大的包裹,便于后续贪心。

遍历排序后的包裹组,同时维护当前剩余容量 \(cur\)(表示电梯当前趟剩余可承载的重量)。

对于每组包裹,先用当前剩余容量尽可能多地装载该组包裹。如果当前剩余容量不足以装载整个组,且剩余容量大于 \(0\) 但小于当前包裹重量(即无法再装一个当前包裹),则尝试从低楼层组中找一个重量为1且包裹数量为奇数的组,从中取一个包裹填充剩余容量(这样既不增加电能消耗,又利用了剩余容量)。然后计算剩余包裹需要的趟数:每趟最多装载 \(\lfloor k / w_i \rfloor\) 个当前组包裹,每趟消耗电能为当前楼层 \(f_i\)。如果最后还有剩余包裹,则再安排一趟运输,并更新剩余容量 \(cur\)\(k - \text{剩余包裹数} \times w_i\)

C++ 代码

#include<bits/stdc++.h>
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
using namespace std;
long long t,n,k;
struct ab {long long c,w,f;};
ab a[100005];
bool cmp(ab x,ab y) {
    return x.f>y.f||(x.f==y.f&&x.w>y.w);
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>t;
    while(t--) {
        cin>>n>>k;
        for(int i=1; i<=n; i++) {
            cin>>a[i].c>>a[i].w>>a[i].f;
        }
        sort(a+1,a+n+1,cmp);
        long long ans=0,l=1,cur=0;
        for(int i=1; i<=n; i++) {
            long long t=a[i].c,t1=cur/a[i].w;
            if(t1>=t) {
                cur-=t*a[i].w;
            } else {
                a[i].c-=t1;
                cur-=t1*a[i].w;
                if(cur) {
                    l=max(l,i+1ll);
                    while(l<=n&&(a[l].w==2||a[l].c%2==0))l++;
                    if(l<=n) {
                        cur=0;
                        a[l].c--;
                    }
                }
                long long t3=a[i].c/(k/a[i].w);
                ans+=a[i].f*t3;
                a[i].c-=(k/a[i].w)*t3;
                if(a[i].c) {
                    ans+=a[i].f;
                    cur=k-a[i].c*a[i].w;
                    continue;
                }
            }
        }
        cout<<ans<<"\n";
    }
    return 0;
}

M - 接雨水 / Trapping Rain Water

题意

给定一个长度为 \(n\) 的序列 \(a_1, a_2, \cdots, a_n\),表示柱状图中各柱子的高度。进行 \(q\) 次修改,每次修改将第 \(x_i\) 根柱子的高度增加 \(v_i\)。每次修改后,需要计算柱状图中能留存多少雨水。雨水的计算方式为:对于每个位置 \(i\),计算 \(\min(f_i, g_i) - a_i\) 并求和,其中 \(f_i = \max(a_1, a_2, \cdots, a_i)\) 是前缀最大值,\(g_i = \max(a_i, a_{i+1}, \cdots, a_n)\) 是后缀最大值。

思路

通过观察发现,雨水存量可以通过以下公式计算:

\[ans = \sum_{i=1}^n f_i + \sum_{i=1}^n g_i - n \times\max(a_1, a_2, \cdots, a_n) - \sum_{i=1}^n a_i \]

\(s1 = \sum_{i=1}^n f_i\),即所有前缀最大值之和;\(s2 = \sum_{i=1}^n g_i\),即所有后缀最大值之和;\(A = \max(a_1, a_2, \cdots, a_n)\),即序列中的最大值;\(sum= \sum_{i=1}^n a_i\),即序列所有元素之和。

每次修改后,需要动态更新:

\(sum\) 直接加上 \(v_i\)。检查更新后的 \(a[x_i]\) 是否大于当前 \(A\),如果是则更新\(A\)\(s1\)\(s2\) 是难点,需要高效维护,使用两个 set 来维护前缀最大值和后缀最大值的分段信息:

  • 前缀分段:使用 pr 存储三元组 (l, r, val),表示区间 \([l, r]\) 内的前缀最大值均为 valset 按照 l 从小到大排序。
  • 后缀分段:使用 sf 存储三元组 (l, r, val),表示区间 \([l, r]\) 内的后缀最大值均为 valset 按照 r 从大到小排序。

每次修改后,检查修改位置 \(x\) 的值是否大于其所在分段的前缀最大值(或后缀最大值)。如果是,则合并后续分段中值小于 \(a_x\) 的分段,并更新 \(s1\)(或 \(s2\))。合并过程中,可能需要将原有分段分裂为多个分段。

C++ 代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n,q;
ll a[N],pre[N],rep[N],A,sum1,sum2,sum;
struct node1{
	int l,r; ll val;
	node1(){l=r=val=0;}
	node1(int L,int R,ll A){l=L;r=R;val=A;}
	bool operator<(const node1 &b)const{return l<b.l;}
};
struct node2{
	int l,r; ll val;
	node2(){l=r=val=0;}
	node2(int L,int R,ll A){l=L;r=R;val=A;}
	bool operator<(const node2 &b)const{return r>b.r;}
};
void solve(){
	cin>>n;
	pre[0]=rep[n+1]=A=sum1=sum2=sum=0;
	for(int i=1;i<=n;i++) cin>>a[i],sum+=a[i];
	for(int i=1;i<=n;i++) pre[i]=max(pre[i-1],a[i]),sum1+=pre[i];
	for(int i=n;i>=1;i--) rep[i]=max(rep[i+1],a[i]),sum2+=rep[i];
	A=pre[n];
	set<node1> pr; set<node2> sf;
	for(int i=1;i<=n;i++) pr.insert(node1(i,i,pre[i])),sf.insert(node2(i,i,rep[i]));
	cin>>q;
	auto workPre=[&](int i,ll pre){
		auto it=prev(pr.upper_bound(node1(i,i,0)));
		int l=it->l,r=-1; ll v=it->val;
		if(pre<=v) return ;
		while(it!=pr.end()&&it->val<pre){
			sum1+=((it->r)-(it->l)+1)*(pre-(it->val));
			r=it->r; it=pr.erase(it);
		}
		if(r==-1) return ;
		sum1-=(i-l)*(pre-v);
		pr.insert(node1(i,r,pre));
		if(i!=l) pr.insert(node1(l,i-1,v));
	};
	auto workSuf=[&](int i,ll suf){
		auto it=prev(sf.upper_bound(node2(i,i,0)));
		int r=it->r,l=-1; ll v=it->val;
		if(suf<=v) return ;
		while(it!=sf.end()&&it->val<suf){
			sum2+=((it->r)-(it->l)+1)*(suf-(it->val));
			l=it->l; it=sf.erase(it);
		}
		if(l==-1) return ;
		sum2-=(r-i)*(suf-v);
		sf.insert(node2(l,i,suf));
		if(r!=i) sf.insert(node2(i+1,r,v));
	};
	while(q--){
		int x,v;
		cin>>x>>v;
		a[x]+=v; sum+=v;
		A=max(A,a[x]);
		workPre(x,a[x]);
		workSuf(x,a[x]);
		cout<<sum1+sum2-n*A-sum<<'\n';
	}
}
int main(){int T; cin>>T; while(T--) solve();return 0;}

D - 红黑树 / Red Black Tree

题意

给定一颗有根树,每个节点是黑色或红色,你每次可以,对于每个节点,你需要求出让该节点的子树内每条从该节点到叶子的路径的长度都相等的最小代价。

思路

一种完全和凸包没有关系的做法。

首先我们设 \(dp_{u,i}\) 表示使节点 \(u\) 的子树内每条从 \(u\) 到叶子的路径的长度都是 \(i\) 的最小代价。

\(h_u\)\(u\) 节点到子树内叶子的最短距离,我们不难发现 \(dp_{u,i}\)\(i\le h_u\)

发现如果出一条链我们就炸掉了。

于是我们对一条链上的点特殊处理,对于一条链,我们只在乎上面的黑点和红点的数量。而对于一条链上的点,它的答案和它唯一的子树的答案是相同的。

然后随便搞个线段树就做完了,不用线段树可能也行,时间复杂度为 \(\mathcal O(\displaystyle\sum_{d_u\ge2} h_ud_u\log n)\) ,简单分析一下可以发现这东西是不超过 \(\mathcal O(\displaystyle\sum_{d_u\ge2} (\sum_{(u,v)\in e} h_v-\max_{(u,v)\in e}h_v)\log n)\) 的,也就是不超过长链的长度总和的时间复杂度,即是 \(\mathcal O(n\log n)\) 的。

C++ 代码

注:由于赛时的代码码风太...了,防止读者看不懂,本代码已经使用 AI 改变了码风

#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int> pii;
const int MAXN = 100005;
const long long INF = 1LL << 29;

int testCases, n;
int parent[MAXN];
int minDepth[MAXN];
int count1[MAXN]; // 计数'1'
int count0[MAXN]; // 计数'0'
int result[MAXN];
vector<int> children[MAXN];
vector<int> dp[MAXN]; // 动态规划状态
bool visited[MAXN];
pii maxValues[MAXN];
string s;
vector<int> tempArray;

// 线段树结构,用于高效区间更新
struct SegmentTree {
    int tree[MAXN << 2];
    int updateValue, queryResult;
    int updateLeft, updateRight;
    
    // 区间更新
    void updateRange(int l, int r, int idx) {
        if (updateLeft <= l && r <= updateRight) {
            tree[idx] = min(tree[idx], updateValue);
        } else {
            int mid = (l + r) >> 1;
            int leftChild = idx << 1;
            int rightChild = idx << 1 | 1;
            if (updateLeft <= mid) updateRange(l, mid, leftChild);
            if (updateRight > mid) updateRange(mid + 1, r, rightChild);
        }
    }
    
    // 执行更新操作
    inline void applyUpdate(int l, int r, int value) {
        if (l > r) return;
        updateLeft = l;
        updateRight = r;
        updateValue = value;
        updateRange(0, n, 1);
    }
    
    // 清除线段树数据到临时数组
    void clearData(int l, int r, int idx) {
        if (l == r) {
            tempArray[l] = tree[idx];
        } else {
            int mid = (l + r) >> 1;
            int leftChild = idx << 1;
            int rightChild = idx << 1 | 1;
            tree[leftChild] = min(tree[leftChild], tree[idx]);
            tree[rightChild] = min(tree[rightChild], tree[idx]);
            if (updateLeft <= mid) clearData(l, mid, leftChild);
            if (updateRight > mid) clearData(mid + 1, r, rightChild);
        }
        tree[idx] = INF;
    }
    
    // 执行清除操作
    inline void applyClear(int l, int r) {
        if (l > r) return;
        updateLeft = l;
        updateRight = r;
        clearData(0, n, 1);
    }
};

SegmentTree segTree1, segTree2;

// DFS遍历树并计算结果
void dfs(int node) {
    for (int i = 0; i < children[node].size(); i++) {
        int child = children[node][i];
        dfs(child);
    }
    
    minDepth[node]--;
    
    if (node == 1 || children[node].size() != 1) {
        dp[node].clear();
        dp[node].resize(minDepth[node] + 1, 0);
        tempArray.resize(minDepth[node] + 1);
        
        for (int i = 0; i < children[node].size(); i++) {
            int child = children[node][i];
            int cnt1 = count1[child];
            int cnt0 = count0[child];
            
            for (int j = 0; j < dp[child].size(); j++) {
                int value = dp[child][j];
                int position = j + cnt1;
                
                segTree1.applyUpdate(position, min(position + cnt0, minDepth[node]), value - position);
                segTree2.applyUpdate(max(position - cnt1, 0), min(position, minDepth[node]), value + position);
            }
            
            segTree1.applyClear(0, minDepth[node]);
            vector<int> temp1 = tempArray;
            
            segTree2.applyClear(0, minDepth[node]);
            
            for (int j = 0; j <= minDepth[node]; j++) {
                dp[node][j] += min(temp1[j] + j, tempArray[j] - j);
            }
        }
        
        result[node] = INF;
        for (int j = 0; j <= minDepth[node]; j++) {
            result[node] = min(result[node], dp[node][j]);
        }
        
        count1[node] = count0[node] = 0;
        if (s[node] == '1') count1[node]++;
        else count0[node]++;
        
    } else {
        int child = children[node][0];
        swap(dp[node], dp[child]);
        count1[node] = count1[child];
        count0[node] = count0[child];
        result[node] = result[child];
        
        if (s[node] == '1') count1[node]++;
        else count0[node]++;
    }
}

int main() {
    cin >> testCases;
    while (testCases--) {
        cin >> n >> s;
        tempArray.resize(n + 1);
        segTree1.applyClear(0, n);
        segTree2.applyClear(0, n);
        
        s = " " + s;
        
        for (int i = 1; i <= n; i++) {
            children[i].clear();
            minDepth[i] = INF;
            count1[i] = count0[i] = 0;
            maxValues[i] = make_pair(0, 0);
        }
        
        for (int i = 2; i <= n; i++) {
            cin >> parent[i];
            children[parent[i]].push_back(i);
        }
        
        // 计算每个节点到叶子的最小深度
        for (int i = n; i > 1; i--) {
            if (children[i].size() == 0) minDepth[i] = 1;
            minDepth[parent[i]] = min(minDepth[parent[i]], minDepth[i] + 1);
        }
        
        dfs(1);
        
        for (int i = 1; i <= n; i++) {
            cout << result[i];
            if (i != n) cout << " ";
        }
        cout << "\n";
    }
    return 0;
}

原来的代码:

#include<bits/stdc++.h>
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
using namespace std;
int n,t,p[100005],h[100005],cnt[100005],cnt1[100005],ans[100005];
vector<int>u[100005];
vector<int>ljb[100005];
bool b[100005];
pii ma[100005];
string s;
vector<int>qwq;
struct xds {
    int v[400005],v1,ans;
    int l1,r1;
    void change1(int l,int r,int wz) {
        if(l1<=l&&r<=r1) {
            v[wz]=min(v[wz],v1);
        } else {
            int mid=l+r>>1,lt=wz<<1,rt=wz<<1|1;
            if(l1<=mid)change1(l,mid,lt);
            if(mid+1<=r1)change1(mid+1,r,rt);
        }
    }
    inline void change(int l,int r,int v) {
        if(l>r)return;
        l1=l;
        r1=r;
        v1=v;
        change1(0,n,1);
    }
    void clr1(int l,int r,int wz) {
        if(l==r) {
            qwq[l]=v[wz];
        } else {
            int mid=l+r>>1,lt=wz<<1,rt=wz<<1|1;
            v[lt]=min(v[lt],v[wz]);
            v[rt]=min(v[rt],v[wz]);
            if(l1<=mid)clr1(l,mid,lt);
            if(mid+1<=r1)clr1(mid+1,r,rt);
        }
        v[wz]=1ll<<29;
    }
    inline void clr(int l,int r) {
        if(l>r)return;
        l1=l;
        r1=r;
        clr1(0,n,1);
    }
};
xds c,c1;
void f(int wz) {
    for(int i=0; i<ljb[wz].size(); i++) {
        int t=ljb[wz][i];
        f(t);
    }
    h[wz]--;
    if(wz==1||ljb[wz].size()!=1) {
        u[wz].clear();
        u[wz].resize(h[wz]+1,0);
        qwq.resize(h[wz]+1);
        for(int i=0; i<ljb[wz].size(); i++) {
            int t=ljb[wz][i];
            int t1=cnt[t],t2=cnt1[t];
            for(int j=0; j<u[t].size(); j++) {
                int v=u[t][j],w=j+t1;
                c.change(w,min(w+t2,h[wz]),v-w);
                c1.change(max(w-t1,0),min(w,h[wz]),v+w);
            }
            c.clr(0,h[wz]);
            vector<int>qwq1=qwq;
            c1.clr(0,h[wz]);
            for(int j=0; j<=h[wz]; j++) {
                u[wz][j]+=min(qwq1[j]+j,qwq[j]-j);
            }
        }
        ans[wz]=1ll<<29;
        for(int j=0; j<=h[wz]; j++) {
            ans[wz]=min(ans[wz],u[wz][j]);
        }
        cnt[wz]=cnt1[wz]=0;
        if(s[wz]=='1')cnt[wz]++;
        else cnt1[wz]++;
    } else {
        int t=ljb[wz][0];
        swap(u[wz],u[t]);
        cnt[wz]=cnt[t];
        cnt1[wz]=cnt1[t];
        ans[wz]=ans[t];
        if(s[wz]=='1')cnt[wz]++;
        else cnt1[wz]++;
    }
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>t;
    while(t--) {
        cin>>n>>s;
        qwq.resize(n+1);
        c.clr(0,n);
        c1.clr(0,n);
        s=" "+s;
        for(int i=1; i<=n; i++) {
            ljb[i].clear();
            h[i]=1ll<<29;
            cnt[i]=cnt1[i]=0;
            ma[i]=mp(0,0);
        }
        for(int i=2; i<=n; i++) {
            cin>>p[i];
            ljb[p[i]].pb(i);
        }
        for(int i=n; i>1; i--) {
            if(ljb[i].size()==0)h[i]=1;
            h[p[i]]=min(h[p[i]],h[i]+1);
        }
        f(1);
        for(int i=1; i<=n; i++) {
            cout<<ans[i];
            if(i!=n)cout<<" ";
        }
        cout<<"\n";
    }
    return 0;
}
posted @ 2025-09-05 09:16  AKDreamer_HeXY  阅读(67)  评论(1)    收藏  举报