去洛谷看我的博客
给出一个有向图,每条边有两个权值,分别代表通过该路径的最小要求 r i r_i ri,和通过后增加的值 p i p_i pi。问:从每个点出发,各需要至少多少初始值,才能不停走下去。
首先,分析一下,如果设 f i f_i fi 为从 i i i 点出发需要的最少初始值。那么可以轻易的推出转移方程: f i = min ( f i , max ( f j − p i , j , r i , j ) ) f_i=\min(f_i,\max(f_j-p_{i,j},r_{i,j})) fi=min(fi,max(fj−pi,j,ri,j)) (存在有向路径 i → j i\to j i→j)。
但是,因为有环的出现,没办法 dp,也没办法爆搜,所以我们需要从其他方面进行考虑。
很容易地想到一下两个性质:
如果一个点没有办法进入一个环,那么他一定无解。
如果一个点有解,那么 f i f_i fi 一定小于等于环中最大的 r i r_i ri。因为 p i ≥ 0 p_i \geq 0 pi≥0,所以初始值只可能变大或不变不可能变小,如果初始值等于最大的 r i r_i ri,那它在这个环中哪条路径都可以走,自然满足条件。
如果正着找,需要搜索回溯,多半会 TLE。那为什么不反着建图,然后就可以用起点更新终点了(其实也可以不反着建图,就是更新要用终点更新起点)。
然后我们就可以用拓扑排序+贪心来做这道题啦。
先把所有最开始出度为 0 0 0 的点找到,然后去掉同时减去相连的点的出度,用这个方法可以找到所有无解的点,找到后也不用更新,就赋成原来的值,最后输出的时候特判就好,然后把所有相关的边和点都删掉。
然后把边按照 r i r_i ri 从大到小排序,每次取最大的边,用最开始推得转移方程更新对应点的值,然后删边、减出度,直到有个点出度为 0 0 0 了,就代表答案固定了,就可以入队了。
然后每次取队首,更新连接的点,然后删边、减出度,如果又有点出度为 0 0 0,就又入队,直到队空,然后又开始找最大且没被删的边。
为什么要按照从大到小排序呢?如果最开始取了环中的较小边,某个点固定的答案,就可能不能满足较大边,而取最大边虽然最开始答案可能会比真实答案略大,但是最后一定是可以更新成正确答案的。
另外删边就直接用数组标记就好。
#include
using namespace std;
const int inf=0x3f3f3f3f;
struct node{int u,v,id,r,p;node(int _u=0,int _v=0,int _id=0,int _r=0,int _p=0){u=_u,v=_v,id=_id,r=_r,p=_p;}}a[200005];
inline bool cmp(node a,node b){return a.r>b.r;}
int n,m,e[400005],ne[400005],id[400005],wr[400005],wp[400005],h[200005],idx=1,out[200005],ans[200005],x,y,z,k;
bool vis[200005];
queue<int>q;
inline void add(int a,int b,int i,int r,int p){e[idx]=b,id[idx]=i,wr[idx]=r,wp[idx]=p,ne[idx]=h[a],h[a]=idx++;}
int main()
{
scanf("%d%d",&n,&m),memset(ans,0x3f,sizeof(ans));
for(int i=1;i<=m;++i) scanf("%d%d%d%d",&x,&y,&z,&k),a[i]=node(x,y,i,z,k),add(y,x,i,z,k),out[x]++;//反向建边,还要记得存边用来排序,顺便统计出度
for(int i=1;i<=n;++i) if(!out[i]) q.push(i);//找到出度为0的点
sort(a+1,a+m+1,cmp);
for(int i=1;i<=m;++i)
{
while(!q.empty())//更新答案
{
int u=q.front();q.pop();
for(int i=h[u];i;i=ne[i])
{
if(!vis[id[i]])
{
vis[id[i]]=1,--out[e[i]];
if(ans[u]!=inf) ans[e[i]]=min(ans[e[i]],max(wr[i],ans[u]-wp[i]));//此处特判是用于最开始的无解情况,无解情况就不能更新答案了
if(!out[e[i]]) q.push(e[i]);
}
}
}
if(!vis[a[i].id]) vis[a[i].id]=1,ans[a[i].u]=min(ans[a[i].u],a[i].r),out[a[i].u]--;//每次找最大边,保证答案符合题意
if(!out[a[i].u]) q.push(a[i].u);//出度为0了就要入队
}
for(int i=1;i<=n;++i) printf("%d ",(ans[i]!=inf)?ans[i]:-1);//输出记得特判-1
return 0;
}