题意:
一个有向有环有重边有自环图,一条路径可以多次经过,并且每次路径长度的计算方式为:设路径长度为
w
w
w,在第一次到达时路径长度为
w
w
w,第二次到达时为
m
a
x
(
w
−
1
,
0
)
max(w-1,0)
max(w−1,0),第三次为
m
a
x
(
w
−
1
−
2
,
0
)
max(w-1-2,0)
max(w−1−2,0),第四次为
m
a
x
(
w
−
1
−
2
−
3
,
0
)
max(w-1-2-3,0)
max(w−1−2−3,0),即若在第i次经过路径,则路径长度为
w
−
(
i
−
1
)
∗
i
/
2
w-(i-1)*i/2
w−(i−1)∗i/2。
现在问你从起点出发可以获得的最大路径长度为多少。
题目链接:
一般这种题目可以先从有向无环图的情形下分析,既然是有向无环图,那么说明一条边只会被经过一次,故按照拓扑排序即可确定路径长度,即:
对于节点u,遍历出边 ans[v]=max(ans[u]+w);
现在引入环,注意到如果存在一个环可以在环上一直走到环上路径长度都为0了再走出来即可,故对于一个环我们可以直接计算出这个环可以贡献总长度为多少的路径。于是可以先tarjan缩点,建新图,跑拓扑序。
若是正向思考转移方程的话,本题还有一个坑点,就是在你建立的新图中可能会存在多个点入度都为0,故需要将这些边删掉,但删边太麻烦了,可以直接减掉这些边的终点的度数,因为我们必须从给定的起点出发,那么那些其他的入度为0的点的出边必然不会被经过,于是可以有一个度数–的方法。
具体来说。
先tarjan缩点,然后遍历所有边,计算环上长度贡献,建新图。
给不合法路径的终点度数–
拓扑排序转移方程即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+7;
const int maxm=2e6+7;
struct Edge{
int v,next;
ll w;
}edge[maxm];
int head[maxn],top,Tar[maxn];
void init(){top=0;memset(head,-1,sizeof(head));memset(Tar,-1,sizeof(Tar));}
void add(int u,int v,ll w,int head[]){
edge[top].v=v;
edge[top].w=w;
edge[top].next=head[u];
head[u]=top++;
}
int low[maxn],dfn[maxn];
bool vis[maxn];
int num;
stack<int> S;
int res;//强联通分量个数;
int belong[maxn];//标记所属分量;
void tarjan(int u){
vis[u]=1;
S.push(u);
dfn[u]=low[u]=++num;
for(int i=head[u];i!=-1;i=edge[i].next){
int v=edge[i].v;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
int x;
++res;
do{
x=S.top();S.pop();
vis[x]=0;
belong[x]=res;
}while(x!=u);
}
}
ll Val[maxn];//环上长度贡献;
int du[maxn];
ll Res;//最终答案;
ll Ans[maxn];//起点为st,终点为i的路径长度最大值;
queue<int> q;
void Top_sort(int st){
q.push(st);
Ans[st]=Val[st];
Res=max(Res,Ans[st]);
while(q.size()){
int u=q.front(); q.pop();
for(int i=Tar[u];i!=-1;i=edge[i].next){
int v=edge[i].v;
ll w=edge[i].w;
Ans[v]=max(Ans[v],Ans[u]+w+Val[v]);
Res=max(Res,Ans[v]);
if(--du[v]==0) q.push(v);
}
}
printf("%lld\n",Res);
}
struct Node{
int u,v,w;
}a[maxn];
ll qian[maxn];//等差数列;
ll sum[maxn];//等差数列前缀和;
ll gett(ll w){//计算原长度为w的边的总长度;
ll l=0,r=1e5,mid;
while(l<=r){
mid=l+r>>1;
ll x=w-(mid+1)*mid/2;
if(x<0) r=mid-1;
else l=mid+1;
}
return (r+1)*w-sum[r];
}
long long val(ll wei){//O(1)计算,注意到方程可以直接求解,同时等差数列的前缀和其实是一个组合数公式;
long long k=(long long)(sqrt((long double)wei*2+0.25)-0.5);
return (k*(6*wei-(k+1)*(k+2)))/6+wei;
}
void dfs(int u){
vis[u]=1;
for(int i=Tar[u];i!=-1;i=edge[i].next){
int v=edge[i].v;
if(vis[v]) continue;
dfs(v);
}
}
int main(){
for(int i=1;i<maxn;++i) qian[i]=qian[i-1]+i;
for(int i=1;i<maxn;++i) sum[i]=sum[i-1]+qian[i];
init();
int n,m,st,u,v,w;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i){
scanf("%d%d%d",&u,&v,&w);
add(u,v,w,head);
a[i].u=u,a[i].v=v,a[i].w=w;
}
scanf("%d",&st);
for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
for(int i=1;i<=m;++i){
u=a[i].u,v=a[i].v,w=a[i].w;
int bu=belong[u],bv=belong[v];
if(bu==bv) Val[bu]+=gett(w);
else add(bu,bv,w,Tar),++du[bv];
}
memset(vis,0,sizeof(vis));
dfs(belong[st]);
for(int i=1;i<=res;++i)
if(!vis[i])
for(int j=Tar[i];j!=-1;j=edge[j].next)
--du[edge[j].v];
Top_sort(belong[st]);
return 0;
}