ICPC2023南京站题解(A C D F G I L M)

本场金牌线为7题前一半。做出8题可稳金牌,这里是难度前8题的题解。

ICPC2023南京站

I:

签到题。

#include <bits/stdc++.h>
#define ll long long
#define QWQ cout<<"QwQ"<<endl;
#define FOR() ll le=e[u].size();for(ll i=0;i<le;i++)
using namespace std;
const ll N=501010;

inline ll read() {
	ll sum = 0, ff = 1; char c = getchar();
	while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
	while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
	return sum * ff;
}

int T;
int n,m;
struct E{
	int wei,zhi;
}a[N];
inline bool cmp(E A,E B) { return A.wei<B.wei; }


int main() {
	T = read();
	while(T--) {
		bool flag = 1;
		n = read(); m = read();
		for(int i=1;i<=m;i++) {
			a[i].wei = read(); a[i].zhi = read();
		}
		sort(a+1,a+m+1,cmp);
		for(int i=1;i<=m;i++) {
			if(a[i].wei-a[i-1].wei==a[i].zhi-a[i-1].zhi) continue;
			if(a[i].zhi<a[i].wei-a[i-1].wei) continue;
			flag = 0;
			break;
		}
		if(flag) cout<<"Yes\n";
		else cout<<"No\n";
	}
	return 0;
}

F:

等价重写,可以轻松地想到本题和图论有关,每个操作修改的数字是独一无二的,因此每个覆盖事件都可以产生一个先后关系,我们可以将某个位置先写入的数字连向后写入的数字,在拓扑排序中就可以表示先写入较早的数字。

我WA了一发哈哈,因为没考虑完整,连边只需要连向每个位置最后覆盖的数字就行,之前谁把谁覆盖没有关系。

#include <bits/stdc++.h>
#define ll long long
#define QWQ cout<<"QwQ"<<endl;
#define FOR() ll le=e[u].size();for(ll i=0;i<le;i++)
using namespace std;
const ll N=501010;

inline ll read() {
	ll sum = 0, ff = 1; char c = getchar();
	while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
	while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
	return sum * ff;
}


int T;
int n,m;
int a[N];
int ru[N];
vector <int> e[N];
int ans[N],cnt;
priority_queue <int> q;
vector <int> g[N];

void chushihua() {
	for(ll i=1;i<=n;i++) ru[i] = 0, e[i].clear();
	for(ll i=1;i<=m;i++) g[i].clear();
	cnt = 0;
}

int main() {
	int x,y;
	T = read();
	while(T--) {
		chushihua();
		n = read(); m = read();
		for(int i=1;i<=n;i++) {
			x = read();
			while(x--) {
				y = read();
				g[y].push_back(i);
			}
		}
		for(int i=1;i<=m;i++) {
			if(g[i].size()==0) continue;
			int hou = g[i].back();
			for(auto v : g[i]) if(v != hou) e[v].push_back(hou), ru[hou]++;
		}
		for(int i=1;i<=n;i++) if(!ru[i]) q.push(i);
		while(!q.empty()) {
			int u = q.top(); q.pop();
			ans[++cnt] = u;
			FOR() {
				int v = e[u][i];
				ru[v]--;
				if(ru[v]==0) q.push(v);
			}
		}
		bool tong = 1;
		for(int i=1;i<=cnt;i++) if(i!=ans[i]) tong = 0;
		if(tong) {
			cout<<"No\n";
			continue;
		}
		cout<<"Yes\n";
		for(int i=1;i<=n;i++) cout<<ans[i]<<" \n"[i==n];
	}
	return 0;
}

C:

官方题解中给出了两种解法,都有思考价值。

解法一:

异或运算满足不等式:\(a-b\leq a\oplus b \leq a+b\),那么我们把题目中的 g 写出来:\(g=(kP+1)\oplus(P-1)\),它也满足不等式:\((k-1)P+2\leq (kP+1)\oplus(P-1)\leq (k+1)P\)

