$Self~Problem~C~:~Samsara$
題目背景:
在這個\(Canman\)界的人都知道,世界上最偉大的修道者 —— \(Felling\),曾經結束了\(Canman\)的無垠盞之災,守護了\(Canman\)的和平。在無垠盞之災的最後,近神的\(Felling\)正在和墮入魔道的修道者,無垠災的始作俑者\(Neyii\)進行最後的對峙。掌握著輪回之力的他,可以逆向流逝輪回,造成外爆內斂的奇點爆炸,比一般的爆炸要強悍數倍。
題目描述:
已知當前\(Neyii\)張開魔疆,把\(Felling\)包圍了進去。\(Felling\)在魔疆中實力受到大幅的限制,甚至連時間和空間之力都被禁止了。但是魔疆是一把雙面刃,包圍了\(Felling\)
已知當前\(Neyii\)的經絡圖由成百上千的穴位和經脈鏈接而成,經脈負責聯通各個穴位。習武之人皆知,經脈內運氣的流動是單向的,否則將會導致運氣沖突,經脈爆裂的嚴重後果。如果引爆一個穴位,那麽以這個穴位為起點的經脈都會報廢。而如果一個穴位沒有任何經脈流入供應,那麽這個穴位就會進入閉脈狀態,使\(Felling\)無法對其催動奇點爆炸。而\(Felling\)所引爆的穴位所蘊含的能量都將返還\(Felling\)
輸入格式:
第一行,三個正整數\(N, M, K\)表示穴位數和經脈數和最多的爆炸次數。
第二行,\(N\)個整數\(Data[i]\),分別表示第\(i\)號穴位的能量。
下面\(M\)行,每行三個正整數,\(X, Y\)表示從\(X\)到\(Y\)有一條單向流動的經脈。
輸出格式:
一行,一個整數,表示最多能獲得的能量數。
輸入樣例 :
7 7 3 10 2 8 4 9 5 7 1 2 1 3 1 4 2 5 3 6 3 7 4 7
輸出樣例:
24
數據大小:
對於\(10\)%的數據,\(1 \leq N \leq 10, 1 \leq M \leq 20\)。
對於另外\(30\)%的數據,經絡圖無環。
對於另外\(10\)%的數據,有且只有一個點的入度為\(0\)。
對於\(100\)%的數據:\(1 \leq N \leq 10000\), \(1 \leq M \leq 500003\), \(1 \leq K \leq 100003\)
所有邊權\(\leq 1000\)
首先簡化一下題面:
你現在有一張\(N\)個點\(M\)條邊的一般有向圖,你可以造成至多\(K\)次點上的爆炸,每次爆炸都可以獲得這個點的點權。爆炸之後所有以這個點為起點的出邊都會報廢。如果一個點沒有入邊,那麽不可以實施爆炸。請求最大化點權和。
首先我們考慮一張有向無環圖。
你可以發現,假如你現在想要引爆\(Now_1\)和\(Now_2\),如果\(Now_2\)是從\(Now_1\)來的,那麽我們一定是先引爆\(Now_2\),然後引爆\(Now_1\)獲得的價值更大。
比如上圖的\(3和\)\(6\)節點,如果我想要引爆這兩個節點,那麽我一定先引爆\(6\),因為如果我先引爆\(3\),那麽會導致\(6\)不能被引爆。
而如果\(Now_1\)和\(Now_2\)是類似於一種“並列關系”的話,就不用彼此考慮。就比如\(3\)和\(4\)。
所以我們可以發現這個\(DAG\)上除了入度為\(0\)的那些點以外,其它的點我們都可以選。因為總有一種合法順序可以讓我們選完所有的點。因此\(DAG\)上我們只要刪去所有入度為\(0\)的點,然後排序\(For\)到\(K\)即可。\(30\)分到手。
那麽我們來考慮下圖這種情況。
我們可以發現,如果我們從\(4\)開始引爆,那麽\(4\)後引爆\(3\),\(3\)後引爆\(2\),\(2\)之後只剩了一個\(1\)沒有引爆。當然換一個起點也是一樣的。所以說,我們只需要舍棄一個點,那麽其它的點都是可以被選中的。那麽我們當然刪去最小的點。
那麽轉而來看一般有向圖。
我們發現這個情況下,原來的\(\{1, 2, 3, 4\}\)環都可以選了,因為多了一個入邊\(\{5 - > 1\}\),那麽我們發現\(\{1, 2, 3, 4\}\)就可以看做是一個點。
那麽得出算法:對於每一個強連通分量:
如果該強聯通分量有入度,那麽這個強聯通分量裏面的所有的點都可以選擇。
如果該強聯通分量沒有入度,那麽去掉一個點之後,其余的所有的點都可以選擇。
所以算法流程如下:
- 縮點
- 對於每一條邊,如果從\(SCC_1\)跑到了\(SCC_2\),那麽認為\(SCC_2\)有入度,標記\(Flag[SCC_2] = 1\)
- \(for\)所有的\(SCC_i\),若\(Flag[SCC_i] == 1\)那麽將該\(SCC\)裏面的所有點加入數組。否則去掉裏面點權最小的點,然後將其余的點加入數組。
- 對於上面說的那個數組,用\(nth\_element\)排序前\(K\)大,然後計算點權和。
算法實際上是一個比較奇妙的貪心。
標程:
#include <algorithm>
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <cmath>
#include <map>
#define For1(i, A, B) for(register int i = (A), i##_end_ = (B); i <= i##_end_; ++ i)
#define For2(i, A, B) for(register int i = (A), i##_end_ = (B); i >= i##_end_; -- i)
#define MEM(Now, K) memset(Now, K, sizeof(Now))
#define CPY(Now, K) memcpy(Now, K, sizeof(Now))
#define Debug(Now) (cerr << Now << endl)
#define Min(A, B) (A < B ? A : B)
#define Max(A, B) (A < B ? B : A)
#define SCPY(A, B) strcpy(A, B)
#define Inf 0x7fffffff
#define RE register
#define IL inline
#define MAXN 100010
#define MAXM 500010
#define _X first
#define _Y second
using namespace std ;
typedef unsigned long long ULL ;
typedef pair<long long, int> PLI;
typedef pair<int, int> PII;
typedef unsigned int UINT;
typedef long double LDB;
typedef long long LL ;
typedef double DB ;
IL int Read(){
LL X = 0, F = 1 ; char ch = getchar() ;
while(ch < '0' || ch > '9'){ if(ch == '-') F = - 1 ; ch = getchar() ; }
while(ch <= '9' && ch >= '0') X = (X << 1) + (X << 3) + (ch ^ 48), ch = getchar() ;
return X * F ;
}
IL double DBRead(){
double X = 0, Y = 1.0 ; LL W = 0 ; char ch = 0 ;
while(! isdigit(ch)) { W |= ch == '-' ; ch = getchar() ; }
while(isdigit(ch)) X = X * 10 + (ch ^ 48), ch = getchar() ;
ch = getchar();
while(isdigit(ch)) X += (Y /= 10) * (ch ^ 48), ch = getchar() ;
return W ? - X : X ;
}
IL void Print(/*LL*/ LL X){
if(X < 0) putchar('-'), X = - X ;
if(X > 9) Print(X / 10) ; putchar(X % 10 + '0') ;
//cout << endl ;
//cout << " " ;
}
LL N, M, K, Data[MAXN], Ans ;
struct Node{
LL From, To, Next ;
}Edge[MAXM] ;
LL Head[MAXN], Total ;
void Add(LL F, LL T){
Total ++ ;
Edge[Total].From = F ;
Edge[Total].To = T ;
Edge[Total].Next = Head[F] ;
Head[F] = Total ;
}
LL Dfn[MAXN], Low[MAXN], Deep, Cnt, Flag[MAXN] ;
LL Stack[MAXN], Insta[MAXN], Top ;
LL Belong[MAXN], Est[MAXN], MIN[MAXN] ;
LL Finally[MAXN], All ;
void Tarjan(LL Now){
Dfn[Now] = Low[Now] = ++ Deep ;
Stack[++ Top] = Now ; Insta[Now] = 1 ;
for(LL i = Head[Now]; i; i = Edge[i].Next){
if(! Dfn[Edge[i].To]){
Tarjan(Edge[i].To) ;
Low[Now] = min(Low[Now], Low[Edge[i].To]) ;
} else if(Insta[Edge[i].To])
Low[Now] = min(Low[Now], Dfn[Edge[i].To]) ;
}
if(Low[Now] == Dfn[Now]){
Cnt ++ ; LL Pass ;
do{
Pass = Stack[Top --] ;
if(Est[Cnt] > Data[Pass])
MIN[Cnt] = Pass, Est[Cnt] = Data[Pass] ;
Belong[Pass] = Cnt ;
Insta[Pass] = 0 ;
}while(Now != Pass) ;
}
}
bool CMP(LL X, LL Y){
return X > Y ;
}
int main() {
freopen("samsara.in", "r", stdin) ;
freopen("samsara.out", "w", stdout) ;
N = Read(), M = Read(), K = Read() ;
memset(Est, 127, sizeof(Est)) ;
for(LL i = 1; i <= N; i ++)
Data[i] = Read() ;
for(LL i = 1; i <= M; i ++){
LL F = Read(), T = Read() ;
Add(F, T) ;
}
for(LL i = 1; i <= N; i ++)
if(! Dfn[i]) Tarjan(i) ;
for(LL i = 1; i <= M; i ++){
if(Belong[Edge[i].From] != Belong[Edge[i].To])
Flag[Belong[Edge[i].To]] = 1 ;
}
for(LL i = 1; i <= Cnt; i ++)
if(! Flag[i])
Data[MIN[i]] = - 1 ;
for(LL i = 1; i <= N; i ++){
if(Data[i] == -1) continue ;
Finally[++ All] = - Data[i] ;
}
nth_element(Finally + 1, Finally + K + 1, Finally + All + 1) ;
for(LL i = 1; i <= K; i ++)
Ans += - Finally[i] ;
printf("%lld", Ans) ;
fclose(stdin) ; fclose(stdout) ;
return 0 ;
}
$Self~Problem~C~:~Samsara$