题解[Luogu P5008 [yLOI2018] 锦鲤抄]
题目
这首歌很好听。
就是因为喜欢歌才来做这道题的
一道很不错的贪心题,对思维的启发极大。
Sol
Sub1
暴搜即可。
是不是有模拟赛题解那味了
Sub2
这个\(subtask\)十分关键,直接让我们有通向正解的思路。
发现,如果图是一个有向无环图,那我们根据反向拓扑序来选点,只要不是一开始就没入度的点,一定是可以选的。这样就为我们提供了做法:除去本无入度的点以外,其他点随便选。
正是这一个有向无环图 ,让我们往缩点方向思考。
正解
手动画两个图,两个图都带一个环,一个环有入度,一个环没入度:
有入度的:
按照\(1->4->3->2\)的顺序可以取遍所有点。
没入度的:
无论怎样都有一个点取不到。
那我们的结论就有了:
缩点以后,有入度的环里的点随便选,其他的环舍掉最小的不选。
Talk is cheap,show you the code.
Code
#include<bits/stdc++.h>
#define M (2000010)
#define N (2000010)
using namespace std;
struct xbk{int ed,nx;}e[M];
int n,m,k,top,cnt,tot,ans,color,x[N],y[N],v[N];
int head[N],rd[N],dfn[N],low[N],stk[N],col[N];
bool used[N];
priority_queue<int>q;
vector<int>son[N];
inline int read(){
int w=0;
char ch=getchar();
while(ch>'9'||ch<'0') ch=getchar();
while(ch>='0'&&ch<='9'){
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w;
}
inline void add(int a,int b){
e[++cnt].ed=b;
e[cnt].nx=head[a];
head[a]=cnt;
}
inline void Tarjan(int st){
dfn[st]=low[st]=++tot;
used[st]=1,stk[++top]=st;
for(int i=head[st];i;i=e[i].nx){
int ed=e[i].ed;
if(!dfn[ed]){
Tarjan(ed);
low[st]=min(low[st],low[ed]);
}
else if(used[ed]) low[st]=min(low[st],dfn[ed]);
}
if(dfn[st]==low[st]){
color++;
while(stk[top+1]!=st){
son[color].push_back(stk[top]);
col[stk[top]]=color;
used[stk[top--]]=0;
}
}
return;
}
int main(){
n=read(),m=read(),k=read();
for(int i=1;i<=n;i++) v[i]=read();
for(int i=1;i<=m;i++){
x[i]=read(),y[i]=read();
add(x[i],y[i]);
}
for(int i=1;i<=n;i++)
if(!dfn[i]) Tarjan(i);
for(int i=1;i<=m;i++)
if(col[x[i]]!=col[y[i]]) rd[col[y[i]]]++;
for(int i=1;i<=color;i++){
if(rd[i])
for(int j=0;j<(int)son[i].size();j++) q.push(v[son[i][j]]);
else{
int mn=v[son[i][0]];
for(int j=1;j<(int)son[i].size();j++){
if(v[son[i][j]]>=mn) q.push(v[son[i][j]]);
else q.push(mn),mn=v[son[i][j]];
}
}
}
for(int i=1;i<=k;i++){
ans+=q.top(),q.pop();
if(q.empty()) break;
}
printf("%d\n",ans);
return 0;
}