这个不等式能告诉我们什么?虽然我们的 \((kP+1)\) 异或上了 \((P-1)\) 使得结果 g 的具体值不确定,但是 g 是有取值范围的,这个范围的大小不超过2P,随着 \(k\) 的增长,g 的取值范围左右各增加一个 P,也就是说,g 的取值范围接近上限 m 时,k 的取值就在 \(\lfloor\frac mP\rfloor\) 左右,小于这个值时,g 一定比 m 小,大于这个值时 g 一定比 m 大,在 \(\lfloor\frac mP\rfloor\) 左右时特判即可。

解法二:

这个做法是比较无脑且科技的,比较推荐:

将 [l,r] 这个区间的所有整数异或上一个值 X ,可以得到 log 段连续的区间。[0,m] 这个区间异或上 (P-1),每一段判断有多少 (kP+1) 即可。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=101010;

inline ll read() {
	ll sum = 0, ff = 1; char c = getchar();
	while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
	while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
	return sum * ff;
}

ll T;
ll m,P;
struct D{
	ll l,r;
}d[N];
inline bool cmp(D A,D B) { return A.l < B.l; }
ll cnt;

void get_fen(ll R,ll X) {
	ll da = 1ll << 60ll;
	d[cnt=1] = {0,R};
	for(ll i=60;i>=0;i--,da>>=1) {
		if(d[1].l+da<=d[1].r) {
			if((X>>i)&1) {
				d[++cnt] = {d[1].l+da,d[1].l+2*da-1};
				d[1].r -= da;
			}
			else {
				d[++cnt] = {d[1].l,d[1].l+da-1};
				d[1].l += da;
			}
		}
		else if((X>>i)&1) d[1].l += da, d[1].r += da;
	}
	sort(d+1,d+cnt+1,cmp);
	// for(ll i=1;i<=cnt;i++) cout<<d[i].l<<" "<<d[i].r<<"\n";
}

inline ll ask(ll R) {
	if(R<=0) return 0;
	return (R-1)/P+1;
}

int main()  {
	T = read();
	while(T--) {
		P = read(); m = read();
		get_fen(m,P-1);
		ll ans = 0;
		for(ll i=1;i<=cnt;i++) {
			ans += ask(d[i].r);
			ans -= ask(d[i].l-1);
		}
		cout<<ans<<"\n";
	}

	return 0;
}

G:

可以免费选物品的背包,总感觉哪里做过。

假如从珠宝店出来,你拿着一些物品,把这些物品分为两类,一类是你买的,一类是你免费选的,那么可以肯定的是,你免费选的那些物品,一定是价格比较高的,这样省钱。

所以这两类物品是存在一个分界线的,是按价格排序的分界线。

枚举一个价格,设免费选的都高于这个价格,买来的都低于这个价格,那就高于这个价格的优先选价值高的,低于这个价格的用背包。

前者优先队列,后者朴素背包。

#include <bits/stdc++.h>
#define ll long long
#define QWQ cout<<"QwQ"<<endl;
#define FOR() ll le=e[u].size();for(ll i=0;i<le;i++)
using namespace std;
const ll N=501010;

inline ll read() {
	ll sum = 0, ff = 1; char c = getchar();
	while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
	while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
	return sum * ff;
}

ll n,W,K;
struct E{
	ll w,v;
}a[N];
inline bool cmp(E A,E B) { return A.w > B.w; }
priority_queue <ll, vector<ll>, greater<ll> > q;
ll qian[N];
ll f[N];

int main() {
	n = read(); W = read(); K = read();
	for(ll i=1;i<=n;i++) {
		a[i].w = read(); a[i].v = read();
	}
	sort(a+1,a+n+1,cmp);
	ll now = 0;
	for(ll i=1;i<=n;i++) {
		now += a[i].v;
		q.push(a[i].v);
		if(i>K) now -= q.top(), q.pop();
		qian[i] = now;
	}
	ll ans = 0;
	for(ll i=n;i>=1;i--) {
		ll da = 0;
		for(ll j=W;j>=a[i].w;j--) {
			f[j] = max(f[j], f[j-a[i].w] + a[i].v);
			da = max(f[j], da);
		}
		ans = max(ans,da+qian[i-1]);
	}
	cout<<ans;
	return 0;
}

A:

南京站四次重现,还挺酷的。

题目比较吓人,但是稍微想想会发现,如果我是袋鼠,我会把我能走到的地方都走一遍,如果还没有赢,那我就赢不了了。

