T1
分析:
我们可以很清晰的发现,这是一道树形dp的问题,找最短链的方法很简单,就是数叶节点的个数,然后就可以考虑如何去DP了。
我们先从下面这张图来分析:
因为要求链的一个端点必须是另一个端点的祖先,因此可以论证,链的数量等于叶子结点的数量,因此需要求的只有最小代价。
定义dp[u][j]为考虑子树u,并且点u所属的链当前的异或和为j时的最小代价。对每一个点枚举与它相连的子节点即可。
v — u 的儿子结点
w[u]— 结点 u 的标志值
mini[u]— 以结点 u 为树根开始编译文件的最小代价,mini[1]即为最终答案。
minisum—当前结点所有儿子结点的最小代价总和
dp[u][j]—以结点 u 所属的链当前的异或值和为 j 时的最小代价
动态转移方程:
dp[u][jw[u]]=min(dp[u][jw[u]],minisum-mini[v]+dp[v][j]) 0<=j<=255
mini[u] = min(mini[u],dp[u][j]+j),0<=j<=255
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int u[40010],v[40010],first[40010],nex[40010];
int n,cnt=0,ans=0;
int p[20010];
int dp[20010][260];
int mini[20010];
void add(int x,int y) //建图,注意是双向
{
cnt++;
u[cnt]=x;v[cnt]=y;
nex[cnt]=first[x];
first[x]=cnt;
}
void dfs(int me,int father)
{
int t=0; //表示儿子节点的个数
for(int i=first[me];i!=-1;i=nex[i])
{
if(v[i]!=father)
{
dfs(v[i],me);
t++;
}
}
if(t==0) //叶节点
{
ans++;
mini[me]=p[me];
dp[me][p[me]]=0;
return;
}
else
{
int s=0; //和
for(int j=first[me];j!=-1;j=nex[j])
if(v[j]!=father)
s+=mini[v[j]];
for(int j=first[me];j!=-1;j=nex[j])
if(v[j]!=father)
for(int i=0;i<=255;i++)
dp[me][i^p[me]]=min(s-mini[v[j]]+dp[v[j]][i],dp[me][i^p[me]]);
for(int i=0;i<=255;i++)
mini[me]=min(mini[me],dp[me][i]+i);
return;
}
}
int main()
{
freopen("compiler.in","r",stdin);
freopen("compiler.out","w",stdout);
memset(first,-1,sizeof(first));
memset(nex,-1,sizeof(nex));
memset(dp,0x7f,sizeof(dp));
memset(mini,0x7f,sizeof(mini));
cin>>n;
for(int i=1;i<=n;i++)
scanf("%02x",&p[i]);
for(int i=1,x,y;i<n;i++)
{
cin>>x>>y;
add(x,y);
add(y,x);
}
dfs(1,0);
cout<<ans<<' '<<mini[1];
return 0;
}