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