Gushing over Programm|

BigSmall_En

园龄:3年2个月粉丝:3关注:5

2022-08-01 21:04阅读: 30评论: 0推荐: 0

CF1704E 题解

CF1704E solution

给定一个只有 \(n\) 个点 \(m\) 条边且只有一个点出度为 \(0\) 的有向无环图,点有点权且非负。每次可以选择将所有点权大于 \(0\) 的点的点权减 \(1\),同时他的每条出边上的点权 加 \(+1\),问需要经过多少次才能将图中所有的点的值变为 \(0\)

模拟一类简单的情况,图中只有两个点 \(u,v\),存在一条从 \(u\)\(v\) 的有向边,\(u\) 点的值为 \(3\)\(v\) 点的值为 \(0\)

发现 \(u\) 点在 \(0,1,2\) 三个时刻可以给 \(v\)\(1\),所以 \(v\) 点必须要给自身减 \(3\) 才能变为 \(0\),但是 \(v\) 点不能在 \(0,1,2\) 时刻给自己分别 \(-1\),而只能选择在 \(1,2,3\) 时刻,即自身有值的时候才能给自己减 \(1\)。这两个 \(-1\) 的过程是存在时间差的。我们可以认为 \(u\) 点在 \(1\) 时刻给 \(v\)\(+3\)\(v\) 点可以在自己获得贡献的同一时刻将自己的值减去。

那么再假设现在有 \(w\) 点,存在一条 \(v\)\(w\) 的有向边,\(w\) 点取值为 \(0\)。容易发现 \(w\) 点只能在 \(2,3,4\) 时刻给自己 \(-1\),也就是说 \(v\) 点在 \(2\) 时刻给 \(w\)\(+3\)

于是我们定义一个桶 \(bot_{i,j}\),表示 \(i\) 点在 \(j\) 时刻从前驱节点得到的值的总和。

对于 \(i\) 点的初始值 \(a_i\),我们可以认为存在一个超级点在 \(0\) 时刻给 \(i\)\(+3\)

for(int i=1;i<=n;++i){
    a[i]=read();
    bot[i][0].lef=a[i];
}

假设现在存在有向边 \(u\to v\),那么可以得到转移

\[bot_{v,j+1}\gets bot_{u,j} \]

因为这是个有向图,所以可以考虑使用拓扑排序进行无后效性的转移。

对每条边的转移是 \(O(n)\) 的,所以整个拓扑的时间复杂度是 \(O(nm)\)

for(int i=1;i<=m;++i){
    int u=read(),v=read();
    edge[u].push_back(v);++ind[v];
}
queue<int>q;
for(int i=1;i<=n;++i)
    if(!ind[i]){q.push(i);}
while(!q.empty()){
    int u=q.front();q.pop();
    for(auto v:edge[u]){
        for(int i=1;i<=n;++i)
            bot[v][i]=bot[v][i]+bot[u][i-1];
        if(!--ind[v])q.push(v);
    }
}

现在我们求得了每个节点的 \(bot\) 值,那么统计答案的时候显然不能简单地对 \(bot_i\) 中的元素求和。假设我们计算了到 \(j-1\) 时刻 \(i\) 节点剩余的值为 \(k\),且 \(bot_{i,j}\neq 0\),如果 \(k=0\),那么答案就是 \(j+bot_{i,j}\);如果 \(k>0\),那么答案才是 \(k+bot_{i,j}\)

ll ans=0;
for(int i=1;i<=n;++i){
    ll tmp=0;
    for(int j=0;j<=n;++j){
        if(bot[i][j]){
            tmp=max((ll)j,tmp);
            tmp+=bot[i][j];
        }
    }
    ans=max(ans,tmp);
}

但是上面的计算不足以通过此题,因为 \(bot_{i,j}\) 中的值很大,甚至会超过 __int128 的范围,但是为了满足答案的正确性又不能时时取模。

解决这个问题还是要从题目本身入手,题目保证了只存在一个点出度为 \(0\),那么只用对这个点计算答案即可。记录拓扑排序中的最后一个点为 \(las\),其实答案就是计算 \(bot_{las}\)。(但我在做的时候竟然没看到这个条件,最后随便取了个 \(las\) 发现过了,过了好几个小时重温的时候才发现有出度限制这个条件。)

这样我们用一个结构体记录这个值本身是否超过模数,和这个值对模数取模后的值,就可以开心地判断与 \(j\) 的大小了。(不知道大佬们有没有更好的实现方法)

struct number{
	bool tim;ll lef;
	inline void init(){tim=0,lef=0;}
	number(){init();}
	number operator+(const number &a){
		number tmp;
		tmp.tim=(tim|a.tim);
		if(a.lef+lef>=MOD){
			tmp.tim|=1;
			tmp.lef=a.lef+lef-MOD;
		}else tmp.lef=a.lef+lef;
		return tmp;
	}
};
number ans;
for(int j=0;j<=n;++j){
    if(bot[las][j].tim||bot[las][j].lef){
        if(!ans.tim&&ans.lef<j)ans.lef=j;
        ans=ans+bot[las][j];
    }
}
printf("%lld\n",ans.lef);

代码块已经很全面了,完整代码就不贴了,总时间复杂度为平方级别。

本文作者:BigSmall_En

本文链接:https://www.cnblogs.com/BigSmall-En/p/16541838.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   BigSmall_En  阅读(30)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起