我能走的区域就是四连通块,如果场内有能够包含我这个连通块的其他连通块,那我就成为不了赢家,反之肯定能赢,且我连通块内的其他袋鼠也一定能赢(这个很关键)。

枚举所有的连通块,扫一遍全图,每个位置判断需要花费我连通块大小的时间,设每个连通块大小为 \(siz_i\) ,总复杂度为 \(\sum siz_i*nm=(nm)^2\) ,因为题目中 \(n*m\) 是 1000,所以平方可过。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1010;
const int inf=0x3f3f3f3f;

inline ll read() {
	ll sum = 0, ff = 1; char c = getchar();
	while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
	while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
	return sum * ff;
}

ll T;
int n,m;
char s[N];
int ff[] = {1,-1,0,0};
int gg[] = {0,0,1,-1};
int a[N][N];
int shang, xia, zuo, you;
int vis[N][N];
struct D { int x,y; }st[N];
int cnt;
int ans;

void chushihua() {
	for(int i=0;i<=n+1;i++) for(int j=0;j<=m+1;j++) vis[i][j] = a[i][j] = 0;
	ans = 0;
}

void DFS(int x,int y) {
	st[++cnt] = {x,y};
	vis[x][y] = 1;
	shang = min(shang,x);
	zuo = min(zuo,y);
	xia = max(xia,x);
	you = max(you,y);
	for(int k=0;k<4;k++) {
		int xx = ff[k] + x;
		int yy = gg[k] + y;
		if(vis[xx][yy] || !a[xx][yy]) continue;
		DFS(xx,yy);
	}
}

int main() {
	T = read();
	while(T--) {
		chushihua();
		n = read(); m = read();
		for(int i=1;i<=n;i++) {
			scanf("%s",s+1);
			for(int j=1;j<=m;j++) {
				if(s[j]=='.') a[i][j] = 1;
				else a[i][j] = 0;
			}
		}
		for(int i=1;i<=n;i++) {
			for(int j=1;j<=m;j++) {
				if(a[i][j] && !vis[i][j]) {
					cnt = 0;
					shang = zuo = inf;
					xia = you = 0;
					DFS(i,j);
					int shu = xia-shang+1;
					int heng = you-zuo+1;
					bool you = 0;
					for(int I=1;I<=n;I++) {
						if(I+shu-1>n) break;
						for(int J=1;J<=m;J++) {
							if(J+heng-1>m) break;
							if(I==shang && J==zuo) continue;
							bool can = 1;
							for(int k=1;k<=cnt;k++) {
								if(!a[ I+st[k].x-shang ][ J+st[k].y-zuo ]) { can = 0; break; }
							}
							if(can) {you = 1; break;}
						}
						if(you==1) break;
					}
					if(!you) ans += cnt;
				}
			}
		}
		cout<<ans<<endl;
	}

	return 0;
}

L:

这题思考难度是签到,但是难写程度甚至导致成为了银牌题。

电梯运包裹,因为包裹大小只有 1 和 2,且贪心满足要求,所以是可以排完序后直接模拟的,直接模拟的话可能会有些卡手,因为想使用两个set分别存 1 和 2 的包裹的话,来回选还是比较难写的。

我的做法把所有包裹放在一起按楼层排序,如何寻找最高的大小为 1 的包裹呢,想必大家都听说过树状数组二分这个东西吧?支持单点减(某个1包裹被运完),查询第k个数的值(查询楼层最大的1包裹的位置)。

但是我觉得麻烦没用树状数组,我是直接二分的,在前缀和上二分,找楼层最大的1包裹,因为答案是单调的,所以某个包裹被运完之后,我可以把二分的右区间改成这个楼层,这样就保证答案一定还没运完。

题解提供了避免区分 1 和 2 包裹的转化做法。因为保证电梯大小是偶数,这就诱使选手去思考下更简单的转化:如果我电梯只剩下1的空间,我挑选一个1包裹,和我把下一个2包裹拆成两个1,效果是一样的,都不影响最终答案。证明不难,也比较妙。最终结论就是,2包裹全拆成1包裹就行了,不用讨论了。

但是我想练练码力所以没转化。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=101010;

inline ll read() {
	ll sum = 0, ff = 1; char c = getchar();
	while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
	while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
	return sum * ff;
}

