問題描述node
有一棵 n 個節點的樹,樹上每一個節點都有一個正整數權值。若是一個點被選擇了,那麼在樹上和它相鄰的點都不能被選擇。求選出的點的權值和最大是多少?算法
輸入格式數組
第一行包含一個整數 n 。學習
接下來的一行包含 n 個正整數,第 i 個正整數表明點 i 的權值。spa
接下來一共 n-1 行,每行描述樹上的一條邊。指針
輸出格式blog
輸出一個整數,表明選出的點的權值和的最大值。內存
樣例輸入資源
5
1 2 3 4 5
1 2
1 3
2 4
2 5string
樣例輸出
12
樣例說明
選擇三、四、5號點,權值和爲 3+4+5 = 12 。
數據規模與約定
對於20%的數據, n <= 20。
對於50%的數據, n <= 1000。
對於100%的數據, n <= 100000。
權值均爲不超過1000的正整數。
解題過程
剛學習完樹形動態規劃的原理,因此乍一看就知道此題應該用樹形動態規劃解決。分兩步:一、建樹。二、動態規劃。
剛開始選擇的存儲結構是二維數組,既每一行表示樹的一層,每一列表示該層(行)的全部節點;記錄下樹的最大層數,從最後一層開始改變每一個節點的狀態,最後從根節點中獲取最優解。
#include<stdio.h> #include<stdlib.h> #include<string.h> #define M 100010 //數組最大長度 int fu[M],hz[M][M],shu[M][M],pow[M],f[M][2]; //父節點數組; 孩子數組hz[i][0]第i個節點的孩子數,hz[i][j](j>0)表示i節點的第j個孩子 //樹二維數組,shu[i][0]表示第i層節點數,shu[i][j](j>0)表示第i層的第j個節點; //pow[]權值數組,p[i]表示第i個節點的權值 //f[i][1]保留節點i時最大權值,f[i][0]不保留節點i時的最大權值 int main() { int n,i,j,u,v; memset(fu,0,sizeof(fu)); memset(hz,0,sizeof(hz)); memset(shu,0,sizeof(shu)); memset(f,0,sizeof(f)); scanf("%d",&n); for(i=1;i<=n;i++)scanf("%d",&pow[i]); for(i=1;i<n;i++) { scanf("%d%d",&u,&v); fu[v]=u; hz[u][0]++; hz[u][hz[u][0]]=v; } //建樹 int x,maxlev=-1,s; for(i=1;i<=n;i++) { x=fu[i]; s=1; while(x!=0){s++;x=fu[x];} shu[s][0]++; shu[s][shu[s][0]]=i; if(s>maxlev)maxlev=s; } //動態規劃 int now,k,a,b; for(i=maxlev;i>0;i--) { for(j=1;j<=shu[i][0];j++) { now=shu[i][j]; if(hz[now][0]==0) { f[now][0]=0; f[now][1]=pow[now]; } else { for(k=1;k<=hz[now][0];k++) { a=f[hz[now][k]][0]; b=f[hz[now][k]][1]; f[now][1]+=a; if(b>a)a=b; f[now][0]+=a; } } } } int sum=0; for(i=1;i<=shu[1][0];i++) { now=shu[1][i]; a=f[now][0];b=f[now][1]; if(b>a)a=b; sum+=a; } printf("%d\n",sum); return 0; }
按理說這個算法是可行的,可是再提交答案時,竟然發生運行錯誤,我看了看內存使用率很是大,返回題目看了數據規模,節點數n<=100000,也就意味着要用二維數組存儲樹的話,二維數組至少定義爲shu[100000][100000],佔用了很是大的控件資源。再者,題目給n個頂點,n-1條邊,也就意味着樹沒有孤立點,而且有且僅有一個根節點,可見每一層的節點不少時候是遠少於100000的,因此應該改用動態存儲結構。
樹的存儲結構
《1》、雙親表示法
假設以一組連續空間存儲樹的節點,同時在每一個節點中附設一個指示器指示其雙親節點在鏈表中的位置,其形式說明以下:
#define MAX_TREE_SIZE 100 typedef struct PTNode{//節點結構 TElemType data; int parent;//雙親位置 }PTNode; typedef struct{ //樹結構 PTNode nodes[MAX_TREE_SIZE]; int r,n; //根節點位置和節點數 }PTree;
這種存儲結構利用了每一個節點(除根節點之外)只有惟一雙親的性質。PARENT(T,x)操做能夠在常數時間內實現。反覆調用PARENT操做,直到碰見無雙親的節點時,便找到了樹的根,這個就是ROOT(x)的過程。可是,在這種表示法中,求節點的孩子時須要遍歷整個結構。
《2》、孩子表示法
這裏主要給出一種相似於鄰接表的表示法。把每一個節點的孩子節點排列起來,當作是一個線性表,且以單鏈表做爲存儲結構,則n個節點有n個孩子鏈表(葉子節點的孩子鏈表爲空表)。而n個頭指針又組成一個線性表,爲了便於查找,可採用順序存儲結構。這種存儲結構可形式地說明以下:
#define MAX_TREE_SIZE 100 typedef struct CTNode{ //孩子節點 int child; struct CTNode *next; }*ChildPtr; typedef struct{ TElemType data; ChildPtr firstchild; //孩子鏈表頭指針 }CTBox; typedef struct{ CTBox nodes[ MAX_TREE_SIZE ]; int n,r; //節點數和根節點位置 }CTree;
與雙親表示法相反,孩子表示法便於那些涉及孩子操做的實現,卻不適合用於PARENT(T,x)的操做。咱們能夠把雙親表示法和孩子表示法合起來,既將雙親表示和孩子鏈表和在一塊兒。
《3》、孩子兄弟表示法
又稱二叉樹表示法,或二叉樹表示法。既以二叉樹表做樹的存儲結構。鏈表中節點的兩個鏈域分別指向該節點的第一個孩子節點和下一個兄弟節點,分別命名爲firstchild域和nextsibling域。存儲結構形式說明以下:
#define MAX_TREE_SIZE 100 typedef struct CSNode{ ElemType data; struct CSNode *firstchild,*nextsibling; }CSNode,*CSTree;
利用這種結構便於實現各類樹的操做。
符合題目要求的結果
採用了樹存儲結構中的《孩子表示法》,固然有些改進。
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<algorithm> #define M 100100 //最大長度 using namespace std; //孩子節點結構 typedef struct Node { int vex; Node* next; }Child; Child* head[M];//鏈表頭數組 int f[M][2],pow[M],visit[M]; //pow[]權值數組,p[i]表示第i個節點的權值 //f[i][1]保留節點i時最大權值,f[i][0]不保留節點i時的最大權值 //visit[i]==1表示i點被訪問過,visit[i]==0表示節點i未被訪問過 //添加邊(對稱的) void addADJ(int u,int v) { Child *p,*q; p=(Child*)malloc(sizeof(Child)); p->vex=v; p->next=head[u]; head[u]=p; q=(Child*)malloc(sizeof(Child)); q->vex=u; q->next=head[v]; head[v]=q; } //動態規劃獲取結果 void GetResul(int v) { visit[v]=1; Child *p; for(p=head[v];p!=NULL;p=p->next) { if(visit[p->vex]==0) { GetResul(p->vex); f[v][1] = f[v][1]+f[p->vex][0]; f[v][0]+=max(f[p->vex][0],f[p->vex][1]); } } f[v][1]+=pow[v]; } int main() { int i,j,u,v,n; memset(head,NULL,sizeof(head)); memset(f,0,sizeof(f)); memset(visit,0,sizeof(visit)); scanf("%d",&n); for(i=1;i<=n;i++) { scanf("%d",&pow[i]); } for(i=1;i<n;i++) { scanf("%d%d",&u,&v); addADJ(u,v); } GetResul(1);//從節點1開始進行動態規劃 printf("%d\n",max(f[1][0],f[1][1]));//結果輸出 return 0; }