【洛谷P5008 逛庭院】tarjan縮點+貪心
既然沒有題解,那麼我就來提供給一份。
--
首先我們看到資料範圍。媽耶!資料這麼大,一開始還想用個DP來做,但是看著就不行,那麼根據這個資料範圍,我們大致可以猜到這道題的演算法是一個貪心,那麼我們怎麼貪呢?
我們首先還是先畫一個圖:
樣例解釋一下:
我們取的點是\(3\),\(5\),\(7\)。
看到題目,因為\(1\)號節點的入度為0,那麼就一定不能選擇\(1\)號節點,那麼接下來可以供我們選擇的最大的權值的點也就只有\(3\),\(5\),\(7\)號節點,那麼我們就來一個貪心策略:對每一個節點的權值進行排序,然後將所有不能取的節點全部不算,剩下的就都取最大的那幾個。
以下是\(30\)
# include <bits/stdc++.h> # define Ri register int # define for1(i,a,b) for(Ri i(a);i<=b;++i) # define for2(i,a,b) for(Ri i(a);i>=b;--i) using namespace std; inline int read () { int w = 0,x = 0; char ch = 0; while (!isdigit(ch)) { w |= ch =='-'; ch = getchar();} while (isdigit(ch)) { x = (x<<1) + (x<<3) + (ch ^ 48); ch = getchar(); } return w ? -x : x; } const int Maxm = 2000004; const int Maxn = 5000004; int Nedge, n, m, k; int head[Maxm], ind[Maxn]; struct node{ int v ,id ,ind ; }a[5000004]; bool cmp (node a,node b) { return a.v > b.v; } int main() { n = read(),m = read(),k = read(); Nedge = 1; for1(i ,1 ,n ) a[i].v = read(),a[i].id = i; for1(i ,1 ,m ) { int u = read(),v = read(); a[v].ind ++; } sort (a + 1 , a + 1 + n , cmp); int cnt = 0 , ans = 0; for1(i ,1 ,n) { if (a[i].ind == 0) continue; else { ans += a[i].v; cnt ++; if (cnt == k) break; } } printf ("%d\n", ans); return 0; }
但是這個貪心一定是錯的。
為什麼
我們來想一下,如果可以去掉的節點,是某一個接下來可以取的節點的唯一一個入邊來源,那麼這個一定會影響後面的答案,這個點也就不取了,所以我們就不能這樣做。
那麼我們應該怎麼做呢?
這個時候我們就需要 膽大心細地思考題目了。我是好好聽了出征大會的
其實也就只需要在這個貪心的基礎上,加上一個契機,這個契機就是讓當前這個刪去的點,可以不對後面的點產生影響。
正解策略是:我們首先縮點,然後找到入度為0的環,刪去這個環中權值最小的點,然後從小到大排序,取前k大的點。
我們先給一個縮點的模板吧!
inline void tarjan(int u) { dfn[u] = low[u] = ++ dep; vis[u] = 1; S[top++] = u; for (int i = head[u]; i != -1; i = edge[i].next ) { int v = edge[i].to; if (!dfn[v]) { tarjan(v); low[u] = Min(low[u] ,low[v]); } else if (vis[v]) low[u] = Min(low[u] ,low[v]); } int j; if (dfn[u] == low[u]) { sum ++; do { j = S[ -- top]; belong[j] = sum ; vis [j] = 0; }while (u != j) ; } }
解釋
那麼我們就需要一個手段,使得這個這個點成為一個刪去和不刪去,不會影響答案得到東西:這個玩意的名字叫做縮點。
為什麼我們會想到縮點,我們得從DAG中的環開始說起。
早在。。因為在有向圖中,每一個點都是可以互相到達的,那麼所以這個有向圖中的每一個點都是有入度的,沒有人反駁吧!,所以這個裡面的點都是可以隨意取的,但是要注意attention:當你這這個環是\(0\)的入度時,那麼你就不能隨意取掉最後一個點了,因為你這個最後一個點可能就沒有入度了,那麼我們為了保證所有的點都可以取到,我們就將這個環內的權值最小的點刪去,那麼這樣就可以保證這個環斷開後,這個點集中的點就可以隨便取了。
那麼還有一個問題,也就是如果是一個節點的縮點?
其實也是一個道理,我就不解釋了,也就是把這個點刪掉,反正這個點完全沒有用。
以下是AC程式碼(新的碼風本人感覺還是挺好看的QAQ)
# include <bits/stdc++.h>
# define Ri register int
# define for1(i,a,b) for(Ri i(a);i<=b;++i)
# define for2(i,a,b) for(Ri i(a);i>=b;--i)
# define ms(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long LL;
const int M = 2000005;
struct Edge{
int to ,next;
}edge[M];
int dfn[M], vis[M], low[M], S[M], head[M] ,belong[M] ,ind[M];
int dep, top, sum , n ,m ,k ,Nedge;
struct node{
int v ,id ;
}a[M];
inline int read() //快讀
{
int w = 0,x = 0;
char ch = 0;
while (!isdigit(ch))
{
w |= ch == '-';
ch = getchar();
}
while (isdigit(ch))
{
x = (x<<1) + (x<<3) + (ch^48);
ch = getchar();
}
return w ? -x : x ;
}
inline int Min(int n,int m) //三目取min
{
return n < m ? n : m;
}
inline void Add_Edge(int u ,int v) //鏈式前向星
{
edge[Nedge] = (Edge) {v ,head[u]} ;
head[u] = Nedge++;
}
inline void tarjan(int u) //tarjan縮點模板
{
dfn[u] = low[u] = ++ dep;
vis[u] = 1;
S[top++] = u;
for (int i = head[u]; i != -1; i = edge[i].next )
{
int v = edge[i].to;
if (!dfn[v])
{
tarjan(v);
low[u] = Min(low[u] ,low[v]);
}
else if (vis[v]) low[u] = Min(low[u] ,low[v]);
}
int j;
if (dfn[u] == low[u])
{
sum ++;
do
{
j = S[ -- top];
belong[j] = sum ;
vis [j] = 0;
}while (u != j) ;
}
}
inline bool cmp1(node a,node b) //從小到大排序
{
return a.v < b.v;
}
inline bool cmp2(node a,node b) //從大到小排序
{
return a.v > b.v;
}
int main()
{
ms(head ,-1);
ms(dfn ,0);
ms(vis ,0);
ms(belong ,0);
sum = 0,dep = 0,top = 0;
n = read(),m = read(),k = read();
for1(i ,1 ,n) a[i].v = read(),a[i].id = i;
for1(i ,1 ,m)
{
int u = read(),v = read();
Add_Edge(u , v);
}
for1(i ,1 ,n)
{
if (!dfn[i]) tarjan(i); // 縮一波點
}
for1(i ,1 ,n)
{
for (int j = head[i]; j != -1; j = edge[j].next)
{
int v = edge[j].to;
if ( belong[i] != belong[v] ) ind[belong[v]] ++;
}
}//統計當前縮完點後的每個點的入度
sort(a + 1, a + 1 + n ,cmp1);
for1(i ,1 ,n)
{
if (ind[belong[a[i].id]] == 0)
{
a[i].v = 0;
ind[belong[a[i].id]] = 1;
}
}//刪去一個聯通塊中權值最小的點
sort(a + 1 , a + 1 + n ,cmp2);
LL ans = 0, cnt = 0;
for1(i ,1 ,n) //計算我們的答案
{
ans += a[i].v;
cnt ++ ;
if (cnt == k) break;
}
printf ("%lld\n", ans);
return 0;
}