ll T;
ll n,K;
struct E{
	ll num,fl,cl;
}a[N];
inline bool cmp(E A,E B) { return A.fl < B.fl; }
ll sum[N];
ll R;

void chushihua() {
	R = 0;
}

int main() {
	T = read();
	while(T--) {
		chushihua();
		n = read(); K = read();
		for(ll i=1;i<=n;i++) {
			a[i].num = read(); a[i].cl = read(); a[i].fl = read();
		}
		sort(a+1,a+n+1,cmp);
		for(ll i=1;i<=n;i++) {
			sum[i] = sum[i-1] + (a[i].cl==1);
			if(a[i].cl==1) R = i;
		}
		ll ans = 0;
		for(ll i=n;i>=1;i--) {
			// for(ll j=1;j<=n;j++) cout<<a[j].fl<<","<<a[j].cl<<","<<a[j].num<<"  ";
			// cout<<"ans = "<<ans<<endl;
			if(a[i].cl * a[i].num >= K) ans += a[i].fl * ((a[i].cl * a[i].num) / K), a[i].num %= K / a[i].cl;
			if(!a[i].num) continue;
			ll now = K;
			ll j = i;
			ans += a[i].fl;
			while(1) {
				if(a[j].cl * a[j].num < now) now -= a[j].cl * a[j].num, a[j].num = 0;
				else { a[j].num -= now / a[j].cl; now %= a[j].cl; break; }
				j--;
				if(j==0) break;
			}
			if(j==0) break;
			R = min(j,R);
			if(sum[R]==0 || now==0) continue;
			ll l = 1, r = R, mid, res;
			while(l<=r) {
				mid = l+r >> 1;
				if(sum[mid]==sum[j]) res = mid, r = mid-1;
				else l = mid+1;
			}
			R = res;
			a[R].num--;
			if(!a[R].num) R--;
		}
		cout<<ans<<"\n";
	}
	return 0;
}

M:

好经典的题意,上一次见降雨存水的题好像是一道防AK几何。

好心的出题人怕大家做不出来,温馨地给了一个“更正式地”,其实是个关键的题意转化:\(ans = \sum\limits_{i=1}^n (min(f_i,g_i)-a_i)\)\(f_i,g_i\) 分别表示前缀最大值和后缀最大值。这个转化是在枚举每一个位置上有多高的水柱。

更新 \(a_i\) 时,单独更新每一个位置的 \(f_i,g_i\) 是比较好更新的,可是它俩的 min 值就不是很方便更新,能不能进一步转化?

我们发现,\(f_i,g_i\)本来就是左右的最大值,这俩已经包括了全局所有的数,那么对它们取个min,是不是跟全局次大值有关呢。

只要能想到这一点,不难转化成下式:\(min(f_i,g_i) = f_i+g_i-max(f_i,g_i)=f_i+g_i-max_i\)\(max_i\) 表示全局最大值。\(ans = \sum\limits_{i=1}^nf_i+g_i-max_i-a_i\)

因为 \(a_i\) 的修改只有增加,这就方便了我们对这三个值的维护,比如 \(f_i\) 是单增的,就可以用 set 维护,相同的数值只需在 set 中记录第一个位置。

#include <bits/stdc++.h>
#define ll long long

using namespace std;
const ll N=501010;
const ll inf=0x3f3f3f3f3f3f3f3f;

inline ll read() {
	ll sum = 0, ff = 1; char c = getchar();
	while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
	while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
	return sum * ff;
}

ll T;
ll n,Q;
struct E{
	ll wei,zhi;
};
inline bool operator < (E A,E B) { if(A.wei!=B.wei) return A.wei < B.wei; return A.zhi > B.zhi; }
set <E> s;
ll a[N],b[N];
ll X[N],Y[N];
ll ans[N];
ll tot,ma,F,G;

void chushihua() {
	tot = ma = F = G = 0;
	for(ll i=1;i<=Q;i++) ans[i] = 0;
	s.clear();
}

void outing() {
	cout<<"G = "<<G<<endl;
	for(auto v : s) {
		cout<<v.wei<<","<<v.zhi<<" ";
	}
	cout<<endl;
}

