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

maple276發表於2024-04-17

本場金牌線為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;
}

相關文章