洛谷P10282 [USACO24OPEN] Smaller Averages G(归并排序优化dp)

题目

思路来源

乱搞ac

题解

由于k不是固定的,所以不用记录k,只需每次往上续上一段即可

容易想到O(n^4)的做法,就是枚举a的最后一段,b的最后一段,

只要这最后一段对应合法,续在前面的合法方案后就可以了

然后考虑怎么优化这个O(n^4),还是dp[i][j]表示第一段以i结尾、第二段以j结尾的合法方案

枚举x<i,枚举y<j,[x,i]区间与[y,j]区间一共是O(n)个的,

可以先预处理它们的平均值的排序,O(n^2logn),

然后对两堆有序的再进行归并排序,这样复杂度就是O(n^3)的了

思想是这个思想,但是注意到,如果直接这样枚举的话,

转移的时候,dp[i][j]要从dp[x-1][y-1]转移过来,这两维都不是固定的,实际无法完成归并

所以,要把第一维固定成i,这样才能对第二维进行归并,

所以,枚举x>i,枚举y<j,对区间[i,x]和区间[y,j]进行归并,

不妨按平均值从大到小归并,这样可以把所有平均值大的dp[i-1][y-1]

加到当前dp[x][j]上,保证了变量同时只会有一维

代码

#include<iostream>
#include<cstdio>
#include<vector>
#include<map>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
typedef double db;
typedef pair<int,int> P;
#define fi first
#define se second
#define pb push_back
#define dbg(x) cerr<<(#x)<<":"<<x<<" ";
#define dbg2(x) cerr<<(#x)<<":"<<x<<endl;
#define SZ(a) (int)(a.size())
#define sci(a) scanf("%d",&(a))
#define pt(a) printf("%d",a);
#define pte(a) printf("%d\n",a)
#define ptlle(a) printf("%lld\n",a)
#define debug(...) fprintf(stderr, __VA_ARGS__)
const int N=505,mod=1e9+7;
int n,a[N],b[N],sum[N],sum2[N],dp[N][N];
vector<int>p[N],q[N];
void add(int &x,int y){
    x=(x+y)%mod;
}
int main(){
    sci(n);
    rep(i,1,n){
        sci(a[i]);
        sum[i]=sum[i-1]+a[i];
    }
    rep(i,1,n){
        rep(j,i,n){
            p[i].pb(j);
        }
        sort(p[i].begin(),p[i].end(),[&](int x,int y){
            return 1ll*(sum[x]-sum[i-1])*(y-i+1)>1ll*(sum[y]-sum[i-1])*(x-i+1);//[i,x] [i,y]
        });
    }
    rep(i,1,n){
        sci(b[i]);
        sum2[i]=sum2[i-1]+b[i];
        rep(j,0,i-1){
            q[i].pb(j);
        }
        sort(q[i].begin(),q[i].end(),[&](int x,int y){
            return 1ll*(sum2[i]-sum2[x])*(i-y)>1ll*(sum2[i]-sum2[y])*(i-x);//(x,i] (y,i] 1ll*(sum[i]-sum[x])/(i-x)<1ll*(sum[i]-sum[y])/(i-y)
        });
    }
    dp[0][0]=1;
    rep(i,1,n){
        rep(j,1,n){//(i,j) 对于固定y i伸出去一段 用于固定左端点 j缩进来一段 dp值已知
            int p1=0,p2=0,now=0;
            while(p1<SZ(p[i]) && p2<SZ(q[j])){
                int x=p[i][p1],y=q[j][p2];
                if(1ll*(sum[x]-sum[i-1])*(j-y)<=1ll*(sum2[j]-sum2[y])*(x-i+1)){//[i,x] [y+1,j] 固定i和j
                    add(now,dp[i-1][y]);
                    p2++;
                }
                else{
                    add(dp[x][j],now);
                    p1++;
                }
                //printf("i:%d j:%d now:%d p1:%d p2:%d x:%d y:%d\n",i,j,now,p1,p2,x,y);
            }
            while(p1<SZ(p[i])){
                int x=p[i][p1];
                add(dp[x][j],now);
                p1++;
            }
            //printf("i:%d j:%d dp:%d\n",i,j,dp[i][j]);
        }
    }
    pte(dp[n][n]);
    return 0;
}

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值