int main() {
	T = read();
	while(T--) {
		chushihua();
		n = read();
		for(ll i=1;i<=n;i++) b[i] = a[i] = read(), tot += a[i], ma = max(a[i],ma);
		Q = read();
		for(ll i=1;i<=Q;i++) {
			X[i] = read(); Y[i] = read();
			a[X[i]] += Y[i]; ma = max(ma,a[X[i]]);
			tot += Y[i];
			ans[i] -= tot + ma * n;
			// cout<<"i = "<<i<<" tot = "<<tot<<" ma = "<<ma<<endl;
		}
		a[n+1] = a[0] = inf;
		for(ll i=1;i<=n;i++) a[i] = b[i];
		ll now = 0;
		s.insert({0,0});
		for(ll i=1;i<=n+1;i++) {
			if(a[i]>now) {
				now = a[i];
				E lst = *(--s.end());
				F += (i - lst.wei) * lst.zhi;
				s.insert({i,a[i]});
			}
		}
		for(ll i=1;i<=Q;i++) {
			a[X[i]] += Y[i];
			E wo = {X[i], a[X[i]]};
			auto it = --s.lower_bound(wo);
			if((*it).zhi < a[X[i]]) {
				ll shang = (*it).zhi;
				s.insert(wo);
				it = ++s.find(wo);
				F -= shang * (it->wei - wo.wei);
				while(it->zhi <= wo.zhi) {
					auto nxt = ++it; it--;
					F -= (nxt->wei - it->wei) * it->zhi;
					s.erase(it);
					it = nxt;
				}
				F += (it->wei - wo.wei) * wo.zhi;
			}
			ans[i] += F;
		}

		s.clear();
		for(ll i=1;i<=n;i++) a[i] = b[i];
		now = 0;
		s.insert({-n-1,0});
		for(ll i=n;i>=0;i--) {
			if(a[i]>now) {
				now = a[i];
				E lst = *(--s.end());
				G += (-i - lst.wei) * lst.zhi;
				s.insert({-i, a[i]});
			}
		}
		for(ll i=1;i<=Q;i++) {
			a[X[i]] += Y[i];
			E wo = {-X[i], a[X[i]]};
			auto it = --s.lower_bound(wo);
			if((*it).zhi < a[X[i]]) {
				ll shang = (*it).zhi;
				s.insert(wo);
				it = ++s.find(wo);
				G -= shang * (it->wei - wo.wei);
				while(it->zhi <= wo.zhi) {
					auto nxt = ++it; it--;
					G -= (nxt->wei - it->wei) * it->zhi;
					s.erase(it);
					it = nxt;
				}
				G += (it->wei - wo.wei) * wo.zhi;
			}
			ans[i] += G;
		}
		for(ll i=1;i<=Q;i++) cout<<ans[i]<<"\n";
	}
	return 0;
}

D:

凸优化的树上DP。

\(f[u][i]\) 表示 u 这棵子树所有分支都有 i 个黑点的最小修改量,\(g[u][0/1]\) 表示把 u 这个点改成红点/黑点的代价。

有如下转化:\(f[u][i] = min(g[u][1]+\sum f[v][i-1],g[u][0]+\sum f[v][i])\)

i 的取值是 u 子树中最短的分支,也就是说复杂度就是所有子树中最短分支的长度和,但显然当数据是链状结构时会卡死。

喜欢拿金牌的选手们会发现,这是个可以凸优化的DP,由于 DP 转移式子是将两个序列进行 (min,+) 卷积,满足闵可夫斯基和的形式,也就是两个凸包相加融合(这个在几何题中也比较常见),两个凸序列卷完还是凸序列,g 因为只有两个值所以一定是凸的,f 通过归纳自然也能证明是凸序列。

那么凸序列有什么好处呢?我们可以用 “差分序列加首项” 来维护一个凸序列,如果凸序列存在最小值,那么差分数列是单增的。回想几何中的闵可夫斯基和,我们将两个凸包上的每一条线段合在一起按斜率进行排序,序列上的 (min,+) 卷积也是如此,将首项相加,再将两个差分数列的每一个数合在一起排序,生成新的差分数列,即为卷积后 \(f[u]\) 的序列。

回到这题的做法,我们每次求 \(f[u]\) 序列,需要先将 \(f[v]\) 直接相加,再和 \(g[u]\) 做卷积,\(f[v]\) 直接相加只需要保留最短分支的长度即可(下面的代码中用 \(sho[u]\) 表示)。

