题
OwO http://acm.hdu.edu.cn/showproblem.php?pid=6065
(2017 Multi-University Training Contest - Team 3 - 1010)
解
首先,一个连续段的LCA就是每相邻两个点的LCA的最小值
证明:
假设有一段区间s1~sn,称这段区间为S,他们的LCA是U,那么U必然存在多个后继,这里为了方便假设U存在2个后继,若后继不止2个,可类似得到结论
那么这两个后继就有对应的2个子树A,B,由于S的LCA是U,所以A,B中均有S的元素。那么设S中在子树A中的元素为a1~ap,在子树B中的元素为b1~bq。
不妨设s1存在于A中。对于si,如果si+1和si不同在A或不同在B中,那么对于si和si+1,他们的LCA就是u
那么,如果不存在相邻两点他们LCA为u的话,对于任意si,si和si+1他们同在A或同在B,由于s1在A,所以s1~sn全在A中,那么区间S中就没有B的元素,则S的LCA就不是U,矛盾。
那么可以先预处理出每两个相邻点的LCA
然后声明一个dp数组dp[i][j]的意义为,从1~i分成j段的最小答案。
把P分成k个连续段P1,P2…Pn,则设这些段中LCA深度最小的相邻两点对为pair1,pair2,pair3…pairn,那么我们可以把它们当做由这些pair领头的序列(开头可以出现一段不计入答案的序列)(如果这些段只有一个点那么就不变)
这样的dp[i][j]就可以由以下3种方式推导过来
1.dp[i][j]=dp[i-1][j] 相当于把第j段扩展下去,由于最小两对是开头,所以不用更新其值
2.dp[i][j]=dp[i-2][j-1]+depth[LCA(P[i-1],P[i])] 就是从分成j-1段那里递推来,然后了个开头(开头为2两个相邻点,这两个相邻点当做第j段LCA深度最小的相邻点)
3.dp[i][j]=dp[i-1][j-1]+depth[P[i]] 也是从分成j-1段那里递推过来,只不过开头是一个点,
这样dp[n][k]就是答案
(思路来自某位大佬 orz)
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
const int M=3e5+55;
const int MAXN = 340044;
const int MAXQ = 500010;
int F[MAXN];//需要初始化为-1
int find(int x)
{
if(F[x] == -1)return x;
return F[x] = find(F[x]);
}
void bing(int u,int v)
{
int t1 = find(u);
int t2 = find(v);
if(t1 != t2)
F[t1] = t2;
}
bool vis[MAXN];//访问标记
int ancestor[MAXN];//祖先
struct Edge
{
int to,next;
}edge[MAXN*2];
int head[MAXN],tot;
void addedge(int u,int v)
{
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
}
struct Query
{
int q,next;
int index;//查询编号
}query[MAXQ*2];
int answer[MAXQ];//存储最后的查询结果,下标0~Q-1
int h[MAXQ];
int tt;
int Q;
void add_query(int u,int v,int index)
{
query[tt].q = v;
query[tt].next = h[u];
query[tt].index = index;
h[u] = tt++;
query[tt].q = u;
query[tt].next = h[v];
query[tt].index = index;
h[v] = tt++;
}
void init()
{
tot = 0;
memset(head,-1,sizeof(head));
tt = 0;
memset(h,-1,sizeof(h));
memset(vis,false,sizeof(vis));
memset(F,-1,sizeof(F));
memset(ancestor,0,sizeof(ancestor));
}
void LCA(int u)
{
ancestor[u] = u;
vis[u] = true;
for(int i = head[u];i != -1;i = edge[i].next)
{
int v = edge[i].to;
if(vis[v])continue;
LCA(v);
bing(u,v);
ancestor[find(u)] = u;
}
for(int i = h[u];i != -1;i = query[i].next)
{
int v = query[i].q;
if(vis[v])
{
answer[query[i].index] = ancestor[find(v)];
}
}
}
int dep[M];
int n,k;
int s[M];
int ans;
vector<vector<int> > dp;
void getdep(int rt,int pa,int depth)
{
int i,j,v;
dep[rt]=depth;
for(i=head[rt];i!=-1;i=edge[i].next)
{
v=edge[i].to;
if(v==pa)
continue;
getdep(v,rt,depth+1);
}
}
void solve()
{
int i,j,tmp;
getdep(1,-1,1);
for(i=0;i<=n;i++)
dp[i][0]=0;
for(i=1;i<=n;i++)
for(j=1;j<=min(i,k);j++)
{
tmp=1e9+7;
if(i-1>=1)
tmp=min(tmp,dp[i-2][j-1]+dep[answer[i-1-1]]);
// cout<<i<<' '<<j<<' '<<tmp<<endl;
tmp=min(tmp,dp[i-1][j-1]+dep[s[i]]);
if(i-1>=j)
tmp=min(tmp,dp[i-1][j]);
// cout<<i<<' '<<j<<' '<<tmp<<endl;
dp[i][j]=tmp;
// cout<<i<<' '<<j<<' '<<tmp<<endl;
}
ans=dp[n][k];
cout<<ans<<endl;
}
int main()
{
int i,j;
int u,v;
while(scanf("%d%d",&n,&k)!=EOF)
{
dp.assign(n+2,vector<int>(k+2,1e9+7));
init();
for(i=1;i<=n;i++)
scanf("%d",&s[i]);
for(i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
}
Q=n-1;
for(i=0;i<Q;i++)
add_query(s[1+i],s[1+i+1],i);
LCA(1);
solve();
}
return 0;
}