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\),那么可以得到转移
因为这是个有向图,所以可以考虑使用拓扑排序进行无后效性的转移。
对每条边的转移是 \(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 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步