Link
做了大概一年的样子
Solution
因为任何站都可以到达,所以这个图的形态一定是一个包含的环上挂着很多树。
考虑环,
带进去最后得到
,
可以看出每个点对答案的贡献与到的距离成正相关,且的值是和环的大小有关的。所以通过枚举修改环上点的后继为,就可以得到确定环的大小,并且把这个图转化成一棵树,可以进行DP了(在建图的时候直接忽略的后继,因为忽略了才是树,而且从上式可以看出忽略也没影响)。
然后就转化成一个树上的问题:
可以修改一个点的后继为,使得最大。
然后就开始高能了。
设计状态表示在以为根的子树中修改次,子树中的点对答案的最大贡献。
假如说现在要修改一个点的后继为,有如下的情况:

- 其子孙节点都没有改过
- 其子孙节点有一个已经改到了1
明显对答案的影响是不一样的。这个状态没法转移。
于是表示在以为根的子树中修改次,其子孙节点的修改状态为,子树中的点对答案的最大贡献。
发现这个问题的特点:当前状态的转移方法会对之后的状态的转移产生影响,而将当前状态的转移方法进行状压又不现实。
于是观察答案的性质:每个点对答案的贡献是,也就是说,只要知道了,这个点对答案的贡献就可以算了。所以就考虑把状态加上一维:表示在以为根的子树中修改次,的情况下,子树中的点对答案的最大贡献。
这样以来,每个点对答案的影响就可以独立统计了,也就解决了当前转移对后面状态转移产生影响的问题。
现在考虑如何转移(我做的时候把不合法的状态都设置了..)
首先定义的深度
对于的点,有两种选择:修改自己和不修改自己
如果修改自己,那子节点如果不修改,深度就是,子节点如果修改,深度就是;
如果不修改自己,子节点如果不修改,深度就是,如果修改,深度就是。
所以
然后和也类似,就可做了。
初始的合法状态
以下纯属一个蒟蒻的自言自语
当,
对于自己不修改的情况
肯定是合法的,置为。
当在这棵子树中花费为的时候,任意的都是合法的(祖先可能被修改)。
而对于自己有修改的情况,只需要把置为即可。
如果正向推不出来,可以反过来思考:
对于可能出现的每种合法转移,都必须能够沿着它的转移路线走到一个。
对于自己不修改的情况,对于不同的,的转移是互相独立的,所以在每种上都需要有一个。
对于自己有修改的情况,最终追溯到最优的一定是先用1次修改来把自己接到上。
其实这么做完全是作茧自缚,网上遍地是全都置,在转移的时候走合法路径的代码。。
多重背包的滚动数组
有种物品和一个容量为的背包。第种物品最多有件可用,每件耗费的空间是,价值是。求解将哪些物品装入背包可使这些物品的耗费的空间总和不超过背包容量,且价值总和最大。
1 | for(int i=1;i<=n;i++) |
对于背包和完全背包,可以使用滚动数组优化空间复杂度。
1
2
3
4for(int i=1;i<=n;i++)
for(int v=V;v>=c[i];v--)
//for(int v=c[i];v<=V;v++)
chkmx(f[v],f[v-c[i]]+w[i]);
而对于多重背包,使用滚动数组必须要保证:对于同一个体积,不会同时被和个相同的物品同时优化。
即,不能在一个体积的背包里先装上个,然后又装上个。
实现的方法是
1
2
3
4for(int i=1;i<=n;i++)
for(int v=V;~v;v--)
for(int k=0;k*c[i]<=v && k<=cnt[i];k++)
chkmx(f[v],f[v-k*c[i]]+k*w[i]);
对于一般的多重背包,第三层循环的枚举顺序应该可以倒过来(我没想出反例的样子?)
但是对于这道题目就不行了。
因为这个题目在的时候也可能会对答案产生贡献,如果从大到小枚举,就会让答案多加了(不要问我怎么知道的)
所以不管怎样写成从小到大的还是保险一些。(也许这样写也有坑点?)
Code
1 | //Code by Lucida |