【题解】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;
}
后记
黑题++
完结撒花!