[USACO24OPEN] Smaller Averages G (单调性优化dp)

来源

题目

Bessie 有两个长度为 N的数组(1≤N≤500)。第一个数组的第 i 个元素为 ai​(1≤ai​≤10^6),第二个数组的第 i个元素为bi​(1≤bi​≤10^6)。

Bessie 希望将两个数组均划分为若干非空子数组,使得以下条件成立。

  1. 每个元素恰属于 1个子数组。
  2. 两个数组划分为相同数量的子数组。令第一个和第二个数组被划分为的子数组数量为 k(即,第一个数组被划分为恰好 k个子数组,第二个数组被划分为恰好 k 个子数组)。
  3. 对于所有1≤i≤k,第一个数组左数第 i 个子数组的平均值小于或等于第二个数组左数第 i个子数组的平均值。

计算她有多少种方式在满足限制的情况下将两个数组划分为非空子数组,对 10^9+7取模。两种划分方式被认为是不同的,如果子数组的数量不同或是某个元素属于不同的子数组。

输入格式

输入的第一行包含 NN。

第二行包含 a_1,a_2,\ldots,a_Na1​,a2​,…,aN​。

第三行包含 b_1,b_2,\ldots,b_Nb1​,b2​,…,bN​。

输出格式

输出在满足限制的情况下将两个数组划分为非空子数组的方法数模 10^9+7109+7 的余数。

思路

        首先不管复杂度,先考虑n^{4}的dp

        设dp_{i, j}为a数组的第i位和 b数组 的第 j 位相匹配的时方案数,那么dp_{i,j}应该为满足条件的数字对dp_{l, k}的和,条件就是a数组l到i的平均值,小于或等于b数组k到j的平均值,算平均值的时候要把除法转化为乘法,避免精度的缺失,对于这个dp只要暴力枚举i,j,l,k就行。

        对于题目所要求的n^{3}的复杂度,可以考虑只枚举三个变量,考虑用单调性优化掉其中的一维,但是对于一个数组的每一段的平均值必然不满足单调性,可以想到固定区间的右端点,把以此为右端点的区间全部根据区间的平均值进行排序。将两个数组的每一个i,j都做这样的预处理之后。

        我们之前是枚举l和k,现在只需要枚举k,对于固定的k,l一个是随着k 的增加而增加或者不变的,这样就优化了枚举l 的复杂度。

#include<bits/stdc++.h>

using namespace std;
#define int long long

const int N = 666;
const int mod = 1e9 + 7;


int n;
int l;
int a[N], b[N], suma[N], sumb[N];
int dp[N][N], sum[N][N];
struct node {
    int l, r, w;
} aa[N][N], bb[N][N];

bool cmp(node xx, node yy) {
    return xx.w * (yy.r - yy.l + 1) <= yy.w * (xx.r - xx.l + 1);
}

void solve() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        suma[i] = suma[i - 1] + a[i];
    }
    for (int i = 1; i <= n; i++) {
        cin >> b[i];
        sumb[i] = sumb[i - 1] + b[i];
    }
    for (int i = 1; i <= n; i++) {
        for (int j = i; j <= n; j++) {
            aa[j][i].l = i;
            aa[j][i].r = j;
            aa[j][i].w = suma[j] - suma[i - 1];
            bb[j][i].l = i;
            bb[j][i].r = j;
            bb[j][i].w = sumb[j] - sumb[i - 1];
        }
    }
    for (int i = 1; i <= n; i++) {
        sort(aa[i] + 1, aa[i] + 1 + i, cmp);
        sort(bb[i] + 1, bb[i] + 1 + i, cmp);
    }
    dp[0][0] = 1;

    for (int i = 1; i <= n; i++) {
        memset(sum, 0, sizeof(sum));

        for (int k = 1; k <= i; k++) {
            for (int j = 1; j <= n; j++) {
                sum[k][j] = (sum[k - 1][j] + dp[aa[i][k].l - 1][j - 1]) % mod;

            }
        }

        for (int j = 1; j <= n; j++) {
            l = 1;
            for (int k = 1; k <= j; k++) {
                while (l <= i) {
                    if (cmp(aa[i][l], bb[j][k])) {
                        l++;
                    } else break;
                }
                if (l!=1)
                    dp[i][j] = (dp[i][j] + sum[l - 1][bb[j][k].l]) % mod;
            }
        }
    }
    cout << dp[n][n];

    
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    solve();
    return 0;
}

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

心刍

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

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

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

打赏作者

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

抵扣说明:

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

余额充值