【题解】P7563 最悪の記者 4 (Worst Reporter 4)

题面

题目传送门

前言

思维难度:紫-

代码难度:黑

正文

对于每一种约束,都可以将其抽象为一条有向边

注意到有 \(n\) 个约束,并且对于图上的每一个结点,都有且仅有一条出边

所以整张图形如内向基环树

建模建完了,该上算法了

考虑拆分成若干个子问题回答

Subtask 1

所谓的“暴力”分

因为 \(a_1=1\),所以基环树退化成朴素的树形结构,考虑树形 DP 处理

并且支持 \(O(n^2)\),可以立刻手搓一份树形 DP 的状态设计及转移方程

我们记 我们设 \(f_{u,i}\) 表示修改 \(h_u\)\(i\) 后,满足 \(u\) 子树中的不等关系所需的最小花费

当然,第二维需要先做离散化处理

转移方程形如

\begin{equation}
f_{u,i} = c_u \times (i \neq h_u) + \sum_{v \in son_u} \min_{j=i}^{mx} \lbrace f_{v,j} \rbrace
\nonumber
\end{equation}

其中 \(mx\) 表示离散化后的赋值上限

至此,我们可以拿到 \(14pts\) 的好成绩

Subtask 2

DP 是很优的,整套流程设计没有任何问题,就是卡时间复杂度

对于 \(n \le 2 \times 10^{5}\) 的数据,\(O(n)\) 实现有些不太现实,盲猜 \(O(\log n)\) 实现

什么东东可以优化树形 DP,并将某一维的计算压入 \(O(\log n)\) 的时间复杂度?

线段树合并!

当然,先说一个小 trick

注意到上面式子的第一项会大量的给 \(f_{u,i}\) 赋值,在利用线段树合并优化的时候,会拖慢整套程序的运行效率,所以考虑将原式变形,改写成如下形式:

\begin{equation}
f_{u,i} = (\sum_{i=1}^{n} c_i) - c_u \times (i=h_u) + \sum_{v \in son_u} \min_{j=i}^{mx} \lbrace f_{v,j} \rbrace
\nonumber
\end{equation}

新式子的第一项是个定值,我们顺利地把一个区间修改转化成了单点修改,大大减少了我们的工作量

找区间最小值时的区间是 \([i,mx]\),然后再将每个子树中的最小值加起来

合并树 \(x,y\) 时,记录 \(pm,qm\) 分别为两棵树的最小值

分讨一下加上最小值后的大小情况即可

\(i\) 虽然不确定,但是右端点 \(mx\) 是固定的。因此我们在合并时不可以用平常的方式合并,而是先合并右子树,再合并左子树

单点修改和合并时遇到一边空树的区间加标记即可

至此,我们已经可以得到 \(79pts\) 的高分力!

Subtask 3

冬天来了,春天还会远吗?

显然子任务二的性质完全可以套用在内向基环森林上

基环树一般怎么处理,缩点,破环为链……

总而言之,就是将环特殊考虑

对于线段树合并来说,我们完全可以先用拓扑排序把环拎出来

随后给剩余的树形结构做上述的线段树合并优化树形 DP

这棵树的答案合并到指向环的结点上

对于环内部,暴力枚举即可

综合来说,实现难度较大

但是思维难度并不高,是一道线段树合并优化 DP 的练手好题!

