題意:
給出一個樹,讓你選擇一個刪掉(根據樹的定義,我們可以知道,只要刪去的該節點的度,那麼就可以得到個子樹。)
對於剩下的子樹中,如果存在和,對於中的 結點對(,),且和之間有邊相連。若存在一個映射關係,使得 中存在結點對,且和之間也有邊相連,那麼和即是identical的。 現在想讓你找到一個點使得這樣的identical的子樹數量最多,並輸出樹的數量。
剛看到題,其實挺懵逼的,簡單分析了一下之後,覺得如果要使得這樣的identical的子樹對越多,那麼肯定是要找度多一些且每一棵分出來的子樹結點要均衡。 然後就不知道該怎麼入手了。
後面看了題解,瞭解到了樹的重心的概念和樹同構的算法(還是基礎太弱了…)
樹的重心
百度百科的定義:樹的重心也叫樹的質心。找到一個點,其所有的子樹中最大的子樹節點數最少,那麼這個點就是這棵樹的重心,刪去重心後,生成的多棵樹儘可能平衡。
對這道題來說,很顯然刪掉的點應該是樹的重心,因爲刪去重心後,剩下的樹儘可能地平衡(節點數相近) 且這樣子樹的數量也會增加,這樣identical的樹對理論上應該會比其他切割點的情況大。(筆者只能口胡,感覺上是正確的
找樹的重心
void dfs(int u,int fa){
siz[u] = 1; vis[u] = true;
int maxn = 0;
for(int i = head[u]; ~i; i = edge[i].nxt){
Edge &e = edge[i];
if(!vis[e.v]) dfs(e.v,u);
siz[u] += siz[e.v];
maxn = max(maxn,siz[e.v];
}
maxn = max(maxn,n - siz[u]);
if(maxans > maxn){
update(ans,maxans);
}
}
然後再看題目:
對於剩下的子樹中,如果存在和,對於中的 結點對(,),且和之間有邊相連。若存在一個映射關係,使得 中存在結點對,且和之間也有邊相連,那麼和即是identical的。
對於這個限制條件該如何轉化?
對於這個題剛開始,我只能想到 兩棵樹要是能identical,那麼他們兩個的結點數一定是一樣的。
#include <bits/stdc++.h>
#define base 2333
using namespace std;
typedef long long ll;
const int maxn = 4e3+5;
const int INF = 0x3f3f3f3f;
const ll seed = 1e6+7;
const ll mod = 998244353;
struct Edge{
int u,v,nxt;
}edge[maxn<<1];
int head[maxn],tot;
inline void addedge(int u,int v){
edge[++tot] = {u,v,head[u]};
head[u] = tot;
}
int siz[maxn]; bool vis[maxn];
int zxNode[maxn]; int id;
inline void dfs(int u,int fa,int &maxans,int n){
// this function is to find the tree centrio
vis[u] = true; siz[u] = 1;
int TheMax = 0;
for(int i = head[u]; ~i; i = edge[i].nxt){
Edge &e = edge[i];
if(!vis[e.v] && u!=fa){
dfs(e.v,u,maxans,n);
siz[u] += siz[e.v];
TheMax = max(TheMax,siz[e.v]);
}
}
TheMax = max(TheMax,n-siz[u]);
if(TheMax < maxans){
zxNode[id=1] = u;
maxans = TheMax;
}else if(TheMax == maxans){
zxNode[++id] = u;
}
}
ll HashVal[maxn][maxn],tp[maxn];
inline ll Hash_Tree(int u,int fa){
// u is Node_index,p is subTree's Node num,k is k th Tree
ll cnt = 0; ll q[maxn];
for(int i = head[u]; ~i; i = edge[i].nxt){
Edge &e = edge[i];
if(vis[e.v] || e.v==fa) continue;
tp[u] = Hash_Tree(e.v,u);
}
for(int i = head[u]; ~i; i = edge[i].nxt){
Edge &e = edge[i];
if(vis[e.v] || e.v==fa) continue;
q[++cnt] = tp[e.v];
}
sort(q + 1,q + cnt + 1); ll now = seed;
for(int i = 1; i <= cnt; ++i) now = (now*base + q[i]);
cout << u << ' ' << now*base + seed +1 << endl;
return tp[u] = now * base + seed +1;
}
int fid[maxn][maxn];
inline void init(int id,int u){
fid[id][++fid[id][0]] = u; vis[u] = 1;
for(int i = head[u]; ~i; i = edge[i].nxt){
Edge&e = edge[i];
if(!vis[e.v]) init(id,e.v);
}
}
inline void check_ans(int u,int ans,int &res,int n){
int num = 0;
memset(HashVal,0,sizeof(HashVal)); memset(vis,0,sizeof(vis)); vis[u] = 1;
for(int i = 1; i <= n; ++i){
if(!vis[i]) ++num,fid[i][0] = 0,init(num,i);
}
memset(vis,0,sizeof(vis)); vis[u] = 1;
for(int i = 1; i <= num; ++i){
if(i > 1 && fid[i][0] != fid[i-1][0]) return;
for(int j = 1; j <= fid[i][0]; ++j){
HashVal[i][j] = Hash_Tree(fid[i][j],0);
cout << "---------------------------" << endl;
// cout << HashVal[i][j] << endl;
}
sort(HashVal[i]+1,HashVal[i]+1+fid[i][0]);
if(i > 1){
int q = 0; while(q <= fid[i][0]) if(HashVal[1][++q]!=HashVal[i][q])return;
}
}
res = max(res,num);
return;
}
int main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
memset(head,-1,sizeof(head));
int n; cin >> n; for(int i = 1,u,v; i < n; ++i) cin >> u >> v,addedge(u,v),addedge(v,u);
int ans = INF; dfs(1,0,ans,n); int res = -1;
for(int i = 1; i <= min(2,id); ++i){
check_ans(zxNode[i],ans,res,n);
}
cout << res << endl;
return 0;
}