P1032 · OFFICIAL SOLUTION

P1032 金缕曲 官方题解

Gioush OJ · P1032 金缕曲

金缕曲

【题意简述】

给定一张有向图和起点 ss。经过一条初始权值为 ww 的边时,第 jj 次经过可获得 wj(j1)2w-\dfrac{j(j-1)}2 中仍为正的部分。可以重复经过边,求最大收益。

【Hint】

在同一个强连通分量内部,只要一条边能被经过,就可以通过绕行把它的正收益全部取完。不同强连通分量之间则只能沿 DAG 前进。

【提示】

先 Tarjan 缩点。分量内部边贡献全部加入分量权值,分量外边保留一次边权作为 DAG 转移贡献。

【解法】

对原图求强连通分量。对于一条边 uvu\to v

  • u,vu,v 在同一强连通分量中,则这条边的全部正收益都可以取得,加入该分量的权值;
  • 若不在同一分量中,则它成为缩点 DAG 上的一条边,经过时只能获得一次 ww

一条边的内部总收益可以直接求,也可以二分最大的 tt 使

t(t1)2<w.\dfrac{t(t-1)}2<w.

缩点后在 DAG 上做最长路。设 DpxDp_x 表示到达分量 xx 时最多已经取得的收益,则沿边 xyx\to y 转移:

Dpymax(Dpy,Dpx+w+Sumy).Dp_y\leftarrow \max(Dp_y,Dp_x+w+\operatorname{Sum}_y).

答案为所有可达分量的最大 DpDp

【分量内部边的贡献】

若一条边初始收益为 ww,第 jj 次经过的正收益为 wj(j1)2w-\dfrac{j(j-1)}2。在同一 SCC 内,进入后可以绕行并反复经过这条边,所以它的全部正收益都能取得。令最大的正收益次数为 tt,满足 t(t1)2<w\dfrac{t(t-1)}2<w,将这 tt 项求和加入分量权值即可。

不同 SCC 之间不能绕回,缩点 DAG 上每条边至多使用一次,故只保留一次 ww 作为最长路转移。这样分量内“取尽”与分量间“至多一次”正好对应两类边。

【复杂度】

Tarjan 与 DAG 转移均为线性。若每条边的 tt 用二分求,复杂度为 O(n+mlogw)\mathcal{O}(n+m\log w);用公式求 tt 则为 O(n+m)\mathcal{O}(n+m)

【参考代码】

/*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;}