代码

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<utility>
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define int long long
using namespace std;
const int maxn=2e5+10;
int n,H[maxn],c[maxn],tmp[maxn],len,h[maxn];
int head[maxn],tot;
struct Edge{
	int to,nxt;
}e[maxn];
inline void add(int u,int v){
	e[++tot].to=v;
	e[tot].nxt=head[u];
	head[u]=tot;
	return;
}
int cnt,rt[maxn],pm,qm,wt[maxn],ans;
struct Segment_tree{
	struct node{
		int l,r,mn,tag;
	}tr[maxn<<6];
	void addtag(int u,int val){
		if(u==0){
			return;
		}
		tr[u].mn+=val;
		tr[u].tag+=val;
	}
	void pushup(int u){
		tr[u].mn=min(tr[tr[u].l].mn,tr[tr[u].r].mn);
		return;
	}
	void pushdown(int u){
		if(tr[u].tag==0){
			return;
		}
		addtag(tr[u].l,tr[u].tag); 
		addtag(tr[u].r,tr[u].tag);
		tr[u].tag=0;
	}
	int insert(int u,int l,int r,int pos,int k){
		if(u==0){
			u=++cnt;
			tr[u].mn=0;
			tr[u].tag=0;
		}
		if(l==r){
			tr[u].mn=k;
			return u;
		}
		pushdown(u);
		int mid=l+r>>1;
		if(pos<=mid){
			tr[u].l=insert(tr[u].l,l,mid,pos,k);
		}else{
			tr[u].r=insert(tr[u].r,mid+1,r,pos,k);
		}
		pushup(u);
		return u;
	}
	int modify(int u,int l,int r,int pos,int k){
		if(u==0){
			u=++cnt;
			tr[u].mn=0;
			tr[u].tag=0;
		}
		if(l==r){
			tr[u].mn+=k;
			return u;
		}
		pushdown(u);
		int mid=l+r>>1;
		if(pos<=mid){
			tr[u].l=insert(tr[u].l,l,mid,pos,k);
		}else{
			tr[u].r=insert(tr[u].r,mid+1,r,pos,k);
		}
		pushup(u);
		return u;
	}
	int query(int u,int ql,int qr,int l,int r){
		if(u==0){
			return 0;
		}
		if(ql<=l&&qr>=r){
			return tr[u].mn;
		}
		pushdown(u);
		int mid=l+r>>1,res=0;
		if(ql<=mid){
			res=min(res,query(tr[u].l,ql,qr,l,mid));
		}
		if(qr>mid){
			res=min(res,query(tr[u].r,ql,qr,mid+1,r));
		}
		return res;
	}
	int merge(int x,int y,int l,int r){
		if(x==0&&y==0){
			return 0;
		}
		if(x==0){
			qm=min(qm,tr[y].mn);
			addtag(y,pm);
			return y;
		}
		if(y==0){
			pm=min(pm,tr[x].mn);
			addtag(x,qm);
			return x;
		}
		if(l==r){
			pm=min(pm,tr[x].mn);
			qm=min(qm,tr[y].mn);
			if(tr[x].mn+qm<=tr[y].mn+pm){
				addtag(x,qm);
				return x;
			}
			addtag(y,pm);
			return y;
		}
		pushdown(x);
		pushdown(y);
		int mid=l+r>>1;
		tr[x].r=merge(tr[x].r,tr[y].r,mid+1,r);
		tr[x].l=merge(tr[x].l,tr[y].l,l,mid);
		pushup(x);
		return x;
	}
}Tr;
queue<int>q;
int in[maxn],tim,dfn[maxn],f[maxn];
vector<pii> R[maxn];
inline void dfs(int u){
	rt[u]=Tr.insert(0,1,len,len,0);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		dfs(v);
		pm=0;
		qm=0;
		rt[u]=Tr.merge(rt[u],rt[v],1,len);
	}
	int cur=lower_bound(h+1,h+len+1,H[u])-h;
	int now=Tr.query(rt[u],cur,len,1,len);
	rt[u]=Tr.insert(rt[u],1,len,cur,now-c[u]);
	return;
}
inline void mark(int x){
	dfn[x]=tim;
	R[tim].push_back(mkp(lower_bound(h+1,h+len+1,H[x])-h,-c[x]));
	if(!dfn[f[x]]){
		mark(f[x]);
	}
	return;
}
inline void topo(){
	for(int i=1;i<=n;i++){
		if(!in[i]){
			q.push(i);
		}
	}
	while(!q.empty()){
		int x=q.front();
		q.pop();
		in[f[x]]--;
		if(!in[f[x]]){
			q.push(f[x]);
		}
	}
	for(int i=1;i<=n;i++){
		if(!in[i]){
			continue;
		}
		if(!dfn[i]){
			tim++;
			mark(i);
		}
	}
	return;
}
inline void cal(){
	for(int i=1;i<=n;i++){
		if(!in[i]&&in[f[i]]){
			dfs(i);
			pm=0;
			qm=0;
			wt[dfn[f[i]]]=Tr.merge(wt[dfn[f[i]]],rt[i],1,len);
		}
	}
	for(int i=1;i<=tim;i++){
		sort(R[i].begin(),R[i].end());
		int mn=Tr.tr[wt[i]].mn;
		int sum=R[i][0].se,pre=R[i][0].fi;
		for(int j=1;j<R[i].size();j++){
			if(R[i][j].fi==pre){
				sum+=R[i][j].se;
			}else{
				mn=min(mn,Tr.query(wt[i],pre,len,1,len)+sum);
				sum=R[i][j].se;
				pre=R[i][j].fi;
			}
		}
		ans+=min(mn,Tr.query(wt[i],pre,len,1,len)+sum);
	}
	return;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>f[i]>>H[i]>>c[i];
		add(f[i],i);
		in[f[i]]++;
		tmp[i]=H[i];
		ans+=c[i];
	}
	sort(tmp+1,tmp+n+1);
	for(int i=1;i<=n;i++){
		if(tmp[i]!=tmp[i-1]){
			h[++len]=tmp[i];
		}
	}
	topo();
	cal();
	cout<<ans<<endl;
	return 0;
}

后记

黑题++

完结撒花!

posted @ 2025-01-14 09:16  sunxuhetai  阅读(20)  评论(0)    收藏  举报