牛客周赛137D
原题
[https://ac.nowcoder.com/acm/contest/130843/D]
大意分析:
给你一组数据代表的是代价,把一段区间染成红色的代价是这段的区间和一个区间是可以重复染色的,这个超级重要
首先考虑的就是dp,可以说这道题一眼dp了,这里的dp[i]是前i个染成红色的最小代价
错误的状态转移方程
很容易想到的是经典的dp状态转移方程
就是先枚举i,i作为右端点,然后枚举j作为这个右端点的左端点
dp[i]=min(dp[i],dp[j-1]+a[j]^a[j+1]………………^a[i])
这对吗,这很不对,因为这个题目说了是可以重复染色的
上面的这个意思是,dp[i]是前面j-1个染成红色的最小代价加上j-i的代价,如下图黑色部分:
但是也可以是下面蓝色部分,也就是
dp[i]可以是dp[j+x-1]+a[j]^a[j+1]………………^a[i],
这种情况是上面那种状态转移方程没有考虑到的

正确代码
void solve(){
int n;cin>>n;
vector<int>a(n+1);
vector<int>s(n+1);
vector<int>dp(n+1,1e18);//代表前i个被染成红色的最小代价
for(int i=1;i<=n;i++){
cin>>a[i];
s[i]=s[i-1]^a[i];
}
dp[0]=0;
for(int i=1;i<=n;i++){
int mn=1e18;
for(int j=i;j>=1;j--){
mn=min(mn,dp[j-1]);
dp[i]=min(dp[i],mn+(s[j-1]^s[i]));
}
}
cout<<dp[n]<<endl;
}
分析
这个题目的数据要求是5000所以O(N^2)是可以的
同样是枚举i,然后反过来枚举j(就是找右端点)从j=i到j=1,找出j-1到i-1之间最小的那个dp[x]
然后
dp[i]=min(dp[i],mn+(s[j-1]^s[i]));
小疑惑
为什么是反过来枚举呢?
假设我们是正着过来枚举的
for(int i=1;i<=n;i++){
int mn=1e18;
for(int j=1;j<=i;j++){
mn=min(mn,dp[j-1]);
dp[i]=min(dp[i],mn+(s[j-1]^s[i]));
}
}
这样写对吗,其实是不对的,假设我们到j=1的时候,我们要的是0~i-1范围内最小的那个dp,那你是不是要往前走?
是的,因为你也不知道是不是前面的更优秀,如下图:

也就是说,当你到j的时候,你要知道你还没走过的地方的dp[x]是不是比你更小(这可能要用线段树那些七七八八的)
以防异或学的不太好的看不懂
s[i]^s[j-1]就是
a[j]^a[j+1]…………^a[i]
s[i]^s[j-1]=a[1]^a[2]^……a[i]^a[1]^a[2]……^a[j-1]相同的数字异或会消除掉

浙公网安备 33010602011771号