这个做法最大的好处是,我们不会被链之类的数据卡掉,当我们只有一个儿子时,直接继承它的 \(f[v]\) 数列即可。

然后是和 \(g[u]\) 做卷积,分为两种:当 u 是红点时,往差分数列加入一个1;当 u 是黑点时,首项加1,再往差分数列加入一个-1。

最终答案,也就是 \(f[1]\) 的最小值,等于首项加上差分数列最小前缀(所有负数相加)。为了避免使用平衡树维护,我们观察发现加入的数字只有1和-1,我们换个简单的写法,用三个vector来记录差分数列,一个从小到大记录负数,一个记录0,一个从大到小记录正数,加入1和-1只需在vector尾部插入即可。

思路可能比较经典,但是思考和实现难度都是妥妥的金牌题。

#include <bits/stdc++.h>
#define ll long long
#define QWQ cout<<"QwQ"<<endl;
#define FOR() ll le=e[u].size();for(ll i=0;i<le;i++)
using namespace std;
const ll N=501010;

inline ll read() {
	ll sum = 0, ff = 1; char c = getchar();
	while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
	while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
	return sum * ff;
}

struct E{
	ll a0,he;
	vector <ll> fu;
	vector <ll> li;
	vector <ll> zh;
}f[N],ling;
E *F[N];    // F[u] 指针指向儿子的f数列,便于直接继承。

ll T;
ll n;
char s[N];
ll cl[N];
vector <ll> e[N];
ll len[N],sho[N];
ll ans[N];
ll a[N];

void chushihua() {
	for(ll i=1;i<=n;i++) len[i] = sho[i] = 0, e[i].clear();
	for(ll i=1;i<=n;i++) f[i] = ling;
}

void DFS(ll u) {
	len[u] = 1;
	FOR() {
		ll v = e[u][i];
		DFS(v);
		len[u] = max(len[u],len[v]+1);
		F[u] = F[v];
		if(!sho[u] || sho[v]+1 < sho[u]) sho[u] = sho[v]+1;
	}
}

void TREE(ll u) {
	FOR() TREE(e[u][i]);
	if(e[u].size()>=2) {
		for(ll i=0;i<=sho[u];i++) a[i] = 0;
		for(ll v : e[u]) {
			a[0] += (*F[v]).a0;
			ll now = 1;
			for(ll w : (*F[v]).fu) {
				a[now] += w;
				now++;
				if(now>sho[u]) break;
			}
			if(now>sho[u]) continue;
			for(ll w : (*F[v]).li) {
				now++;
				if(now>sho[u]) break;
			}
			if(now>sho[u]) continue;
			for(ll i=(*F[v]).zh.size()-1;i>=0;i--) {
				ll w = (*F[v]).zh[i];
				a[now] += w;
				now++;
				if(now>sho[u]) break;
			}
		}
		*F[u] = ling;
		(*F[u]).a0 = a[0];
		for(ll i=1;i<=sho[u];i++) {
			if(a[i]<0) (*F[u]).fu.push_back(a[i]), (*F[u]).he += a[i];
			else if(a[i]==0) (*F[u]).li.push_back(0);
		}
		for(ll i=sho[u];i>=1;i--) {
			if(a[i]>0) (*F[u]).zh.push_back(a[i]);
			else break;
		}
	}
	if(cl[u]) (*F[u]).a0++, (*F[u]).fu.push_back(-1), (*F[u]).he--;
	else      (*F[u]).zh.push_back(1);
	ans[u] = (*F[u]).a0 + (*F[u]).he;
}

int main() {
	T = read();
	while(T--) {
		chushihua();
		n = read();
		scanf("%s",s+1);
		for(ll i=1;i<=n;i++) cl[i] = s[i] - '0', F[i] = &f[i];
		for(ll i=2;i<=n;i++) e[read()].push_back(i);
		DFS(1);
		TREE(1);
		for(ll i=1;i<=n;i++) cout<<ans[i]<<" \n"[i==n];
	}
	return 0;
}
posted @ 2024-04-17 19:32  maple276  阅读(112)  评论(0编辑  收藏  举报