HDU 2829 区间DP & 前缀和优化 & 四边形不等式优化
n个节点n-1条线性边,炸掉M条边也就是分为m+1个区间 问你各个区间的总策略值最少的炸法 就题目本身而言,中规中矩的区间DP问题 d
p[i][j]表示前i个节点,分为j个区间的最优策略值 cost[i][j]为从i到j节点的策略值 所以dp[i][j] = min(dp[k-1][j-1] + cost[k][i]
但是复杂度太高了 可以优化的地方有: cost数组值得求取: 考虑到cost(i,j)=ΣAxAy (i≤x<y≤j) 而(Ai+...+Aj)^2=ΣAxAy (i≤x,y≤j) 于是可以得到: cost(i,j)=((Ai+...+Aj)^2-(Ai^2+...+Aj^2))/2 这是一个优化后线性n的等式 式子中的若干连续项的和与若干连续项的平方和 是可以用 前缀和 预先处理的, 所以设sum(i)=A1+...+Ai,sqsum(i)=A1^2+...+Ai^2, 将原式化为: cost(i,j)=((sum(j)-sum(i-1))^2-(sqsum(j)-sqsum(i-1)))/2
又因为是经典的区间DP问题所以可以用四边形不等式进行优化
设s[i][j]为dp[i][j]的前导状态dp[i][j] = dp[s[i][j][j-1] + cost[s[i][j]+1][j]之后我们枚举k的时候只要枚举s[i][j-1]<=k<=s[i+1][j],此时j必须从小到大遍历i必须从大到小。
#include <iostream> #include <algorithm> #include <string.h> #include <cstdio> #define inf (1 << 30) using namespace std; const int maxn = 1e3 + 1e2; int dp[maxn][maxn]; int s[maxn][maxn]; //设s[i][j]为dp[i][j]的前导状态 //dp[i][j] = dp[s[i][j][j-1] + cost[s[i][j]+1][j] //之后我们枚举k的时候只要枚举 //s[i][j-1]<=k<=s[i+1][j],此时j必须从小到大遍历 //i必须从大到小。 int cost[maxn][maxn]; int sum[maxn],powsum[maxn]; int a[maxn]; /*int get_cost(int l,int r) { if(r < l)return 0; return ((sum[r] - sum[l-1]) * (sum[r] - sum[l-1]) - (powsum[r] - powsum[l-1])) / 2; }*/ void init() { memset(sum,0,sizeof(sum)); memset(powsum,0,sizeof(powsum)); memset(cost,0,sizeof(cost)); } int main() { int n,m; while(~scanf("%d%d",&n,&m)) { if(n == m && n == 0)break; init(); //m++; //前缀和处理 for(int i = 1;i <= n;i++) { scanf("%d",&a[i]); sum[i] = sum[i-1] + a[i]; powsum[i] = powsum[i-1] + a[i] * a[i]; } /*for(int i = 1;i <= n;i++) for(int j = 1;j <= n;j++) { if(j < i) cost[i][j] = 0; else cost[i][j] = cost[i][j-1] + a[j] * (sum[j-1] - sum[i-1]); }*/ //特殊值预处理 //这里没有m++但是0代表分一块 for(int i = 0;i <= n;i++) { dp[i][0] = cost[1][i]; // dp[i][0] = get_cost(1,i); s[i][0] = 0; s[n+1][i] = n;//外面的界限出界后的特殊处理 } //区间DP & 四边形不等式 for(int j = 1;j <= m;j++)//分几部分 { for(int i = n;i >= 1;i--)//前n个节点 { dp[i][j] = inf; for(int k = s[i][j-1];k <= s[i+1][j];k++) { if(dp[i][j] > dp[k][j-1] + get_cost(k+1,i)) { dp[i][j] = dp[k][j-1] + get_cost(k+1,i); s[i][j] = k;//确定上一个状态 } } } } printf("%d ",dp[n][m]); } return 0; }