1. 程式人生 > 實用技巧 >序章-弗蘭德的祕密——利用資料性質的暴力

序章-弗蘭德的祕密——利用資料性質的暴力

序章-弗蘭德的祕密

背景介紹
弗蘭德,我不知道這個地方對我意味著什麼。這裡是一切開始的地方。3年前,還是個什麼都沒見過的少年,來到弗蘭德的樹下,走進了封閉的密室,扭動的封塵已久機關,在石板上知道了這個世界最角落的最陰暗的東西。那種事情,從未忘懷,從未動搖,我還記得,那一天,我,裡修,第一次拔起了劍……

弗蘭德的密室裡,機關上方畫著兩棵樹的字樣,機關下方是一個有數字的刻度……
弗蘭德最高的兩棵樹,只要知道兩棵樹的共同的相似度就行了……
給定兩棵有根樹,可以任意刪除兩棵樹上的節點(刪除一棵節點必須保證該節點的子樹內的所有節點也必須要被刪除,換一種說法,刪除後的樹必須聯通並形成一棵樹,且根節點不能被刪除),使得刪除後的兩棵樹同構,這兩棵樹有一個共同大小,即樹的

size,最大化同構的樹的size即為機關的答案……

注:兩棵同構的樹要滿足以下條件:
1、兩棵樹節點個數相等。
2、兩棵樹的以根節點的兒子為根子樹對應同構。如下圖,為兩棵同構的有根樹。
如下圖,為兩棵同構的有根樹。

一行兩個整數n,m分別表示兩棵有根樹的大小。
以下n-1行描述第一棵樹,每行兩個數x,y表示x號節點是y號節點父親。
以下m-1行描述第二棵樹,每行兩個數x,y表示x號節點是y號節點父親。
資料保證兩棵樹的1號節點均為根。

一行一個數,表示兩棵樹的相似度(刪除後最大化的同構樹的大小)。

33
12
13
12
23

2
【樣例解釋】
第一棵樹可以保留1號節點和2號節點刪除3號節點,也可以保留1號節點與3號節點刪除2號節點,

第二棵樹保留1號節點和2號節點刪除3號節點。
剩下的樹同構,樹的節點個數均為2。

對於30%的資料,1≤n≤10
對於60%的資料,1≤n≤100
對於100%的資料,1≤n≤1000資料保證兩棵樹上每個節點的度均不超過5。

分析:

其實這題目啊,正解和暴力都是一樣的,都是列舉兩棵樹的每個點的全排列,只是由於資料限制每個點的度不超過5,所以dp成了正解。這其實需要我們挖掘條件的能力,現在我們來挖掘一下。我們要以dp的思想去思考,一般而言dp所維護的與答案所求的有一定關係,不妨大膽設f[x][y]為A樹以x為根節點的子樹與B樹以y為根節點的子樹的最大同構數。很明顯的,我們要和暴力一樣去一一列舉配對(總感覺我在吐槽),然後搜搜搜,說的也許有點抽象,所以來串程式碼冷靜一下。

程式碼:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<queue>
 6 #include<algorithm>
 7 #include<vector>
 8 using namespace std;
 9 #define debug printf("zjyvegetable\n")
10 #define int long long
11 inline int read(){
12     int a=0,b=1;char c=getchar();
13     while(!isdigit(c)){if(c=='-')b=-1;c=getchar();}
14     while(isdigit(c)){a=a*10+c-'0';c=getchar();}
15     return a*b;
16 }
17 const int N=2e3+50,M=2e5+50;
18 vector<int>vec1[N],vec2[N];
19 int n,m,f[N][N],xx,yy,vis[M];
20 void dfss(int k,int z){
21     if(k>=vec1[xx].size()){
22         f[xx][yy]=max(f[xx][yy],z);
23         return;
24     }
25     dfss(k+1,z);
26     bool flag=true;
27     for(int i=0;i<vec2[yy].size();i++){
28         if(!vis[i]){
29             vis[i]=1;
30             flag=false;
31             dfss(k+1,z+f[vec1[xx][k]][vec2[yy][i]]);
32             vis[i]=0;
33         }
34     }
35     if(flag)f[xx][yy]=max(f[xx][yy],z);
36 }
37 void dfs(int x){
38     if(!vec1[x].size()){
39         for(int i=1;i<=m;i++)
40         f[x][i]=1;
41         return;
42     }
43     for(int i=0;i<vec1[x].size();i++)
44     dfs(vec1[x][i]);
45     for(int i=1;i<=m;i++){
46         if(!vec2[i].size())f[x][i]=1;
47         else{
48             xx=x;yy=i;
49             dfss(0,0);
50             f[x][i]++;
51         }
52     }
53 }
54 signed main(){
55     //freopen("frand.in","r",stdin);
56     //freopen("frand.out","w",stdout);
57     int u,v;
58     n=read();m=read();
59     for(int i=1;i<n;i++){
60         u=read();v=read();
61         vec1[u].push_back(v);
62     }
63     for(int i=1;i<m;i++){
64         u=read();v=read();
65         vec2[u].push_back(v);
66     }
67     dfs(1);
68     printf("%lld\n",f[1][1]);
69     return 0;
70 }

補充:

其實理解這段程式碼要用暴力的思想去想,你會發現其實沒什麼差。另外,這串程式碼的flag部分其實是可以省去的,這只是從意義上更好理解所以才加上的,但很容易發現flag部分所維護的最大值一定會比搜到邊界的值小(或等於)。