星期一:
补19陕西 C 0689 cf传送门
思路:先不看69,若翻转的区间端点sl==sr,则翻转的结果等于翻转 sl+1至sr-1的结果,所以翻转区间只需考虑左右端点会变化的,即除去00,88,69,96
作一个前缀和,枚举sr,可得知有多少可翻转的sl
注意若s存在0,8,69,96子串任一,答案需加上原字符串
代码如下:
ll n;
string s;
void solve(){
cin >> s;
n=s.size(); s=" "+s;
int cnt[11]={0},if1=0;
ll ans=0;
for(int i=1;i<=n;i++){
cnt[s[i]-'0']++;
if(s[i]=='0') ans+=i-cnt[0],if1|=3;
else if(s[i]=='8') ans+=i-cnt[8],if1|=3;
else if(s[i]=='6') ans+=i-cnt[9],if1|=1;
else ans+=i-cnt[6],if1|=2;
}
cout << ans+(if1==3) << "\n";
}
补山东理工校赛 C 01背包 牛客传送门
思路:和上星期二队内个人赛的F一个思路,方案数使用01背包转移出来
dp【i】【{x,y}】表示考虑前 i步,走到x,y的方案数
对每个操作,有走和不走两种选择,若不走,所有坐标的方案数不变,若走,枚举所有坐标,操作后到达的坐标加上原坐标的方案数,注意判断有无障碍
代码如下:
const int mod=998244353;
ll n;
map<PII,int>dp[110];
void solve(){
int m,x,y; cin >> n >> m >> x >> y;
string s; cin >> s; s=" "+s;
set<PII>st;
while(m--){
int a,b; cin >> a >> b;
st.insert({a,b});
}
dp[0][{x,y}]=1;
for(int i=1;i<=n;i++){
int dx=0,dy=0;
if(s[i]=='U') dy=1;
if(s[i]=='D') dy=-1;
if(s[i]=='L') dx=-1;
if(s[i]=='R') dx=1;
for(auto [xy,sum]:dp[i-1]) dp[i][xy]=sum; //不走这步
for(auto [xy,sum]:dp[i-1]){ //走这步
int xx=xy.first+dx,yy=xy.second+dy;
if(st.find({xx,yy})==st.end()) dp[i][{xx,yy}]+=sum,dp[i][{xx,yy}]%=mod;
else dp[i][xy]+=sum,dp[i][xy]%=mod;
}
}
cout << dp[n][{x,y}];
}
补山东理工校赛 E 牛客传送门
思路:问有多少对( i, j) ( i<j 满足 a【i】*a【i】= k* a【j】,很明显是求对于a【i】后面有多少a【j】满足为a【i】*a【i】的因子( 赛时居然没反应过来
先筛出质数,再对每个数进行质因数分解,然后对因子进行暴力dfs,算出有多少a【j】为其因子,因为1e6内因子数量不大,所以dfs时间复杂度并不高
代码如下:
const int N=1e6+10,M=210;
const int mod=1e9+7;
ll n;
ll a[N],cnt[N];
int p[N],idx;
bool vi[N];
unordered_map<int,int>mp;
vector<PII>ve;
ll ans;
ll qpow(int a,int n){
if(n==0) return 1;
if(n==1) return a;
ll s=qpow(a,n/2);
s=s*s;
if(n&1) s=s*a;
return s;
}
void getp(int x){ //线性筛
for(int i=2;i<=x;i++){
if(!vi[i]) p[++idx]=i;
for(int j=1;1ll*i*p[j]<=x;j++){
vi[i*p[j]]=1;
if(i%p[j]==0) break;
}
}
}
void getd(int x){
for(int i=1;i<=idx && x/p[i]>=p[i];i++){ //分解质因数
int cnt=0;
while(x%p[i]==0){
x/=p[i];
cnt++;
}
if(!cnt) continue;
mp[p[i]]+=cnt;
}
if(x>1) mp[x]++;
}
// void dfs(ll x,int idx){
// if(x>1e6) return ;
// if(idx==(int)ve.size()){ans+=cnt[x]; return ;}
// for(int i=0;i<=(int)ve[idx].second;i++)
// dfs(x*qpow(ve[idx].first,i),idx+1); //和下面dfs等价,但比较笨,用到快速幂
// }
void dfs(ll x,int idx){ //对因子进行任意组合
if(x>1e6) return ;
if(idx==(int)ve.size()){ans+=cnt[x]; return ;}
ll t=1;
for(int i=0;i<=ve[idx].second;i++){
dfs(x*t,idx+1);
t*=ve[idx].first;
}
}
void solve(){
getp(1e6);
cin >> n;
for(int i=1;i<=n;i++) cin >> a[i];
cnt[a[n]]++;
for(int i=n-1;i;i--){
mp.clear(),ve.clear();
getd(a[i]);
for(auto [x,y]:mp) ve.push_back({x,y*2}); //找a[i]*a[i]的因子,所以y*=2
dfs(1,0);
cnt[a[i]]++;
}
cout << ans;
}
补上周二 队内个人赛 G cf传送门
题意: 给 n个灯泡, 每个灯泡有其亮度x和周期t, 即亮 t秒后熄 t秒, 开灯时亮度为 x, 否则为 0, 问1到m秒, 每秒最亮的灯泡亮度为多少
思路:先将灯泡按亮度降序排序, 依次遍历覆盖1-m内区间, 遇到被覆盖区间则直接跳过, 被覆盖区间用并查集维护
代码如下:
const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
ll ans[N],m;
struct nod{
int t,x;
}b[N];
int fa[N];
bool vi[N];
bool cmp1(nod a,nod b){
return a.x>b.x;
}
inline int fnd(int x){
return fa[x]==x?x:fa[x]=fnd(fa[x]);
}
inline void shine(int x,int t){
for(int k=0;k<=m;k+=2*t){
for(int i=k+1;i<=m && i<=k+t;i++){
if(ans[i]){i=fnd(i); continue;} //跳过被覆盖区间
ans[i]=x,fa[i-1]=fa[i];
}
}
}
void solve(){
int tt; cin >> tt;
for(int ii=1;ii<=tt;ii++){
cin >> n >> m;
for(int i=1;i<=1e5;i++) vi[i]=0;
for(int i=1;i<=m;i++) fa[i]=i;
for(int i=1;i<=n;i++) cin >> b[i].t >> b[i].x;
sort(b+1,b+n+1,cmp1);
for(int i=1;i<=n;i++){
if(vi[b[i].t]) continue; //周期已出现,之前有x更大且周期重复灯泡,此灯泡可跳过
shine(b[i].x,b[i].t);
vi[b[i].t]=1;
}
cout << "Case #" << ii << ": ";
for(int i=1;i<=m;i++) cout << ans[i] << " \n"[i==m],ans[i]=0;
}
}
星期二:
下午队内组队赛, 虽然有一些很低级的失误(os全锅), 总体来说发挥的还不错
补上周二队内个人赛 G(线段树做法 cf传送门
思路: 线段树维护区间最大值的板子
又re了, 提两个注意的点
一还是要时刻确保 查询和修改的区间 ql一定要<=qr !!!
二要注意修改和查询的区间 一定要在 1-m以内, 不能超出范围 !!!
代码如下:
const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
struct nod{
int l,r;
int ma,add;
}tr[N];
int ql,qr;
nod merge(nod a,nod b){
nod res={0,0,0,0}; //一定把懒标记初始化为0
res.l=a.l,res.r=b.r;
res.ma=max(a.ma,b.ma);
return res;
}
void pushup(int p){tr[p]=merge(tr[lc],tr[rc]);}
void pushdn(int p){
if(!tr[p].add) return ;
tr[lc].ma=tr[p].add;
tr[rc].ma=tr[p].add;
tr[lc].add=tr[p].add;
tr[rc].add=tr[p].add;
tr[p].add=0;
}
void bd(int p,int l,int r){
tr[p]={l,r,0,0};
if(l==r) return ;
int m=l+r>>1;
bd(lc,l,m);
bd(rc,m+1,r);
pushup(p);
}
void update(int p,int v){
if(ql<=tr[p].l && qr>=tr[p].r){
tr[p].ma=v;
tr[p].add=v;
return ;
}
int m=tr[p].l+tr[p].r>>1;
pushdn(p);
if(ql<=m) update(lc,v);
if(qr>m) update(rc,v);
pushup(p);
}
nod query(int p){
if(ql<=tr[p].l && qr>=tr[p].r) return tr[p];
int m=tr[p].l+tr[p].r>>1;
pushdn(p);
if(qr<=m) return query(lc);
if(ql>m) return query(rc);
return merge(query(lc),query(rc));
}
void updt(int l,int r,int v){
ql=l;
qr=r;
update(1,v);
}
int ask(int l,int r){
ql=l;
qr=r;
return query(1).ma;
}
#undef lc
#undef rc
}t;
void solve(){
int tt; cin >> tt;
for(int ii=1;ii<=tt;ii++){
int m; cin >> n >> m;
t.bd(1,1,m);
unordered_map<int,int>mp;
for(int i=1;i<=n;i++){
int t,x; cin >> t >> x;
mp[t]=max(x,mp[t]);
}
vector<PII>ve;
for(auto [t,x]:mp) ve.push_back({x,t});
sort(ve.begin(),ve.end()); //灯泡按亮度排序
for(auto [a,b]:ve){
for(int k=0;k<m;k+=2*b) //若设为k<=m,k+1即ql可能超出m
t.updt(k+1,min(k+b,1ll*m),a); //小心re,小心k+b超出m
}
cout << "Case #" << ii << ": ";
for(int i=1;i<=m;i++) cout << t.ask(i,i) << " \n"[i==m];
}
}
星期三:
补24河南邀请赛 A 构造 cf传送门
思路:题意可转化为找一个幸运数,满足num%n==0
n的范围是[1,1e8], k的范围是[1,2e10],所以幸运数的范围可在n的2e10倍之内
把幸运数构造为123456789*10+d,此时满足幸运数的条件,那么如何使其%n==0呢
在num末尾加上n的位数个0,然后num+=n - num%n,可确保num%n==0, 且 num/n在2e10内
代码如下:
ll n;
void solve(){
int d; cin >> n >> d;
ll num=123456789ll*10+d;
int sz=to_string(n).size();
while(sz--) num*=10;
num+=n-num%n;
cout << num/n << "\n";
}
补河南邀请赛 L dp cf传送门
赛时os出的这题,我先交了发wa 6,遂补
思路:注意到22的4次幂已经>=2e5了,所以可以用二维的dp数组,第二维只需开30
dp【i】【j】表示考虑到第 i个碧油鸡,连续往上处理 j个碧油鸡的所需时间
当时为什么会wa6呢,因为第二三重循环枚举第二维时,没将其限制在22以内,因为初始化也只初始化到了26,第二维超出26值为0,所以转移出问题了,如果数据范围再大点,就会因为三重循环n^3的复杂度T掉。。也属实是我粗糙了
代码如下:
const int N=2e6+10,M=210;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
ll n;
ll dp[N][30];
int a[N];
void solve(){
int m; cin >> n >> m;
for(int i=1;i<=m;i++){
cin >> a[i];
for(int j=0;j<=26;j++) dp[i][j]=1e18; //从0开始初始化
}
for(int i=1;i<=m;i++){
for(int j=1;j<=min(i,22);j++) //记得取min,将第二维限制在22以内
for(int k=0;k<=min(i-j,22);k++) //k从0开始,转移需要dp[0][0]
dp[i][j]=min(dp[i-j][k]+a[i]+1ll*j*j*j*j,dp[i][j]);
}
ll ans=1e18;
for(int i=1;i<=26;i++) ans=min(dp[m][i],ans);
cout << ans;
}
星期四:
补河南邀请赛 C cf传送门
题意有点绕
思路:注意到b2==b5,那么f2==f6==f10,只能满足一个,即需付出2的代价
f1<=(f10==f6==f2)<=f8<=f9<=f4<=f5,代价为4,这里其实可以猜下结论,是求最长上升子序列,若满足f8,f9,f4,f5需>=9,则需要付出2代价,满足f4,f5同理,那若 f5后还有 f7呢,肯定选择满足f4=4,f5=5,f7=7,满足最多的 f 实际就是求LIS
这里的实现首先需要去重,然后将值相同的区间降序排序,再 nlogn求最长上升子序列即可
代码如下:
const int N=2e6+10,M=210;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
ll n;
int a[N];
void solve(){
cin >> n;
unordered_map<int,int>mp;
for(int i=1;i<=n;i++){
cin >> a[i];
mp[a[i]]=i;
}
vector<int>ve;
set<int,greater<int>>st;
int idx=0;
for(int i=1;i<=n;i++){
st.insert(a[i]);
idx=max(mp[a[i]],idx);
if(idx!=i) continue;
for(int j:st) ve.push_back(j);
st.clear();
}
vector<int>vv;
vv.push_back(ve[0]);
for(int i=1,sz=ve.size();i<sz;i++){ //求最长上升子序列
if(ve[i]>vv.back()) vv.push_back(ve[i]);
else *lower_bound(vv.begin(),vv.end(),ve[i])=ve[i];
}
cout << ve.size()-vv.size();
}
补24河南邀请赛 D
思路:可以想到两点之间应该是斜着角度越接近45°越好,推下式子后可证明此结论,且能发现,最佳情况是 |x1-x2| == |y1-y2|,则去掉绝对值符号后,可以得到 x1-y1==x2-y2和x1+y1==x2+y2,则可按x和y的差值 与 和来排序两遍,答案就在排序后相邻点间,取最大值即可
代码如下:
const int N=2e6+10,M=210;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
ll n;
struct nod{
int x,y;
int xy1,xy2;
}p[N];
bool cmp1(nod a,nod b){
return a.xy1<b.xy1;
}
bool cmp2(nod a,nod b){
return a.xy2<b.xy2;
}
void solve(){
cin >> n;
for(int i=1;i<=n;i++){
cin >> p[i].x >> p[i].y;
p[i].xy1=p[i].x-p[i].y;
p[i].xy2=p[i].x+p[i].y;
}
sort(p+1,p+n+1,cmp1);
double ans=0;
for(int i=1;i<n;i++){
double tx=abs(p[i].x-p[i+1].x),ty=abs(p[i].y-p[i+1].y);
ans=max((tx+ty)/sqrt(tx*tx+ty*ty),ans);
}
sort(p+1,p+n+1,cmp2);
for(int i=1;i<n;i++){
double tx=abs(p[i].x-p[i+1].x),ty=abs(p[i].y-p[i+1].y);
ans=max((tx+ty)/sqrt(tx*tx+ty*ty),ans);
}
printf("%.11lf\n",ans);
}
刚写分解质因数分解时犯了一个令人发指的错误
int get(int x){
unordered_set<int>st;
for(int i=2;i<=idx && p[i]*p[i]<=x;i++){
while(x%p[i]==0){
st.insert(p[i]);
x/=p[i];
}
}
if(x>1) st.insert(x);
return st.size();
}
质数表的下标写为从2开始了,晕晕
入门 换根dp 洛谷传送门
思路:dp【i】表示以 i为根时的答案,dep【i】表示深度,sz【i】表示 i的子树节点数量
先以 1为根dfs一遍求出dp【1】,然后第二遍dfs进行答案的转移,假设 d到点 x,其父节点为 f且dp【f】已知,则dp【x】=dp【f】- sz【x】+ n - sz【x】,因为根从 f转移到 x,x为根的子树距离都会减 1,除此之外的节点到根距离都会+1
代码如下:
const int N=2e6+10,M=210;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
ll n;
vector<int>ve[N];
int sz[N],dep[N];
ll dp[N];
ll ma,idx;
void dfs1(int x,int f){
sz[x]=1,dep[x]=dep[f]+1;
if(ve[x].size()==1 && ve[x][0]==f) return ;
for(int i:ve[x]){
if(i==f) continue;
dfs1(i,x);
sz[x]+=sz[i];
}
}
void dfs2(int x,int f){
if(x!=1) dp[x]=dp[f]-sz[x]+n-sz[x]; //换根的转移
if(dp[x]>ma) idx=x,ma=dp[x];
for(int i:ve[x]){
if(i==f) continue;
dfs2(i,x);
}
}
void solve(){
cin >> n;
for(int i=1;i<n;i++){
int u,v; cin >> u >> v;
ve[u].push_back(v);
ve[v].push_back(u);
}
dfs1(1,0);
for(int i=2;i<=n;i++) dp[1]+=dep[i]-1; //算出dp[1]
idx=1,ma=dp[1];
dfs2(1,0);
cout << idx;
}
星期五:
上一题的换皮 洛谷传送门
难得敲了一次链式前向星,出错了,还是得换成vector存邻接表
思路:有一点需要注意,这题每个点和每条边的信息由题目给出,所以d到一个新点时,先需要把其信息从父节点转移过来,才能继续往子树dfs
代码如下:
const int N=2e6+10,M=210;
ll n;
int dep[N],c[N];
ll dp[N],sz[N],sum;
vector<PII>ve[N];
void dfs1(int x,int f){
sz[x]=c[x];
for(auto [w,v]:ve[x]){
if(v==f){dep[x]=dep[f]+w; break;}
}
for(auto [w,v]:ve[x]){
if(v==f) continue;
dfs1(v,x);
sz[x]+=sz[v];
}
}
void dfs2(int x,int f){
for(auto [w,v]:ve[x]){
if(v==f){dp[x]=dp[f]-sz[x]*w+(sum-sz[x])*w; break;}
}
for(auto [w,v]:ve[x]){
if(v==f) continue;
dfs2(v,x);
}
}
void solve(){
cin >> n;
for(int i=1;i<=n;i++){
cin >> c[i];
sum+=c[i];
}
for(int i=1;i<n;i++){
int u,v,w; cin >> u >> v >> w;
ve[u].push_back({w,v});
ve[v].push_back({w,u});
}
dfs1(1,0);
for(int i=2;i<=n;i++) dp[1]+=1ll*dep[i]*c[i];
dfs2(1,0);
ll mi=1e18;
for(int i=1;i<=n;i++) mi=min(dp[i],mi);
cout << mi << "\n";
}
修正,链式前向星出错原因是edge数组开小了,n个点,双向建边即2*n条边,起码开2e5(2e6也行
链式前向星写法如下:
const int N=1e5+10,M=210;
ll n;
ll sz[N],dep[N],c[N];
ll dp[N],sum;
struct nod{
ll nex,to,w;
}e[N<<1]; //双向建边,记得开点的2倍大小
int hd[N],cnt;
void add(int u,int v,int w){
e[++cnt].to=v;
e[cnt].w=w;
e[cnt].nex=hd[u];
hd[u]=cnt;
}
void dfs1(int x,int f){
sz[x]=c[x];
for(int i=hd[x];i;i=e[i].nex){
int v=e[i].to;
if(v==f){dep[x]=dep[f]+e[i].w; break;}
}
for(int i=hd[x];i;i=e[i].nex){
int v=e[i].to;
if(v==f) continue;
dfs1(v,x);
sz[x]+=sz[v];
}
}
void dfs2(int x,int f){
for(int i=hd[x];i;i=e[i].nex){
int v=e[i].to;
if(v==f){dp[x]=dp[f]-1ll*sz[x]*e[i].w+(sum-sz[x])*e[i].w; break;}
}
for(int i=hd[x];i;i=e[i].nex){
int v=e[i].to;
if(v==f) continue;
dfs2(v,x);
}
}
void solve(){
cin >> n;
for(int i=1;i<=n;i++){
cin >> c[i];
sum+=c[i];
}
for(int i=1;i<n;i++){
int u,v,w; cin >> u >> v >> w;
add(u,v,w),add(v,u,w);
}
dfs1(1,0);
for(int i=2;i<=n;i++) dp[1]+=1ll*dep[i]*c[i];
dfs2(1,0);
ll mi=1e18;
for(int i=1;i<=n;i++) mi=min(dp[i],mi);
cout << mi << "\n";
}
继续一道换根dp cf round927 F cf传送门
思路:dfs1先预处理出每个节点只考虑以自己为根的子树最大值,dfs2即可进行转移
设节点为 i,父节点为 f且dp【f】已知
若ma【i】< 0,dp【f】肯定没包含 i为根子树,若dp【f】为正,dp【i】+= dp【f】,否则不加
若ma【i】> 0,dp【f】包含 i为根子树,dp【f】- ma【i】若为正,dp【i】加上它,否则不加,实际等价于dp【i】为 ma【i】还是 dp【f】
代码如下:
const int N=2e6+10,M=210;
ll n;
int a[N],ma[N];
ll dp[N];
vector<int>ve[N];
void dfs1(int x,int f){
ma[x]=a[x];
for(int i:ve[x]){
if(i==f) continue;
dfs1(i,x);
if(ma[i]>0) ma[x]+=ma[i];
}
}
void dfs2(int x,int f){
dp[x]=ma[x];
if(dp[x]>0) dp[x]=max(dp[x]+dp[f]-dp[x],dp[x]);
else dp[x]=max(dp[f]+dp[x],dp[x]);
for(int i:ve[x]){
if(i==f) continue;
dfs2(i,x);
}
}
void solve(){
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i];
if(!a[i]) a[i]--; //把为0的点处理为-1
}
for(int i=1;i<n;i++){
int u,v; cin >> u >> v;
ve[u].push_back(v);
ve[v].push_back(u);
}
dfs1(1,0);
dp[1]=ma[1];
dfs2(1,0);
for(int i=1;i<=n;i++) cout << dp[i] << " \n"[i==n];
}
24河南邀请赛 K 换根dp写法
赛时和os用并查集缩点写的,用换根dp再写一遍
思路:dfs1预处理出以1为根有多少不符合条件的边
dfs2转移时,从 f到 x,只需考虑 f和x 一条边,其余边的情况不会改变,dp【f】已知
若 f->x 不符合条件,转移根后 x->f 即符合条件,dp【x】= dp【f】- 1
若 f->x符合条件,但转移根后x->f 不符合条件,dp【x】=dp【f】+ 1,ans += ( dp【x】== 0)
代码如下:
const int N=2e6+10,M=210;
ll n;
int a[N];
ll dp[N],ans;
vector<int>ve[N];
bool cmp(int x,int f){
int fa=a[f]/2; if(a[f]&1) fa++;
return a[x]>=fa;
}
void dfs1(int x,int f){
if(!cmp(x,f)) dp[1]++;
for(int i:ve[x]){
if(i==f) continue;
dfs1(i,x);
}
}
void dfs2(int x,int f){
if(f) dp[x]=dp[f];
if(!cmp(x,f)) dp[x]--;
if(f && !cmp(f,x)) dp[x]++;
ans+=(dp[x]==0);
for(int i:ve[x]){
if(i==f) continue;
dfs2(i,x);
}
}
void solve(){
dp[1]=ans=0;
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i];
ve[i].clear();
}
for(int i=1;i<n;i++){
int u,v; cin >> u >> v;
ve[u].push_back(v);
ve[v].push_back(u);
}
dfs1(1,0);
dfs2(1,0);
cout << ans << "\n";
}
星期六:
重庆科技大学,C待补
周日:
武汉纺织大学,A待补
稍微难点的换根dp 洛谷传送门
思路:dfs1预处理出 f【i】【j】表示 i节点只考虑往下距离 j以内的节点权值和
dfs2进行转移,x在向子节点dfs之前,可以先把子节点的dp【i】【j】处理出来,再dfs,免得到了子节点还得再找父节点?范围为2时,dp【i】【2】+= dp【x】【1】,但此时dp【x】【1】包含了 i节点权值,所以需要减去f【i】【0】,可推出转移方程
代码如下:
const int N=2e6+10,M=210;
ll n;
int k;
int c[N];
ll f[N][22],dp[N][22];
vector<int>ve[N];
void dfs1(int x,int fa){
for(int i=0;i<=k;i++) f[x][i]=c[x];
for(int i:ve[x]){
if(i==fa) continue;
dfs1(i,x);
for(int j=1;j<=k;j++) f[x][j]+=f[i][j-1];
}
}
void dfs2(int x,int fa){
for(int i:ve[x]){
if(i==fa) continue;
dp[i][1]+=f[x][0];
for(int j=2;j<=k;j++) dp[i][j]+=dp[x][j-1]-f[i][j-2];
dfs2(i,x);
}
}
void solve(){
cin >> n >> k;
for(int i=1;i<n;i++){
int u,v; cin >> u >> v;
ve[u].push_back(v);
ve[v].push_back(u);
}
for(int i=1;i<=n;i++) cin >> c[i];
dfs1(1,0);
for(int i=1;i<=n;i++)
for(int j=0;j<=k;j++) dp[i][j]=f[i][j];
dfs2(1,0);
for(int i=1;i<=n;i++) cout << dp[i][k] << "\n";
}