洛谷3233 HNOI2014(虛樹+dp)
膜拜一發
這兩位爺是真的無敵!
首先來看這個題,一看題目的資料範圍和“關鍵點”字眼,我們就能得知這是一道虛樹題
那就先一如既往的建出來虛樹吧
QWQ
但是這之後,應該怎麼去dp呢。
首先,我們需要知道在虛樹上每個點的從屬都是誰,這樣才便於我們進一步擴充套件到虛樹之外的點。
那麼怎麼求這個東西呢?我們可以先通過一編dfs,求出來子樹對父親的影響,也就是從下到上的答案(先 到底,再更新)
void dp1(int x,int flag)
{
dis[x]=inf;
bel[x]=0;
ans[x]=0;
if (tag[x]==flag)
{
dis[x]=0;
bel[x]=x;
}
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
int now = val[i];
dp1(p,flag);
if ((dis[x]>dis[p]+val[i]) || (dis[x]==dis[p]+val[i] && bel[x]>bel[p]))
{
dis[x]=dis[p]+val[i];
bel[x]=bel[p];
}
}
}
然後呢,因為還存在說通過兄弟更新,或者子樹之外的點更新的情況,所以我們還需要重新 一遍,不過這次是嘗試通過用父親來更新兒子,也就是從上到下(先更新,後 )
void dp2(int x,int flag)
{
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
int now = val[i];
if ((dis[p]>dis[x]+val[i]) || (dis[p]==dis[x]+val[i] && bel[p]>bel[x]))
{
dis[p]=dis[x]+val[i];
bel[p]=bel[x];
}
dp2(p,flag);
}
}
至此,我們就得到了所有虛樹上的點的 和 ,那怎麼擴充套件到所有點呢QWQ
這裡就需要一個奇妙的統計答案的技巧了
我們另 表示與 相同議事處的點的個數。
首先,我們將初值弄成 ,是i在原樹的子樹大小(這一定是不對的,因為子樹中有一些會和他的某個非直系子輩給包含,而他在上面的一片區域,也一定有和他一樣的點)
然後我們進行dfs
對於這個東西,顯然是要從下向上更新的
所以我們
到底,對於當前
這條邊,如果說兩個點的
是相等的,我們就令
,相當於把原樹
這路徑附近部分所有的點,都給了
,不論是合法還是不合法。
那麼上一種情況裡面不合法的情況,就是兩個點之間存在 不一樣的點,也就是說,會存在一條邊 ,其中 ,那麼這條路徑之間的東西應該怎麼算呢。
不難發現,一定是會存在說,這段路徑中間會有一個點,以上全是屬於 ,以下全是屬於 的。
那麼我們可以通過倍增的方式來求出這個點(具體求的時候有一些細節,直接寫在程式碼裡面了)
然後假設求出來的點是 ,那麼
原理的話,和上面同理
這種用ymh陣列求解的方式,實際上就是先弄一個初值,然後把不合法的(或者是會算重複的)減掉,然後把少算的加進去
QWQ總之就是很巧妙!!!!!!!
既不會算少,也不會算重複
直接放程式碼
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk makr_pair
#define ll long long
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int maxn = 6e5+1e2;
const int maxm = 2*maxn;
const int inf = 1e9;
int point[maxn],nxt[maxm],to[maxm],val[maxm];
int bel[maxn],dis[maxn],f[maxn][21];
int num[maxn];
int size[maxn],deep[maxn],dfn[maxn];
int cnt,n,m;
int tot,top;
int s[maxn];
int k,a[maxn];
int ymh[maxn],tag[maxn];
int ans[maxn];
void addedge(int x,int y,int w)
{
nxt[++cnt]=point[x];
to[cnt]=y;
val[cnt]=w;
point[x]=cnt;
}
void dfs(int x,int fa,int dep)
{
deep[x]=dep;
dfn[x]=++tot;
size[x]=1;
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
if (p==fa) continue;
f[p][0]=x;
dfs(p,x,dep+1);
size[x]+=size[p];
}
}
void init()
{
for (int j=1;j<=20;j++)
for (int i=1;i<=n;i++)
{
f[i][j]=f[f[i][j-1]][j-1];
}
}
int go_up(int x,int d)
{
for (int i=0;i<=20;i++)
{
if (d & (1<<i))
x=f[x][i];
}
return x;
}
int lca(int x,int y)
{
if (deep[x]>deep[y]) x=go_up(x,deep[x]-deep[y]);
else y=go_up(y,deep[y]-deep[x]);
if (x==y) return x;
for (int i=20;i>=0;i--)
{
if (f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
bool cmp(int a,int b)
{
return dfn[a]<dfn[b];
}
void solve()
{
sort(a+1,a+1+k,cmp);
cnt=0;
top=1;
s[top]=1;
for (int i=1;i<=k;i++)
{
int l = lca(s[top],a[i]);
if (l!=s[top])
{
while (top>1)
{
if (dfn[s[top-1]]>dfn[l])
{
addedge(s[top-1],s[top],deep[s[top]]-deep[s[top-1]]);
top--;
}
else
{
if (dfn[s[top-1]]==dfn[l])
{
addedge(s[top-1],s[top],deep[s[top]]-deep[s[top-1]]);
top--;
break;
}
else
{
addedge(l,s[top],deep[s[top]]-deep[l]);
s[top]=l;
break;
}
}
}
}
if (s[top]!=a[i]) s[++top]=a[i];
}
while (top>1)
{
addedge(s[top-1],s[top],deep[s[top]]-deep[s[top-1]]);
top--;
}
}
void dp1(int x,int flag)
{
dis[x]=inf;
bel[x]=0;
ans[x]=0;
if (tag[x]==flag)
{
dis[x]=0;
bel[x]=x;
}
for (int i=point[x];i;i=nxt[i])
{
int p = to[<