hihoCoder 1387 A Research on "The Hundred Family Surnames"

题面

描述

The Hundred Family Surnames is a classic Chinese text composed of common Chinese surnames. The book was composed in the early Song Dynasty. It originally contained 411 surnames, and was later expanded to 504. Of these, 444 are single-character surnames, and 60 are double-character surnames. (quoted from Wikipedia)

Li Lei, a student of Peking University, has done some research on that name book. He wrote a program to built a surname tree, as implied by the book. Here, "tree" is a concept of graph  theory. On Li's surname tree, each node is a Chinese surname. A Chinese surname consists of only English letters and its length is no more than 5 letters.

There is a mysterious legend about this surname tree. If a pair of Chinese loves can find a path on the tree whose two end points are their surnames, they will have a happy marriage. The longer the path is , the more happiness they will have.

Now, we have many pairs of lovers, they want to find out the longest path whose two end points are their surnames.

输入

The input contains no more than 10 test cases.

For each case, the first line contains two positive integers N and Q(N,Q <= 105). N is the number of nodes on the tree which numbered from 1 to N, and Q is the number of queries.

Among the following N lines, the i-th line is the surname of node i .

In the next N-1 lines, each line contains two numbers x , y , meaning that there is an edge between node x and node y.

Then, each of the next Q lines is a query, which contains two strings meaning the surnames of two lovers.

输出

For every query , output the number of nodes on the longest happiness path. If the path does not exist, output  -1.

题解

 根据经验,解决超时的办法有两种。

  1. 优化

  2. 贪心

那么这道题该怎么贪心呢 ?

这里就要用到树的直径,即树上距离最远的一条路径。

我们先考虑一个点到一个子集(结构也是树)的最长路径,用反证法。

下面是点A到绿色点集的路径实例,BC是点集的直径。

先给出猜想:AC是最终的答案,即一个点到点集的最长路是这个点到直径两端之一的路径的较大的一条。

此时,如果有一个更优的答案,AD,比AC更长,

对不起,这是不可能的,因为这样的话绿色子集的直径就是CD了。

 

再来证一种情况,如果有两条直径相等怎么办?

还是点A到绿色点集的路径实例,BC和DE都是点集的直径。

先假设 DO1 = BO1 = CO1 = EO1,A从O2穿插进来,很明显,AB = AC = AD > AE

也就是说,任选一条直径,根据之前的结论,都能算出正确答案。

如果两条直径不是互相平分的怎么办?比如DO1比其他的都大?

那答案是不是就是AD了呢?

对不起,这也是不可能的,如果DO1比谁都大,那点集的直径就是BD或CD了。

那么,求一个点集的直径其实也可以这样求,每次加入一个点时,更新一下直径。

在这道题中,把每一种姓的点集的直径都求出来,最后求两个点集的最长路时,就把两条直径的四个端点间的距离互相两两比较,这个就易证了。

