• 「JOISC 2019 Day3」指定城市 Solution


    「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;
    }
    
  • 相关阅读:
    分支(选择)语句练习——7月22日
    语句:分支语句、switch case ——7月22日
    C#语言基础——7月21日
    进制转换——7月20日
    运行Tomcat报错 解决方法
    Mybatis面试题
    java面试题02
    当你没有能力去改变别人命运的时候 就不要随意去伸出援手......
    快速学习MD5的方法
    java面试题01
  • 原文地址:https://www.cnblogs.com/Azazel/p/15854304.html
Copyright © 2020-2023  润新知