컴파일 (트리 DP)

컴파일러
[문제 설명]
내가 알려져 안드로이드 시스템을 재생 좋아하기 때문에 CCF는 오르 반의 편지의 구성원입니다.
CCF는 훌륭한 C ++ 프로젝트 N 소스 파일의 전체를 포함 썼다. 마음에 CCF는 N 소스 파일은 트리 구조를 구성한다. 각 소스 파일은 루트 노드는 숫자 1 인 트리의 노드입니다.
이제, CCF는 프로젝트를 컴파일하기 시작했다. 때마다 그는 나무에서 체인 (두 엔드 포인트를 포함) 종 라인 컴파일러를 선택할 것. 컴파일러의 특징 때문에, 이것은 연쇄 끝점은 다른 끝점의 조상이 필요합니다. 체인은 점으로 변질된다. 각 소스 파일은 한 번만 컴파일 할 필요가있다.
각 소스 파일 (00에서 FF까지 범위의) 시스템 플래그 값의 2 진수 종 수있다. 체인의 각 선택이 체인의 위의 배타적 OR 값까지 고유의 모든 소스 파일의 플래그. 모든 기능은 값을 선택한 체인은 컴파일 비용을 얻기 위해 추가됩니다.
CCF는 지금 == == 최소한 몇 체인을 알고있는 모든 파일을 컴파일 할 때 선택합니다. == 최저 가격을 컴파일 할 때 선택한 체인의 최소 수는 얼마나 ==입니다.
[입력 형식
정수 N.의 제 1 라인의
줄 이어 두 종 N 헥스 번호 시스템은, 제 N 고유 소스 파일 소스 파일 번호 1을 나타낸다. (16 진수 체계 종 사용된다 ++는 C / C 앞의 소문자, 두 개 미만, 즉 제로 패딩을 가지고 ". % 02x 포맷"을 출력한다.)
다음의 (N을 - 1) 개의 정수 라인 연결된 나무의 가장자리의 두 정점을 부여.
출력 형식]
열 두 정수. 컴파일 선택된 최소 비용의 체인의 최소 개수 하였다. 종 두 개의 숫자는 열 개 출력의 형태로 만들어집니다.
입력 샘플 [1]

3
01 02 0F
1 2 1 3

샘플 출력 [1]

2 16

설명 : 최적해는 (1, 3), (2).
입력 샘플 [2]

5
(67)에서 D1 F0 33
1 2
1 3
2 4
2 5

샘플 출력 [2]

3 288

설명 : 최적해는 (1, 3), (2, 4), (5) 또는 (1, 3), (2, 5), (4).
[데이터] 범위
20,000 N ≤ ≤ 0.
분석 : 최소의 비용으로 컴파일 2. 선택한 체인의 최소한 1 개 체인의 최소,이 질문은 두 가지 질문이 포함되어 있습니다.

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
struct node{
    int u;
    int v;
    int next;
};
bool fl[20050] = {0}; //由于建的是无向图,所以遍历时标记父亲节点,避免找儿子时误找到父亲 
node edge[40050]; //记录边 
int tot = -1,first[20050],w[20050],minn[20050],dp[20050][270];
// minn 当前节点为根的子树异或总和的最小值
// dp[u][j] 以点u为根节点,当子树异或值为j时,该子树异或总和的最小值
// minn[u] = min{minn[u],dp[u][j] + j}
// dp[u][j ^ w[u]] = min{minn[Vi]总和 - minn[Vj] + dp[vj][j]} 

void add(int u,int v){//建边 
    edge[++tot].u = u;
    edge[tot].v = v;
    edge[tot].next = first[u];
    first[u] = tot;
}

void back(int x,int sum){//找完叶子结点后回溯时计算dp和minn 
    minn[x] = 0x7f7f7f7f;
    for(int i=0;i<=255;i++){
        for(int j=first[x];j!=-1;j = edge[j].next){
            if(!fl[edge[j].v])dp[x][i ^ w[x]] = min(dp[x][i ^ w[x]],sum - minn[edge[j].v] + dp[edge[j].v][i]);
        }
    }
    for(int i=0;i<=255;i++)
        minn[x] = min(minn[x],dp[x][i] + i);
}

int ans = 0;
void find(int x){//找叶子节点 
    bool leaf = 1;//是否是叶子结点的标记 
    int sum = 0;//所有儿子的minn之和,在计算当前节点的minn和 dp时要用 
    fl[x] = true;
    for(int i=first[x];i!=-1;i = edge[i].next){
        if(fl[edge[i].v]) continue;
        leaf = 0;
        find(edge[i].v);
        sum += minn[edge[i].v];
    }
    fl[x] = false;//将fl还原,以便back函数中判断儿子 
    if(leaf){
        ans++;
        minn[x] = w[x];
        dp[x][w[x]] = 0;
    }
    else back(x,sum);
}


int main(){
    freopen("compiler.in","r",stdin);
    freopen("compiler.out","w",stdout);
    memset(first,-1,sizeof(first));
    memset(dp,0x3f,sizeof(dp));
    int u,v,n;scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%02x",&w[i]);
    for(int i=1;i<n;i++){
        scanf("%d%d",&u,&v);
        //输入不一定是父亲 儿子的顺序,所以建无向图,双向的边 
        add(u,v);
        add(v,u);
    }
    ans = 0;
    find(1);
    printf("%d ",ans);
    printf("%d",minn[1]);
    
    return 0;
}

추천

출처www.cnblogs.com/Cindy-Chan/p/11294345.html