CODE

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<algorithm>
#define LL long long
#define MAXN 100005
using namespace std;
inline int read() {
	int f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {if(s == '-') f = -1;s = getchar();}
	while(s >= '0' && s <= '9') {x = x * 10 + s - '0';s = getchar();}
	return x * f;
}
struct ed{
	int v,w;
	ed(){v = w = 0;}
	ed(int V,int W){v = V;w = W;}
};
struct it{
	int id;
	LL k;
	it(){id = k = 0;}
	it(int I,LL K){id = I;k = K;}
}sch[MAXN];
bool operator < (it a,it b) {
	return a.k < b.k;
}
bool operator > (it a,it b) {
	return a.k > b.k;
}
vector<ed> g[MAXN];
LL c[MAXN];
int fa[MAXN][22];
int d[MAXN],dfn[MAXN];
int n,m,i,j,s,o,k,cnt;
LL dp[MAXN][10];
int b[MAXN];
struct npair{
	int p[2];
	int di;
	npair(){p[0] = p[1] = 0;di = 0;}
	npair(int L,int R){p[0] = L;p[1] = R;}
	npair(int L,int R,int D){p[0] = L;p[1] = R;di = D;}
};
map<LL,npair> mat;
bool cmp(int a,int b) {
	return dfn[a] < dfn[b];
}
void dfs(int x,int fat,int edge) {
	dfn[x] = ++cnt;
	fa[x][0] = fat;
	d[x] = d[fat] + 1;
	for(int i = 1;i <= 19;i ++) {
		fa[x][i] = fa[fa[x][i - 1]][i - 1];
	}
	for(int i = 0;i < g[x].size();i ++) {
		if(g[x][i].v != fat) {
			dfs(g[x][i].v,x,g[x][i].w);
		}
	}
}
int lca(int a,int b) {
	if(d[a] > d[b]) {
		for(int i = 19;i >= 0;i --) {
			if(d[fa[a][i]] >= d[b]) a = fa[a][i];
		}
	}
	if(d[a] < d[b]) {
		for(int i = 19;i >= 0;i --) {
			if(d[fa[b][i]] >= d[a]) b = fa[b][i];
		}
	}
	if(a == b) return a;
	for(int i = 19;i >= 0;i --) {
		if(fa[a][i] != fa[b][i]) {
			a = fa[a][i];
			b = fa[b][i];
		}
	}
	return fa[a][0];
}
int dis(int a,int b) {
	int lc = lca(a,b);
	return d[a] - d[lc] + d[b] - d[lc];
}
stack<int> st;
int v[MAXN];
LL K1,K2;
char ss[10];
LL charto[270];
int main() {
	for(int i = 'A';i <= 'Z';i ++) charto[i] = i - 'A';
	for(int i = 'a';i <= 'z';i ++) charto[i] = i - 'a' + 26;
	while(scanf("%d%d",&n,&m) == 2) {
		for(int i = 1;i <= n;i ++) {
			scanf("%s",ss + 1);
			int ll = strlen(ss + 1);
			c[i] = 0;
			for(int j = 1;j <= ll;j ++) c[i] = c[i] * 52ll + charto[ss[j]];
			g[i].clear();
			sch[i] = it(i,c[i]);
		}sort(sch + 1,sch + 1 + n);
		for(int i = 1;i < n;i ++) {
			s = read();o = read();
			g[s].push_back(ed(o,1));
			g[o].push_back(ed(s,1));
		}sch[0].k = sch[n + 1].k = 0;
		dfs(1,1,0);
		npair D;
		int ds = -0x7f7f7f7f;
		for(int i = 1;i <= n;i ++) {
			if(D.p[0] == 0 && D.p[1] == 0) {
				D = npair(sch[i].id,sch[i].id);
			}
			else {
				int ds2;int ii = sch[i].id,jj = D.p[1];
				ds2 = dis(D.p[0],ii); 
				if((ds2) > ds) {
					ds = ds2;
					D = npair(D.p[0],ii);
				}ds2 = dis(jj,ii);
				if((ds2) > ds) {
					ds = ds2;
					D = npair(jj,ii);
				}
			}
			if(sch[i].k != sch[i + 1].k) {
				mat[sch[i].k] = npair(D.p[0],D.p[1],ds);
//				printf("%lld: %d - %d\n",sch[i].k,D.p[0],D.p[1]);
				D = npair();
				ds = -0x7f7f7f7f;
			}
		}
		while(m --) {
			LL A = 0,B = 0;
			scanf("%s",ss + 1);
			int ll = strlen(ss + 1);
			for(int j = 1;j <= ll;j ++) A = A * 52ll + charto[ss[j]];
			scanf("%s",ss + 1);
			ll = strlen(ss + 1);
			for(int j = 1;j <= ll;j ++) B = B * 52ll + charto[ss[j]];
			npair p1 = mat[A],p2 = mat[B];
			if(p1.p[0] == 0 || p1.p[1] == 0 || p2.p[0] == 0 || p2.p[1] == 0) {
				printf("-1\n");
				continue;
			}
			int ans = 0;
			ans = max(ans,dis(p1.p[0],p2.p[0]));
			ans = max(ans,dis(p1.p[1],p2.p[0]));
			ans = max(ans,dis(p1.p[0],p2.p[1]));
			ans = max(ans,dis(p1.p[1],p2.p[1]));
			printf("%d\n",ans + 1);
		}
		mat.clear();
	}
	return 0;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值