【bzoj3672】购票
Portal -->bzoj3672
Solution
天知道我是怎么調完的qwq調到天昏地暗系列。。
?
不管這么多,先嘗試列一個最簡單的狀態轉移方程
用\(f[i]\)表示\(i\)點到\(1\)號點要花費的最少資金,\(dis[i]\)表示\(i\)到\(1\)號點的距離,那么有:
\[ f[i]=min(f[j]+p[i]*(dis[i]-dis[j])+q[i]) \]
其中\(j\)是\(i\)的祖先且\(dis[i]-dis[j]<=l[i]\)
? 然后直接轉移什么的肯定是不現實的啦。。所以我們可以先看看dp本身可以用什么優化
? 首先這個轉移式子,我們換一個方式來寫一下:
\[ f[j]=p[i]*dis[j]+(f[i]-p[i]*dis[i]+q[i]) \]
? 當考慮\(i\)的時候,\(-p[i]*dis[i]+q[i]\)可以看成一個常數,\(p[i]\)也可以看成一個常數,那么上面這個式子可以看成\(y=kx+b\)的形式,也就是說,是一條斜率為\(p[i]\),截距為\((f[i]-p[i]*dis[i]+q[i])\)的直線
然后我們可以考慮斜率優化啦,因為斜率不是單調的所以還是要老老實實在凸包上二分,然后這里我們要\(f[i]\)最小所以是維護下凸殼
然而
? 這題的dp在樹上。。而且還有兩個限制條件(\(lca(i,j)=j\)且\(dis[i]-dis[j]<=l[i]\))
? 關于\(lca(i,j)=j\)這個條件,自己一開始有一個比較初步的想法是每次用這個點去更新其子樹還想著寫線段樹然后再每個區間維護凸包什么之類的。。然而實際上這里有一種比較高級的姿勢:
? 樹上cdq分治(emm大神的博客里面是這么叫的)
?
? 這里的cdq分治的話,大致的過程是(其實寫起來跟點分差不多的。。看到網上也有大神說這個就是點分。。):
1、找一個點\(x\)將樹分為上下兩個部分(為了保證復雜度這里的\(x\)顯然應該要找重心)
2、先遞歸處理這個點\(x\)以上的(也就是包含當前根節點的)那一部分(相當于一般cdq的處理左邊區間)
3、用上一步中處理出來的\(x\)的祖先的信息來更新\(x\)的子樹(相當于統計左邊對右邊貢獻)
4、遞歸處理\(x\)的子樹(處理右邊)
?
? 這樣我們就解決了\(lca(i,j)=j\)的問題,接下來是第二個限制\(dis[i]-dis[j]<=l[i]\)
? 我們稍微處理一下這個不等式,變成:\(dis[i]-l[i]<=dis[j]\)
? 現在我們考慮用不同的\(j\)去更新\(i\)(就是步驟3中用祖先更新子樹)
因為\(dis\)是單調的,我們可以在更新之前先排個序,子樹內的點按照\(dis[i]-l[i]\)從大到小排,\(dis[j]\)也是從大到小(其實兩個都反過來也是可以的,只是因為我在程序里面寫的時候求祖先是暴力跳的,所以祖先的\(dis\)求出來就是從大到小的順序,所以就這么寫了)
? 我們按順序枚舉祖先,每次在將當前枚舉到的祖先\(j\)加到凸包里面去之前,先判斷一下當前的\(dis[i]-l[i]\)是否\(<=dis[j]\),如果不是的話,那么就在還沒有加入祖先\(j\)的凸包中二分來更新\(f[i]\),然后再將祖先\(j\)加入凸包;否則直接將\(j\)加入凸包
? 這里因為\(dis\)(也就是凸包上點的\(x\)坐標)是單調的所以可以直接單調棧維護
? 這樣就能保證我們在凸包中二分出來的答案一定是滿足\(dis[i]-dis[j]<=l[i]\)的
? 然后就非常愉悅地做完了,總的復雜度是\(O(nlog^2n)\)
?
一些可能要注意的細節&自己跳進去的坑:
1、本來凸包是寫的叉積的。。但是后來看了一下數據范圍有點擔心\(x*y\)一下直接爆longlong了。。所以就改成斜率了。。還是要注意一下可能會出現兩個\(x\)相等的情況所以要判一下避免除以\(0\)(之前那個坑爬得太艱難再也不相信除法了qwq)
2、注意是下凸殼下凸殼下凸殼!(調著調著把自己調懵了系列。。)
3、建凸包的時候注意祖先是按照\(dis\)從大到小來排的。。也就是橫坐標是遞減的。。
4、看清數據范圍。。各種long long
?
? 代碼大概長這個樣子(思路好像不算特別繞但是。。調起來就很。。了。。)
#include<iostream> #include<cstdio> #include<cstring> #include<vector> #include<algorithm> #define ll long long using namespace std; const int MAXN=2*(1e5)+10; const ll inf=1LL<<60; struct xxx{int y,nxt; }a[MAXN*2]; int h[MAXN],sz[MAXN],vis[MAXN],mx[MAXN],pre[MAXN]; int rec[MAXN],st[MAXN],rec_pre[MAXN]; ll p[MAXN],q[MAXN],l[MAXN],dis[MAXN]; ll f[MAXN]; int n,m,tot,rt,rt_mx,All,T; void add(int x,int y); void get_sz(int fa,int x); void get_rt(int All,int fa,int x); void dfs(int fa,int x); void solve(int x,int All); bool cmp(int x,int y){return dis[x]-l[x]>dis[y]-l[y];} void update(int x,int top); ll X(int i){return dis[i];} ll Y(int i){return f[i];} double get_k(int i,int j){if (X(i)==X(j)) return inf; return (1.0*(Y(i)-Y(j)))/(1.0*(X(i)-X(j)));}int main(){ #ifndef ONLINE_JUDGEfreopen("a.in","r",stdin); #endifscanf("%d%d\n",&n,&T);memset(h,-1,sizeof(h));tot=0;for (int i=2;i<=n;++i){scanf("%d%lld%lld%lld%lld\n",pre+i,dis+i,p+i,q+i,l+i);dis[i]+=dis[pre[i]];add(pre[i],i);}for (int i=1;i<=n;++i) f[i]=inf;f[1]=0;solve(1,n);for (int i=2;i<=n;++i) printf("%lld\n",f[i]); }void add(int x,int y){a[++tot].y=y; a[tot].nxt=h[x]; h[x]=tot; }void get_sz(int fa,int x){int u;sz[x]=1; mx[x]=0;for (int i=h[x];i!=-1;i=a[i].nxt){u=a[i].y;if (u==fa||vis[u]) continue;get_sz(x,u);sz[x]+=sz[u];mx[x]=max(mx[x],sz[u]);} }void get_rt(int All,int fa,int x){mx[x]=max(mx[x],All-sz[x]);if (mx[x]<=rt_mx) rt=x,rt_mx=mx[x];int u;for (int i=h[x];i!=-1;i=a[i].nxt){u=a[i].y;if (u==fa||vis[u]) continue;get_rt(All,x,u);} }void solve(int x,int All){int u,Rt;rt=-1,rt_mx=n;get_sz(0,x);get_rt(All,0,x);vis[rt]=1;Rt=rt;//處理上面的部分(如果有的話)if (Rt!=x)solve(x,All-sz[Rt]);//記錄子樹中的點并排好序 rec[0]=0;for (int i=h[Rt];i!=-1;i=a[i].nxt)if (!vis[a[i].y]) dfs(x,a[i].y);sort(rec+1,rec+1+rec[0],cmp);//記錄祖先(本身處理出來就是有序的了)rec_pre[0]=1; rec_pre[1]=Rt;for (int i=Rt;i!=x;i=pre[i]){if (dis[Rt]-dis[pre[i]]<=l[Rt])f[Rt]=min(f[Rt],f[pre[i]]+p[Rt]*(dis[Rt]-dis[pre[i]])+q[Rt]);rec_pre[++rec_pre[0]]=pre[i];}//統計祖先對子樹中點的貢獻int top=0,tot=1;for (int i=1;i<=rec_pre[0];++i){while (tot<=rec[0]&&dis[rec[tot]]-dis[rec_pre[i]]>l[rec[tot]])update(rec[tot],top),++tot;while (top>1&&get_k(st[top-1],rec_pre[i])<=get_k(st[top],rec_pre[i])) //down--top;st[++top]=rec_pre[i];}while (tot<=rec[0]) update(rec[tot],top),++tot;//遞歸處理子樹for (int i=h[Rt];i!=-1;i=a[i].nxt){if (!vis[a[i].y])solve(a[i].y,sz[a[i].y]);} }void dfs(int fa,int x){int u;rec[++rec[0]]=x;for (int i=h[x];i!=-1;i=a[i].nxt){u=a[i].y;if (u==fa||vis[u]) continue;dfs(x,u);} }void update(int i,int top){if (!top) return;int l=1,r=top-1,mid,ret=top,j,k;while (l<=r){mid=l+r>>1;j=st[mid];k=st[mid+1];if ((f[j]-f[k])<=p[i]*(dis[j]-dis[k])) ret=mid,r=mid-1;else l=mid+1;}ret=st[ret];f[i]=min(f[i],f[ret]+p[i]*(dis[i]-dis[ret])+q[i]); }轉載于:https://www.cnblogs.com/yoyoball/p/9250575.html
總結
以上是生活随笔為你收集整理的【bzoj3672】购票的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL高级知识(十五)——主从复制
- 下一篇: 【算法】有关点分治的一些理解与看法