2025“钉耙编程”中国大学生算法设计春季联赛(1)1009 切割木材 对于状态合并更加深入的描述

状态合并与候选状态数量上限

在染染船长木材分割问题中,我们在动态规划过程中维护的候选状态用三元组表示:

\[(\text{dp值},\, \text{sand},\, \text{sor}) \]

其中:

  • dp值 表示上一次分割时的最优值。
  • sand 为当前区间所有数字的按位与,初始为全 \(1\)(即 \((1 \ll m)-1\))。
  • sor 为当前区间所有数字的按位或,初始为 \(0\)

候选状态更新

当加入新的数字 \(a[i]\) 时,每个候选状态更新为:

  • \[\text{sand} := \text{sand} \,\&\, a[i] \]

  • \[\text{sor} := \text{sor} \,|\, a[i] \]

更新后的状态对应区间 \((j+1, i)\)\(f\) 值为

\[f = \text{sor} - \text{sand} \]

候选状态对转移贡献为:

\[\text{dp}[j] + g(f) \]


状态合并

  • 原理:
    当多个候选状态更新后若得到相同的 \((\text{sand}, \text{sor})\) 状态,由于 $$g(f)$$ 仅依赖于 $$f = \text{sor} - \text{sand}$$,不同状态对后续转移的贡献只差在 dp 值上。此时只保留 dp 值更大的状态即可。

  • 实现:
    遍历更新后的候选状态集合,若当前状态与前一状态的 \((\text{sand}, \text{sor})\) 相同,则更新前一状态的 dp 值为两者中的最大值;否则将当前状态加入集合。


为什么候选状态数始终维持在 \(O(m)\) 内?

  • 单调性:

    • 对于 sand,初始全 \(1\),随着区间扩展每个位只能由 \(1\) 变为 \(0\),一旦变 \(0\) 不再恢复。
    • 对于 sor,初始为 \(0\),每个位只能由 \(0\) 变为 \(1\),一旦变 \(1\) 不会回退。
  • 变化次数有限:
    每个位仅会发生一次状态转变($$1 \to 0$$ 或 $$0 \to 1$$),所以从某个分割点开始,整个候选状态的 \((\text{sand}, \text{sor})\) 组合的变化最多有 \(m\) 次。

  • 合并消除冗余:
    通过合并那些更新后得到相同 \((\text{sand}, \text{sor})\) 的候选状态,只保留最优 dp 值,从而避免状态重复。最终,每一步候选状态的数量最多不会超过 \(O(m)\)


总结

  • 状态更新与合并: 每次加入新数字 \(a[i]\) 后,候选状态更新为

\[(\text{dp值},\, \text{sand} \,\&\, a[i],\, \text{sor} \,|\, a[i]) \]

若出现相同 \((\text{sand}, \text{sor})\) 状态,则仅保留 dp 值最大的状态。

  • 状态数上限: 由于每个位的状态变化仅发生一次,总共 \(m\) 位,候选状态的变化次数受限,加上合并策略,确保候选状态始终在 \(O(m)\) 范围内,从而实现高效转移。

这种方法确保了动态规划在 $$O(n \times m)$$ 的时间复杂度内完成,适合 \(n\) 较大的情况(如 \(10^5\)),而 \(m\) 通常较小(最多 \(20\))。
code:

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define all(x) x.begin(),x.end()
#define rall(x) x.rbegin(),x.rend()
#define pb push_back
#define pii pair<int,int>
using namespace std;
const int mod=998244353;
int gcd(int a,int b){return b?gcd(b,a%b):a;};
int qpw(int a,int b){int ans=1;while(b){if(b&1)ans=ans*a%mod;a=a*a%mod,b>>=1;}return ans;}
int inv(int x){return qpw(x,mod-2);}
void solve(){
    int n,m;cin>>n>>m;
    vector<int>a(n+1),g(1<<m),dp(n+1);
    for(int i=1;i<=n;i++){cin>>a[i];}
    for (int i=0;i<(1<<m);i++)cin>>g[i];
    vector<tuple<int,int,int>>v;
    v.pb({dp[0],(1<<m)-1,0});
    for (int i=1;i<=n;i++) {
        dp[i]=-1e18;
        for (auto &[val,sand,sor]:v) {
            int new_sand=sand&a[i];
            int new_sor=sor|a[i];
            dp[i]=max(dp[i],val+g[new_sor-new_sand]);
            sand=new_sand;
            sor=new_sor;
        }
        auto tmp=v;
        v.clear();
        for (auto &t:tmp) {
            if (v.empty() ||
                std::get<1>(v.back()) != std::get<1>(t) ||
                std::get<2>(v.back()) != std::get<2>(t)) {
                v.push_back(t);
                }else {
                    get<0>(v.back())=max(get<0>(v.back()),get<0>(t));
                }
        }
        v.emplace_back(dp[i], (1 << m) - 1, 0);
    }
    cout<<dp[n]<<'\n';
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
    int _=1;
    cin>>_;
    while(_--)solve();
}
posted @ 2025-03-13 17:59  archer2333  阅读(79)  评论(0)    收藏  举报