[usOJ3489]排列

题目

传送门 to usOJ

题目描述
琪露诺又开始学数学了——关于 x x x 的方程满足 1 + 1 = x 1+1=x 1+1=x,求 x x x 的值。

众所周知,琪露诺无法解答这个问题,并对提出这个问题的你感到无比愤怒,于是提出了一个简单的问题,希望得到你的解答。

给出一个 1 1 1 n n n 的排列,每次的操作定义为:选择一个区间 [ l , r ] [l,r] [l,r] ,并将这个区间中的所有数变成这个区间中的最大的数,请问所有的能够通过这样的操作到达的排列有多少种呢?

但是聪明的你觉得十分简单于是反问琪露诺,如果我限制总的操作次数那么答案又是多少呢?

琪露诺无法回答这个问题,于是你需要给她答案。自作自受大概就是这个意思

输入格式
第一行包含一个整数 T T T 表示数据的组数。

对于每组数据的第一行两个整数 n , K n,K n,K 表示排列的大小和操作的次数。

接下来一行 n n n 个数构成一个排列。

输出格式
输出包含 T T T 行,每行输出一个答案,模 1 0 9 + 7 10^9+7 109+7 输出。

数据范围和约定
对于所有的数据满足 T ≤ 10 T\le 10 T10
对于前 10 % 10\% 10% 的数据, n ≤ 8 n\le 8 n8
对于另外 30 % 30\% 30% 的数据, n ≤ 50 n\le 50 n50
对于另外 60 % 60\% 60% 的数据, n ≤ 200 n\le 200 n200

思路

猜猜最后构成的排列长什么样?也就是说,最后的序列有什么性质

举个例子。最初的排列是 ⟨ 2 , 3 , 4 , 1 , 5 ⟩ \langle 2,3,4,1,5\rangle 2,3,4,1,5 。最后生成的排列中, 3 3 3 不可能出现在最后(甚至不可能出现在后 3 3 3 位)。原因很简单,有一座高山 4 4 4 阻挡了 3 3 3

形式化地,若原排列为 ⟨ a ⟩ \langle a\rangle a ,最终的排列为 ⟨ b ⟩ \langle b\rangle b ,若 a i = b j a_i=b_j ai=bj ,则 a i = max ⁡ t = i j a t a_i=\max_{t=i}^{j}a_t ai=maxt=ijat 。(其实应该写成 ∣ t − i ∣ + ∣ t − j ∣ = ∣ i − j ∣ |t-i|+|t-j|=|i-j| ti+tj=ij ,因为 i , j i,j i,j 大小关系未知)。

而与之相对应的,其他地方都有可能。甚至有可能只出现在第 1 1 1 位。原因很简单,先覆盖第 1 1 1 位,然后第二位再被 4 4 4 覆盖。说白了就是 法无禁止皆可为

最后有一个性质,数字间先后顺序不改变。综合起来就是等价条件了。

所以我们只需要预处理出每个数可能出现的区间。然后 d p dp dp

f ( x , y , z ) f(x,y,z) f(x,y,z) 表示前 y y y 个数字共同填了长度为 x x x 的序列(或者说,最终的排列中,前 x x x 位只有原来的前 y y y 个值),搞了 z z z 次。

对于这个 z z z ,为了让计算方便,我们计算在每一个原序列的数字上进行了几次操作。即 1 1 1 y y y “身上”的费用。

  • y y y 个数被吃了!那么 f ( x , y , z ) = f ( x , y − 1 , z ) f(x,y,z)=f(x,y-1,z) f(x,y,z)=f(x,y1,z) 。(等着别人覆盖它,所以没有费用)
  • y y y 个数恰好覆盖 i i i !如果 y y y 原本就在 i i i ,那就没有费用;否则就有费用。
  • y y y 个数覆盖了两位或更多!那么 f ( x , y , z ) = ∑ v = L y − 1 x − 2 f ( v , y − 1 , z − 1 ) f(x,y,z)=\sum_{v=L_y-1}^{x-2}f(v,y-1,z-1) f(x,y,z)=v=Ly1x2f(v,y1,z1)

发现第 3 3 3 种转移很慢。但是这是个前缀和,处理一下就好了。

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <ctime>
using namespace std;
inline int readint(){
    int a = 0, f = 1; char c = getchar();
    for(; c<'0' or c>'9'; c=getchar())
    		if(c == '-') f = -1;
    for(; '0'<=c and c<='9'; c=getchar())
    	a = a*10+c-'0';
    return a*f;
}

const int MAXN=200,MOD=1e9+7;
int dp[MAXN+1][MAXN+1][MAXN+1], s[MAXN+1][MAXN+1][MAXN+1];
int a[MAXN+2], L[MAXN+1], R[MAXN+1];

int main(){
    int n, times, ans;
    for(int T=readint(); T; --T){
        n = readint(), times = readint();
        for(int i=1; i<=n; ++i)
            a[i] = readint();

        a[0] = a[n+1] = 2147483647; // 边缘 
        for(int i=1; i<=n; ++i){ // (L[i],R[i])
            for(int j=i+1; j<=n+1; ++j)
                if(a[i] < a[j]){
                    R[i] = j;
                    break;
                }
            for(int j=i-1; ~j; --j)
                if(a[j] > a[i]){
                    L[i] = j;
                    break;
                }
        }

        ans = 0;
        for(int j=0; j<=n; ++j) // init
            s[0][j][0] = dp[0][j][0] = 1;
        
        for(int i=1; i<=n; ++i)// 填了i个位置 
            for(int j=0; j<=n; ++j)// 第j个数 
                for(int k=0; k<=times; ++k){
                    if(j) // j被吞掉了 
                        dp[i][j][k] = dp[i][j-1][k];
                    if(i >= R[j]) // j也很绝望啊 
                        goto XME;
                    if(i == j) // 没准儿没动? 
                        dp[i][j][k] += dp[i-1][j-1][k];
                    else if(L[j] < i and k) // j只填了i
                        dp[i][j][k] += dp[i-1][j-1][k-1];
                    dp[i][j][k] -= dp[i][j][k]/MOD*MOD; // 取模 
                    if(k and L[j] <= i-2) // 填了不止一位 
                        dp[i][j][k] += s[i-2][j-1][k-1]-(L[j]?s[L[j]-1][j-1][k-1]:0);
                    dp[i][j][k] -= dp[i][j][k]/MOD*MOD;
                    if(dp[i][j][k] < 0)
                        dp[i][j][k]+=MOD;
                    XME: // s[i][j][k] = \sum_{v=0}^{i} dp[v][j][k]
                        s[i][j][k] = s[i-1][j][k]+dp[i][j][k];
                        s[i][j][k] -= s[i][j][k]/MOD*MOD;
                }
        for(int k=0; k<=times; ++k)
            ans += dp[n][n][k], ans %= MOD;
        printf("%d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值