hdu5009 Paint Pearls(dp,双向链表优化,范围优化)

题意:

给定为n的序列a,每个元素用来表示第i个格子的颜色为a[i]
现在你需要将这个序列划分为若干个连续区间,每个区间的代价为颜色数的平方,
问划分整个序列的最小代价

数据范围:n<=5e4

解法:

d[i]表示处理完前i个格子的最小代价
d[i]=min{d[j]+col(j+1,i)^2},col(l,r)表示区间[l,r]内的颜色数

但是这样需要枚举j,O(n^2),需要优化

看一个例子:
假设序列为:
------------------
下标 1 2 3 4 5 6
------------------
数值 3 4 2 4 2 4
------------------
dp值 x x x x x ? (x表示已经计算出来了,?表示现在要计算)
------------------
假设我们已经计算完了d[1]到d[5],现在要计算d[6]
枚举j:
d[6]=min(d[6],d[5]+1*1)
d[6]=min(d[6],d[4]+2*2)
d[6]=min(d[6],d[3]+2*2)
d[6]=min(d[6],d[2]+2*2)
d[6]=min(d[6],d[1]+2*2)
d[6]=min(d[6],d[0]+3*3)
发现在这个例子中,有很多都只有24两种颜色,对答案的贡献都是2*2
所以d[4]+2*2到d[1]+2*2本质上后者更优,
因为2*2都是相同的,但是d[1]显然<=d[4],
出现这种状况的原因是这一段的颜色种类没有变化,
选的左端点越前面越优

因为从d[2]到d[4]这段转移可以被d[1]取代,
实际上到达d[6]的有效转移位置只有d[0],d[1],d[5],
考虑如何快速从d[5]直接跳到d[1],省略中间的无用状态.

1.d[5]是位置6前面第一个出现不同数字的位置,a[5]=2,a[6]=4,
2.d[1]是位置5前面第一个出现不同数字的位置,
a[1]=1,而a[5]=2,a[6]=4,中间[2,4]都是24,数字种类不变
3.发现从左往右遍历的时候,只需要保留每个数字最后出现的一个位置,
因此没到一个新得位置,就判断这个数是否出现过,如果出现过就把前面的那个删掉
这个找前驱+删除的操作,可以用双向链表实现

但是这样还是不行,因为如果每个数都不相同,就没法跳了,复杂度还是O(n^2)的
考虑如何优化这种情况:
将这个序列分成n个长度如果为1的区间,代价是n,这是一定存在的最劣代价
如果当前的颜色数量>sq,那么这个区间的代价就是>sq*sq=n,肯定不优,
因此当颜色数量>sq的时候可以break,这样复杂度最坏也只有O(sq)了
最坏总复杂度也只有O(n*sq)
(设当前颜色数量为cnt,代码里面应该判断是否cnt*cnt>i,比写cnt*cnt>n更好一点)


总结:
1.学到了特殊题目的链表优化(删除拓扑图上的劣节点),
感觉和单调队列优化啥的差不多,但是单调队列没办法删除中间节点,
2.推算出题目的最劣答案,可以用来剪枝,例如本题cnt*cnt>n时就break

code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=5e4+5;
int l[maxm],r[maxm];
int d[maxm];
int a[maxm];
int n;
signed main(){
    ios::sync_with_stdio(0);
    while(cin>>n){
        for(int i=1;i<=n;i++)cin>>a[i];
        for(int i=1;i<=n;i++){
            l[i]=i-1;
            r[i]=i+1;
            d[i]=1e9;
        }
        map<int,int>mark;
        d[0]=0;
        l[0]=-1;
        for(int i=1;i<=n;i++){
            if(!mark[a[i]]){
                mark[a[i]]=i;//没出现过
            }else{//出现过,删除前面那个
                int temp=mark[a[i]];
                r[l[temp]]=r[temp];
                l[r[temp]]=l[temp];
                mark[a[i]]=i;
            }
            int cnt=0;//记录不同数字的个数
            for(int j=l[i];j!=-1;j=l[j]){
                cnt++;
                d[i]=min(d[i],d[j]+cnt*cnt);
                if(cnt*cnt>i)break;//超过i一定不优
            }
        }
        cout<<d[n]<<endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_44178736/article/details/107755152