「JOISC 2019 Day3」指定城市
题意
\(~~~~\) 给一棵 \(n\) 个点的树,每条边双向分别有一个权值。现给出 \(q\) 个询问,每个询问可以指定 \(e_i\) 个点为特殊点,对每个特殊点所有非该特殊点到该点的路径相同方向上的边免费,求最小代价。
\(~~~~\) \(1\leq n\leq 2\times 10^5,q\leq n\)。
题解
\(~~~~\) 似乎不难,但我打了3k,/kk
\(~~~~\) 首先由子任务外加手玩不难发现 \(e=1\) 的时候应该做法不大相同,可以发现此时如果把选择的城市作为根,那么最终的答案就是所有向下的边的权值之和,直接换根DP即可,具体实现可见代码。
\(~~~~\) 再看 \(e=2\) ,此时若在某个点 \(u\) 的子树内选两个不属于同一儿子的结点 \(v_1,v_2\),则节省的代价一定是 \(u\) 作为根时节省的代价加上 \(u\) 到这两个点节省的代价,在上面换根DP二次扫描时取最大值即可。
\(~~~~\) 否则当 \(e>2\) 时,感性认识应该是较之前 \(e-1\) 时选择的点不会改变,只会增加。故每次贪心选取节省的代价最大的那个即可,然后删去它造成负贡献的那些边。不难想到将原树以 \(e=2\) 时其中一个选择的点为根跑一遍dfs序,这样就可以用线段树边贡献。由于每条边最多删一次,所以这一部分复杂度是 \(\mathcal{O(n \log n)}\).
代码
查看代码
#include <cstdio>
#include <vector>
#include <algorithm>
#define ll long long
#define PII pair<ll,ll>
#define mp(a,b) make_pair(a,b)
using namespace std;
vector < pair<int,PII> > G[200005];
int root,n,q,fa[200005],pos1,pos2;
ll tot,dp[200005],Ans[200005],dis[200005],pos[200005];
void dfs1(int u,int Fa)
{
for(int i=0;i<G[u].size();i++)
{
int v=G[u][i].first;
if(v==Fa) continue;
dis[v]=dis[u]+G[u][i].second.first;
dfs1(v,u);dp[u]+=dp[v]+G[u][i].second.second;
}
}
void dfs2(int u,int Fa)
{
pos[u]=u;Ans[1]=max(Ans[1],dp[u]);root=(Ans[1]==dp[u]?root:u);
for(int i=0;i<G[u].size();i++)
{
int v=G[u][i].first;
if(v==Fa) continue;
dp[u]-=dp[v]+G[u][i].second.second;
dp[v]+=dp[u]+G[u][i].second.first;
dfs2(v,u);
dp[v]-=dp[u]+G[u][i].second.first;
dp[u]+=dp[v]+G[u][i].second.second;
if(dp[u]+dis[pos[u]]+dis[pos[v]]-dis[u]*2>Ans[2])
{
Ans[2]=dp[u]+dis[pos[u]]+dis[pos[v]]-dis[u]*2;
pos1=pos[u]; pos2=pos[v];
}
if(dis[pos[u]]<dis[pos[v]]) pos[u]=pos[v];
}
}
void Solve1()
{
dfs1(1,0);
dfs2(1,0);
}
bool On[200005];
bool changed[200005];
int dfn[200005],Times,L[200005],R[200005],Val[200005];
struct SegmentTree{
#define ls p<<1
#define rs p<<1|1
#define lson p<<1,l,mid
#define rson p<<1|1,mid+1,r
ll tr[800005],tag[800005],AnsPos[800005];
void pushUp(int p)
{
tr[p]=max(tr[ls],tr[rs]);
if(tr[p]==tr[ls]) AnsPos[p]=AnsPos[ls];
if(tr[p]==tr[rs]) AnsPos[p]=AnsPos[rs];
}
void pushDown(int p)
{
if(tag[p])
{
tr[ls]+=tag[p]; tr[rs]+=tag[p];
tag[ls]+=tag[p]; tag[rs]+=tag[p];
tag[p]=0;
return;
}
}
void Build(int p,int l,int r)
{
if(l==r)
{
tr[p]=0;
AnsPos[p]=l;
return;
}
int mid=(l+r)>>1;
Build(lson); Build(rson);
pushUp(p);
}
void Modify(int p,int l,int r,int lx,int rx,int val)
{
if(lx<=l&&r<=rx)
{
tr[p]+=val;
tag[p]+=val;
return;
}
int mid=(l+r)>>1;pushDown(p);
if(lx<=mid) Modify(lson,lx,rx,val);
if(mid<rx) Modify(rson,lx,rx,val);
pushUp(p);
}
inline PII Query(){return mp(AnsPos[1],tr[1]);}
}Seg;
bool Tag(int u,int Fa)
{
if(u==pos2) {On[u]=true;return true;}
for(int i=0;i<G[u].size();i++)
{
int v=G[u][i].first;
if(v==Fa) continue;
if(Tag(v,u)) {On[u]=true;return true;}
}
return false;
}
int To[200005];
void dfs4(int u,int Fa)
{
ll Sum=0;
dfn[u]=++Times;fa[u]=Fa;
L[u]=Times;To[Times]=u;
for(int i=0;i<G[u].size();i++)
{
int v=G[u][i].first;
if(v==Fa) continue;
dfs4(v,u);Val[v]=G[u][i].second.first;
if(!On[v]) Seg.Modify(1,1,n,L[v],R[v],G[u][i].second.first),Ans[2]+=G[u][i].second.first;
}
R[u]=Times;
}
void Solve2(){Tag(pos1,0);dfs4(pos1,0);}
void Solve()
{
for(int i=3;i<=n;i++)
{
PII P=Seg.Query();
Ans[i]=Ans[i-1]-P.second;
int x=To[P.first];
while(!On[x]&&!changed[x])
{
Seg.Modify(1,1,n,L[x],R[x],-Val[x]);
changed[x]=true;x=fa[x];
}
}
}
int main() {
scanf("%d",&n);
for(int i=1,u,v,a,b;i<n;i++)
{
scanf("%d %d %d %d",&u,&v,&a,&b);
G[u].push_back(mp(v,mp(a,b)));
G[v].push_back(mp(u,mp(b,a)));tot+=a+b;
}
Seg.Build(1,1,n);
Solve1();Ans[1]=tot-Ans[1];Ans[2]=0;
Solve2();
Solve();
scanf("%d",&q);
while(q--)
{
int x;scanf("%d",&x);
printf("%lld\n",Ans[x]);
}
return 0;
}