题目链接:http://www.spoj.com/problems/COT/en/
题目大意:
在一棵树上,求任意两点路径上的点的权值的第k小。
解题思路:
刚看这个问题的时候以为是树链剖分+主席树,但是后来想了好久树链剖分剖出来的线性结构由于不连续导致貌似没办法使用主席树进行维护从而求第k小,然后纠结了好久,还是去看了别人的题解,发现思路跟线性结构的主席树几乎一样,但是略微有点差别,就是更新时候的区别。
首先这道题是树型结构,那么我们从根结点开始遍历,每到一个结点,就更新一棵线段树,保证我当前的线段树是包含这个结点及其上面的所有结点,然后开始找它们之间的关系。
首先线性结构的很简单,前缀和 a[v]-a[u-1] 即可
那么树上其实也有它自己的结构 假设要求u,v之间的第k小 那么前缀和维护出来应该是 a[v]+a[u]-a[lca(u,v)]-a[fa[lca(u,v)]],这里用到了lca 不会的赶紧去学新的技能吧,至于为什么是这样,仔细想想这里面每个结点的数包含哪些结点就知道了。
剩下就很简单了,裸的主席树查找,以下贴代码,
#include
#define pb push_back
#define pow POW //这里因为忘了pow是函数 尴尬
using namespace std;
typedef long long LL;
const int maxn = 1e5+30;
const int pow = 18;
int n,m,a[maxn],fa[maxn],root[maxn];
int tot;
int d[maxn],dp[maxn][pow]; //求lca要用的数组
vector vk[maxn]; //建树
vector v; //利用vector进行去重
struct node //线段树结点
{
int l,r,sum;
}t[maxn*40];
int getid(int x){ return lower_bound(v.begin(),v.end(),x)-v.begin()+1; }
void update(int l,int r,int &x,int y,int pos) //更新线段树
{
t[++tot]=t[y];
t[tot].sum++;
x=tot;
if(l==r)
return ;
int mid=(l+r)>>1;
if(pos<=mid)
update(l,mid,t[x].l,t[y].l,pos);
if(pos>mid)
update(mid+1,r,t[x].r,t[y].r,pos);
}
int query(int l,int r,int x,int y,int la,int fla,int k) //这里变量较多 注意一下即可
{
if(l==r)
return l;
int mid=(l+r)>>1;
int sum=t[t[y].l].sum+t[t[x].l].sum-t[t[la].l].sum-t[t[fla].l].sum; //利用先前得到的结论
if(sum>=k)
return query(l,mid,t[x].l,t[y].l,t[la].l,t[fla].l,k);
else
return query(mid+1,r,t[x].r,t[y].r,t[la].r,t[fla].r,k-sum);
}
void dfs(int u,int f) //dfs遍历求出dp数组和d数组 以及建立主席树
{
fa[u]=f,d[u]=d[f]+1,dp[u][0]=f;
for(int i=1;id[b])
swap(a,b);
if(d[a]=0;i--)
{
if(dp[a][i]!=dp[b][i])
{
a=dp[a][i];
b=dp[b][i];
}
}
a=dp[a][0],b=dp[b][0];
}
return a;
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=0;i<=n;i++)
vk[i].clear();
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
v.pb(a[i]);
}
tot=0;
sort(v.begin(),v.end()); //排序去重
v.erase(unique(v.begin(),v.end()),v.end());
int uu,vv,k;
for(int i=1;i