10.22Bzoj4361 Isn(离散+树状数组迭代+容斥)

Bzoj4361 Isn

题意:

给出一个长度为n的序列A(A1,A2…AN)。如果序列A不是非降的,你必须从中删去一个数,
这一操作,直到A非降为止。求有多少种不同的操作方案,答案模10^9+7。

注意呀,是到非降即停,而不是求有多少种非降的序列

解题思路

可以发现非常类似CF597C的这道题,区别在于,CF这道题是求严格增与多少种序列
我们可以把Bzoj这题往CF上靠,只要排序之后,然后记录编号,再重新赋值,根据顺序对位置为编号重新赋值,既实现了离散化,又转换了问题,求变成严格增的方案

    for (int i = 1; i <= n; i++) {
        cin >> p[i].num;
        p[i].ord = i;
    }
    sort (p + 1, p + n + 1, cmp);
    for (int i = 1; i <= n; i++) {
        g[p[i].ord] = i;
    }

建立以长度为第一维的树状数组,利用迭代,记录每个长度以当前位置为结尾的严格增的个数(先不考虑怎样取数)
顺便利用个二维数组来记录下当前长度的位置有几个

    for (int i = 1; i <= n; i++) {
        a[1][i] = 1;
        update(1, g[i], 1);
        for (int k = 2; k <= n ; k++) {
            ll tmp = getsum(k - 1, g[i] - 1) % mod;
            a[k][i] = tmp % mod; //长度为k的以i位置结尾的严格增序列个数
            update(k, g[i], tmp % mod);
        }
    }

接下来是队友告诉我的思路:(容斥)
fac[]为阶乘函数,num[]为当前长度有几个
对于当前l长度可以先统计==所有可能到达的种类数p = fac[n-l] * num[l] ==
显然种类p不一定是正确答案,因为可能是由一个长度为l+1的合法序列删除一个得来的(有l+1种方法),所以减去fac[n-l+1] * num[l + 1] * (l + 1)
在减去的方案中,无论这是不是合法的方案,都会在之前就已经被计入ans中,所以这样做是对的,而合法的方案,之后又会重新加上

AC代码

#include <bits/stdc++.h>
#define lowbit(x) (x & (-x))
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
typedef long long ll;
using namespace std;
const ll mod = 1e9 + 7;
const int maxn = 2e3 + 10;
ll n, ans = 0;
ll g[maxn], fac[maxn];
ll c[maxn][maxn];
ll a[maxn][maxn];
struct P {
    ll num, ord;
} p[maxn];
void cal() {
    fac[0] = 1;
    for (ll i = 1; i <= 2000; i++) fac[i] = fac[i - 1] % mod * i % mod;
}
bool cmp (P A, P B) {
    if (A.num != B.num) return A.num < B.num;
    else return A.ord < B.ord;
}
void update(ll m, ll i, ll k) {
    while (i < maxn) c[m][i] = (c[m][i] + k) % mod, i += lowbit(i);
}
ll getsum(ll m, ll i) {
    ll res = 0;
    while (i) res = (res + c[m][i]) % mod, i -= lowbit(i);
    return res % mod;
}
int main() {
    IOS;
    cal();
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> p[i].num;
        p[i].ord = i;
    }
    sort (p + 1, p + n + 1, cmp);
    for (int i = 1; i <= n; i++) {
        g[p[i].ord] = i;
    }
    for (int i = 1; i <= n; i++) {
        a[1][i] = 1;
        update(1, g[i], 1);
        for (int k = 2; k <= n ; k++) {
            ll tmp = getsum(k - 1, g[i] - 1) % mod;
            a[k][i] = tmp % mod;
            update(k, g[i], tmp % mod);
        }
    }
    ll pnum = 0, nnum = 0;
    for (int j = 1; j <= n; j++) pnum += a[1][j];
    for (int i = 2; i <= n; i++) {
        for (int j = 1; j <= n; j++) 
            nnum += a[i][j];
        nnum %= mod, pnum %= mod;//pnum计算前一行,nnum计算当一行
        ans = ((ans + pnum % mod * fac[n - i + 1] % mod - i * nnum % mod * fac[n - i] % mod) % mod + mod) % mod;
        pnum = nnum, nnum = 0;
    }
    cout << ans << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值