6715. 【2020.06.11省选模拟】T1 极乐迪斯科

题目


正解

比赛时想到了一个自认为很高明的方法,但是有锅。
如果题目增加限制:监控的范围的包含关系呈树形结构,那我就能AC了。

这题可以 D P DP DP做,设 f i , j f_{i,j} fi,j表示 i i i节点为根,深度为 j j j以上的点都没有选(但是加上了它们的贡献)的最优答案。
转移相当于维护后缀 m a x max max和前缀区间加。
直接写是 O ( n 2 ) O(n^2) O(n2)的,可以用长链剖分或线段树合并来解决。于是时间复杂度就是 O ( n lg ⁡ n ) O(n \lg n) O(nlgn)

然而有个更加神奇的做法:
首先这题暴力是可以直接上网络流的。我们发现这个建图方式很优美,于是考虑优化(模拟)一下网络流。
从叶子结点往上做,对于一条流入树中的边,我们希望它尽量先将树中深度大的点流出去的边填满。因为对于更上面的入边中,树中深度小的点比深度大的点更有机会被选到。这样就可以减少浪费,使得流量尽量大。
于是这样设 f i , j f_{i,j} fi,j表示节点 i i i的子树中,深度为 j j j的位置的出边还有多少容量。
每次枚举到一条入边的时候,尽量自底向上填出边的容量。
回溯的时候,子树的 f i f_i fi需要合并。用map启发式合并,或者长链剖分就可以了。


代码

网络流做法:

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <map>
#define N 500010
#define ll long long
int n,m;
struct EDGE{
	int to;
	EDGE *las;
} e[N];
int ne;
EDGE *last[N];
int fa[N],dep[N];
int v[N];
struct Info{
	int d,c;
};
bool cmph(Info a,Info b){return a.d>b.d;}
vector<Info> h[N];
map<int,ll> f[N];
void merge(map<int,ll> &a,map<int,ll> &b){
	if (a.size()<b.size()){
		for (auto p=a.begin();p!=a.end();++p)
			b[p->first]+=p->second;
		a.clear();
		swap(a,b);
	}
	else{
		for (auto p=b.begin();p!=b.end();++p)
			a[p->first]+=p->second;
		b.clear();
	}
}
ll flow=0;
void dfs(int x){
	dep[x]=dep[fa[x]]+1;
	for (EDGE *ei=last[x];ei;ei=ei->las)
		dfs(ei->to);
	f[x][dep[x]]+=v[x];
	for (EDGE *ei=last[x];ei;ei=ei->las)
		merge(f[x],f[ei->to]);
	for (auto p=h[x].begin();p!=h[x].end() && !f[x].empty();++p){
		auto q=f[x].upper_bound(dep[x]+p->d);
		if (q==f[x].begin())
			continue;
		--q;
		for (auto tmp=q;q!=f[x].begin() && p->c;tmp=q,--q,f[x].erase(tmp)){
			if (p->c<q->second){
				flow+=p->c;
				q->second-=p->c;
				p->c=0;
				break;
			}
			flow+=q->second;
			p->c-=q->second;
			q->second=0;
		}
		if (p->c){
			if (p->c<q->second){
				flow+=p->c;
				q->second-=p->c;
				p->c=0;		
			}
			else{
				flow+=q->second;
				p->c-=q->second;
				q->second=0;
				f[x].erase(q);
			}
		}
	}
}
int main(){
	freopen("elysium.in","r",stdin);
	freopen("elysium.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=2;i<=n;++i){
		scanf("%d",&fa[i]);
		e[ne]={i,last[fa[i]]};
		last[fa[i]]=e+ne++;
	}
	ll sum=0;
	for (int i=1;i<=n;++i)
		scanf("%d",&v[i]),sum+=v[i];
	for (int i=1;i<=m;++i){
		int x,d,c;
		scanf("%d%d%d",&x,&d,&c);
		h[x].push_back({d,c});
	}
	for (int i=1;i<=n;++i)
		if (!h[i].empty())
			sort(h[i].begin(),h[i].end(),cmph);
	dfs(1);
	printf("%lld\n",sum-flow);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值