决策单调性优化dp二则

Posted on 2022-11-23 21:39  Capterlliar  阅读(15)  评论(0编辑  收藏  举报

Ciel and Gondolas  CodeForces - 321E

题意:有n个人,第i个人和第j个人放在一起时会产生a[i][j]的沮丧值(是社恐吗),保证a[i][j]=a[j][i],两个人只产生一份沮丧值。将n个人分成连续的k组,每组产生的沮丧值是这组人两两之间产生的沮丧值之和。求所有沮丧值的最小值。

解:设dp[i][j]为将前j个人分为i组,显然有dp[i][j]=min(dp[i-1][k]+sum(k,j)). 现在sum(k,j)和k有关系了,不能用单调队列优化。引入决策单调性。对于每一个dp[i][j],都会选择一个k进行转移,称k为决策点。决策单调性指后作出决策所选择的决策点,一定不在之前决策选择的决策点前面。

推荐读物:关于决策单调性优化动态规划

这道题属于上文中决策点之间不影响的情况,可以用分治实现。但一开始写了二分,然后T飞了……存个代码。

代码:

#include <bits/stdc++.h>
using namespace std;
#define maxx 4005
#define maxn 25
#define maxm 205
#define ll long long
#define inf 1000000009
#define mod 2520
int dp[805][maxx]={0};
int sum[maxx][maxx]={0};
int q[maxx]={0};
int n,k;
int read(int res = 0) {char ch = getchar(); while (!isdigit(ch)) ch = getchar(); while (isdigit(ch)) res = res * 10 + (ch - '0'), ch = getchar(); return res;}
int cal(int l,int r){
    return (sum[r][r]-sum[l-1][r]-sum[r][l-1]+sum[l-1][l-1])/2;
}
int get(int s,int e,int i){
    int l=e+1,r=n;
    int res=0x3f3f3f3f;
    while(l<=r){
        int mid=(l+r)/2;
        if(dp[i-1][s]+cal(s+1,mid)>=dp[i-1][e]+cal(e+1,mid)){
            res=mid;
            r=mid-1;
        }
        else{
            l=mid+1;
        }
    }
    return res;
}
signed main() {
    n=read();
    k=read();
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            int w=read();
            sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+w;
        }
    }
    for(int i=1;i<=n;i++){
        dp[0][i]=0x3f3f3f3f;
    }
    for(int i=1;i<=k;i++){
        int head=1,tail=1;
        q[1]=0;
        for(int j=1;j<=n;j++){
            while(head<tail&&get(q[head],q[head+1],i)<=j) head++;
            dp[i][j]=dp[i-1][q[head]]+cal(q[head]+1,j);
            while(head<tail&&get(q[tail-1],q[tail],i)>=get(q[tail],j,i)) tail--;
            q[++tail]=j;
        }
    }
    printf("%d\n",dp[k][n]);
    return 0;
}
//dp[i][j]=min(dp[i-1][k]+val(k+1,j))
View Code

分治代码:

#include <bits/stdc++.h>
using namespace std;
#define maxx 4005
#define maxn 25
#define maxm 205
#define ll long long
#define inf 1000000009
#define mod 2520
int dp[805][maxx]={0};
int sum[maxx][maxx]={0};
int n,k;
inline int read(){
    int x=0; char ch=getchar();
    for(;ch<'0'||ch>'9';ch=getchar());
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
    return x;
}
int cal(int l,int r){
    return (sum[r][r]-sum[l-1][r]-sum[r][l-1]+sum[l-1][l-1])/2;
}
int ii;
void solve(int l,int r,int s,int e){
    if(r<l||e<s) return;
    if(s==e){
        //printf("%d %d\n",l,r);
        for(int i=l;i<=r;i++){
            dp[ii][i]=dp[ii-1][s]+cal(s+1,i);
        }
        return;
    }
    int mid=(l+r)/2;
    int ans=s;
    for(int i=s;i<mid&&i<=e;i++){
        if(dp[ii][mid]>dp[ii-1][i]+cal(i+1,mid)){
            dp[ii][mid]=dp[ii-1][i]+cal(i+1,mid);
            ans=i;
        }
    }
    solve(l,mid-1,s,ans);
    solve(mid+1,r,ans,e);
}
signed main() {
    n=read();
    k=read();
    int x;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            x=read();
            sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+x;
        }
    }
    memset(dp,0x3f,sizeof dp);
    for(int i=0;i<=k;i++){
        dp[i][0]=0;
    }
    for(int i=1;i<=n;i++){
        dp[1][i]=cal(1,i);
    }
    for(int i=2;i<=k;i++){
        ii=i;
        solve(1,n,0,n-1);
    }
    printf("%d\n",dp[k][n]);
    return 0;
}
//dp[i][j]=min(dp[i-1][k]+val(k+1,j))
View Code

感觉这样的优化把第一次决策预处理好会舒服一点。

Yet Another Minimization Problem  CodeForces - 868F

题意:把上题的价值改为子数组内能组成多少两个元素相同的二元组,比如n个2可以组成n*(n-1)/2个这样的二元组,n不必须连续。

解:啊和上题套路一样一样的,处理val的时候用一种类似于莫队的方法,暴力加减(

代码:

#include <bits/stdc++.h>
using namespace std;
#define maxx 100005
#define maxn 25
#define maxm 205
#define ll long long
#define inf 1000000009
#define mod 2520
ll dp[25][maxx]={0};
int a[maxx]={0};
int n,k;
ll cnt[maxx]={0};
ll ans=0;
int L=1,R=1;
void upd(int c,int d){
    ans+=d*cnt[c]*(cnt[c]-1)/2;
}
ll cal(int l,int r){
    while(L<l) upd(a[L],-1),cnt[a[L]]--,upd(a[L],1),L++;
    while(L>l) L--,upd(a[L],-1),cnt[a[L]]++,upd(a[L],1);
    while(R>r) upd(a[R],-1),cnt[a[R]]--,upd(a[R],1),R--;
    while(R<r) R++,upd(a[R],-1),cnt[a[R]]++,upd(a[R],1);
    return ans;
}
int ii;
void solve(int l,int r,int s,int e){
    if(l>r||s>e) return;
    if(s==e){
        for(int i=l;i<=r;i++){
            dp[ii][i]=dp[ii-1][s]+cal(s+1,i);
        }
        return;
    }
    int mid=(l+r)/2,p=l;
    for(int i=s;i<mid&&i<=e;i++){
        if(dp[ii][mid]>dp[ii-1][i]+cal(i+1,mid)){
            dp[ii][mid]=dp[ii-1][i]+cal(i+1,mid);
            p=i;
        }
    }
    solve(l,mid-1,s,p);
    solve(mid+1,r,p,e);
}
signed main() {
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    cnt[a[1]]++;
    memset(dp,0x3f,sizeof dp);
    for(int i=1;i<=n;i++){
        dp[1][i]=cal(1,i);
    }
    for(int i=0;i<=k;i++){
        dp[0][i]=0;
    }
    for(int i=2;i<=k;i++){
        ii=i;
        solve(1,n,0,n-1);
    }
    printf("%lld\n",dp[k][n]);
    return 0;
}
//dp[i][j]=min(dp[i-1][k]+val(k+1,j))
View Code