HDU 5732 2016多校Contest 1 Subway【找樹的重心,判斷樹的同構】
題目大意:
給定一棵樹,這兩棵樹肯定是同構的。
問你,第一棵樹的每個節點,可以對應第二個樹的那個節點。 顯然對應方法不唯一,SPJ來檢測結果正確。
方法:
首先找樹的重心, 樹的重心最多2個。
一個重心的情況很多,兩個重心的情況如圖:
有人說這個圖太對稱了……
那給個不對稱的。
這個圖很重要……涉及到一些奇怪的情況
求樹的重心,是一個簡單的tree dp
f[i]表示i如果為根(為整棵樹的根的情況下),所有兒子節點的size的max。
s[i]表示在做DP中,i的所有兒子的節點總數。
f[i] = max(s[j], n-sum(s[j])-1) 其中j為i的兒子。
然後找f[]陣列中元素的最小值,就是樹的重心了。
如果第一棵樹的重心為A,B。 第二顆數的重心是C,D
那麼就檢查以A為根,以C為根的兩棵樹是否同構,再檢查(A,D),(B,C),(B,D)是否同構。同構則相對著輸出即可。
題解這裡判斷同構的是給樹進行雜湊。給的方案是:
(我理解的……)
照抄題解“
這裡提供一種方法:首先求解樹的中點,然後將中點作為根。只有一個結點的子樹雜湊值為 1。選一個比較大的質數P和一個特別大的質數Q。對於每一顆樹,把它的所有子樹的雜湊 值排序。然後hash=sum(Pi∗hash[i])%Q,就能算出來總體的雜湊值。有兩個中點的樹兩個 中點都試一下。為了保險可以檢查下雜湊值有沒有重的。
”
葉子節點的雜湊值為1,假設要求K節點的雜湊值。
K有兒子節點A,B,C。並且A<=B<=C(排序好了)
hash[k] = (P^1*hash[A])+ (P^2*hash[B])+(P^3*hash[C]) % Q
P,Q為素數。
然後就出問題了……
我上面圖上的那個情況,按照我理解的題解的做法,不管你素數取多少,都會重複。導致不管選哪一對重心,都會導致雜湊值相同……
然後我給雜湊函式改一下,排序好的A,B,C.... 並不都用P這一個素數,每5個數字迴圈一次,用5個素數來
hash[k] = (P[1]^1*hash[A])+ (P[2]^2*hash[B])+(P[3]^3*hash[C]) + ....+ [P[I%5+1]^I * hash[?]] % Q
這樣…… 然後就AC了。
當然題解說,需要檢查雜湊值是否有重的。。。那樣好麻煩,還是選一些比較不太容易重的雜湊函式吧~
程式跑了2秒7,幾乎馬上就要超時了……
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstring>
#include <vector>
#include <map>
#include <string>
using namespace std;
typedef long long LL;
const int maxn = 100000 + 100;
int n;
map<string,LL>mp;
int mp_tail=0;
vector<LL> g1[maxn];
vector<LL> g2[maxn];
const LL P[] = {435617, 999983, 327827, 674249, 986191}; //一共5個素數
const LL mod = 1e9+7;
string a1[maxn],a2[maxn];//車站下標,對應的車站名
LL powMod( LL a , LL b , LL p = mod )//a^b % p
{
LL r = 1 ;
a %= p ;
while( b )
{
if( b&1 ) r = r*a%p ;
b >>= 1 ;
a = a*a%p ;
}
return r ;
}
LL get(char s[]) //判斷這個字串是否出現過,並根據情況新增進map,並返回這個字串對應的數字
{
string x="";
for (int i = 0; s[i]!=0; x+=s[i++]);
//cout<<"!!!"<<x<<endl;
if (mp.find(x) == mp.end())
{
mp[x]= ++mp_tail;
}else return mp[x];
return mp_tail;
}
void init()
{
for (int i = 0; i<= n; ++ i)
{
g1[i].clear();
g2[i].clear();
}
mp.clear();
mp_tail=0;
char a[100],b[100];
for (int i = 1; i < n; ++ i)
{
scanf("%s%s",a, b);
LL A = get(a);
a1[A]=a;
LL B = get(b);
a1[B]=b;
g1[A].push_back(B);
g1[B].push_back(A);
}
for (int i = 1; i < n; ++ i)
{
scanf("%s%s",&a, &b);
LL A = get(a);
a2[A]=a;
LL B = get(b);
a2[B]=b;
g2[A].push_back(B);
g2[B].push_back(A);
}
}
LL dp(int now, vector<LL> g[], LL f[], int fa) //求一棵樹的重心的程式
{
LL sum = 0;
f[now] =0;
if (g[now].size()==0)
{
return 1;
}
for (int i = 0; i != g[now].size(); ++ i)
{
int will = g[now][i];
if (will == fa) continue;
LL tmp =dp(will, g, f, now);
sum += tmp;
f[now] = max(f[now], tmp);
}
if (now != fa) f[now] = max(f[now], n - sum - 1);
return sum + 1;
}
LL f1[maxn], f2[maxn];
LL f3[maxn];
vector<LL>zhong1;
vector<LL>zhong2;
struct node
{
LL hash, id;
node(){}
node(LL _hash, LL _id)
{
hash = _hash;
id = _id;
}
};
bool operator < (node a, node b)
{
return a.hash < b.hash;
}
vector<node> tmp[maxn];
vector<LL>g3[maxn], g4[maxn];
LL heav(int now, vector<LL> g[], LL f[], int fa, vector<LL> h[])
{
if (g[now].size() == 1 && g[now][0] == fa)
//這裡有個程式陷阱。。。判斷是否為葉子節點,並不是size=0,而是size=1,並且這個節點為父親。
{
return f[now] = 1;
}
LL hash = 0;
tmp[now].clear();
for (int i = 0; i != g[now].size(); ++ i)
{
int will = g[now][i];
if (will == fa) continue;
LL t = heav(will, g, f, now, h); //得到兒子節點的hash值,儲存進臨時陣列中,為了重新構圖用。
tmp[now].push_back(node(t,will));
}
sort(tmp[now].begin(), tmp[now].end());//根據兒子節點的雜湊值排序
for (int i = 0; i != tmp[now].size(); ++ i)//根據兒子節點的雜湊值,計算自己節點的雜湊值
{
h[now].push_back(tmp[now][i].id);
hash += powMod(P[i%5], i + 1, mod) * tmp[now][i].hash;
hash %= mod;
}
f[now] = hash;
return f[now];
}
void pg(int a, int b) //第一棵樹在a節點,第二課樹在b節點,這2個節點彼此對應,同時遍歷兩棵樹,輸出他們。
{
printf("%s %s\n", a1[a].c_str(), a2[b].c_str());
//這裡a1,a2儲存的是編號對應的字元。 題目給的是a,b,c之類的字串,然後字串被化為了數字,這裡要重新輸出字串
for (int i = 0; i != g3[a].size(); ++ i)
{
int will1 = g3[a][i];
int will2 = g4[b][i];
pg(will1, will2);
}
}
bool check(int a, int b)
{
//cout<<a<<" "<<b<<endl;
for (int i = 0; i <=n;++i)
{
g3[i].clear();
g4[i].clear();
}
heav(a, g1, f1, 0, g3);
//去計算第一棵樹,以a為根,g1儲存的第一棵樹的邊的情況,f1是返回每個節點的雜湊值。 0表示父親,為了遍歷樹避免訪問到父親。 g3為返回一棵有根樹。
heav(b, g2, f2, 0, g4);
//計算第二顆數的資訊
if (f1[a] == f2[b]) //判斷兩棵樹的根節點的雜湊值是否相同
{
pg(a,b); //相同,則輸出答案
return 1;
}
return 0;
}
void doit()
{
dp(1, g1, f1, 1);
dp(1, g2, f2, 1);
zhong1.clear();
zhong2.clear();
memmove(f3,f1,sizeof(f3));
sort(f3+1,f3+1+n);
int tmp = f3[1];
for (int i = 1; i <= n; ++ i)
{
if (f1[i]==tmp) zhong1.push_back(i);
if (f2[i]==tmp) zhong2.push_back(i);
}
for (int i = 0; i != zhong1.size(); ++ i)
for (int j = 0; j != zhong2.size(); ++ j)
{
if (check(zhong1[i], zhong2[j])) return;
}
}
int main()
{
while (~scanf("%d", &n))
{
init();
doit();
}
return 0;
}