P1032 金缕曲 官方题解
Gioush OJ · P1032 金缕曲
金缕曲
【题意简述】
给定一张有向图和起点 。经过一条初始权值为 的边时,第 次经过可获得 中仍为正的部分。可以重复经过边,求最大收益。
【Hint】
在同一个强连通分量内部,只要一条边能被经过,就可以通过绕行把它的正收益全部取完。不同强连通分量之间则只能沿 DAG 前进。
【提示】
先 Tarjan 缩点。分量内部边贡献全部加入分量权值,分量外边保留一次边权作为 DAG 转移贡献。
【解法】
对原图求强连通分量。对于一条边 :
- 若 在同一强连通分量中,则这条边的全部正收益都可以取得,加入该分量的权值;
- 若不在同一分量中,则它成为缩点 DAG 上的一条边,经过时只能获得一次 。
一条边的内部总收益可以直接求,也可以二分最大的 使
缩点后在 DAG 上做最长路。设 表示到达分量 时最多已经取得的收益,则沿边 转移:
答案为所有可达分量的最大 。
【分量内部边的贡献】
若一条边初始收益为 ,第 次经过的正收益为 。在同一 SCC 内,进入后可以绕行并反复经过这条边,所以它的全部正收益都能取得。令最大的正收益次数为 ,满足 ,将这 项求和加入分量权值即可。
不同 SCC 之间不能绕回,缩点 DAG 上每条边至多使用一次,故只保留一次 作为最长路转移。这样分量内“取尽”与分量间“至多一次”正好对应两类边。
【复杂度】
Tarjan 与 DAG 转移均为线性。若每条边的 用二分求,复杂度为 ;用公式求 则为 。
【参考代码】
/*Author:EhundateghDate:2026/7/23Name:goldknot.cppYou steal,I kill.*/#include <cmath>#include <stack>#include <queue>#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 1000010using namespace std; int Head[2][MAXN],Total[2]={0,0},Dfn[MAXN],Low[MAXN],cnd=0,SCC[MAXN],cns=0,Deg[MAXN];int n,m,In1,In2,In3,s,c,T;long long Sum[MAXN],Dp[MAXN],Ans=0;stack <int> S;queue <int> Q; struct edge { int St,Ed,Next,Val;}Edge[2][MAXN<<1]; void Edge_Add(int St,int Ed,int Val,int Type) { Edge[Type][++Total[Type]]={St,Ed,Head[Type][St],Val}; Head[Type][St]=Total[Type];} void Tarjan(int Now) { Dfn[Now]=Low[Now]=++cnd; S.push(Now); for (int i=Head[0][Now];i;i=Edge[0][i].Next) { int To=Edge[0][i].Ed; if (!Dfn[To]) { Tarjan(To); Low[Now]=min(Low[Now],Low[To]); } else if (!SCC[To]) { Low[Now]=min(Low[Now],Dfn[To]); } } if (Dfn[Now]==Low[Now]) { cns++; while (S.top()!=Now) { SCC[S.top()]=cns;S.pop(); } SCC[S.top()]=cns;S.pop(); }} long long Calc(int x) { int Bottom=floor(sqrt(2*x*1.0+0.25)-0.5); return 1ll*x*(Bottom+1)-1ll*(Bottom+1)*(Bottom+2)*Bottom/6;} void TopSort() { for (int i=1;i<=cns;i++) { Dp[i]=-(1ll<<60); if (Deg[i]==0) Q.push(i); } Dp[s]=Sum[s]; while (Q.size()) { int Now=Q.front(); Q.pop(); Ans=max(Ans,Dp[Now]); for (int i=Head[1][Now];i;i=Edge[1][i].Next) { int To=Edge[1][i].Ed; Dp[To]=max(Dp[To],Dp[Now]+Edge[1][i].Val+Sum[To]); Deg[To]--; if (Deg[To]==0) Q.push(To); } } return;} void Solve() { scanf("%d%d",&n,&m); Ans=0,Total[0]=Total[1]=0,cnd=0,cns=0; for (int i=1;i<=n;i++) Head[0][i]=Head[1][i]=0,Dfn[i]=Low[i]=0,SCC[i]=0,Deg[i]=0,Dp[i]=0,Sum[i]=0; for (int i=1;i<=m;i++) { scanf("%d%d%d",&In1,&In2,&In3); Edge_Add(In1,In2,In3,0); } scanf("%d",&s); for (int i=1;i<=n;i++) { if (!Dfn[i]) Tarjan(i); }s=SCC[s]; for (int i=1;i<=m;i++) { int S1=SCC[Edge[0][i].St],S2=SCC[Edge[0][i].Ed],V=Edge[0][i].Val; if (S1==S2) { Sum[S1]+=Calc(V); } else { Edge_Add(S1,S2,V,1); Deg[S2]++; } } TopSort(); printf("%lld\n",Ans);} int main() { freopen("goldknot.in","r",stdin); freopen("goldknot.out","w",stdout); scanf("%d%d",&c,&T); while (T-->0) Solve(); return 0;}