loj2743「JOI Open 2016」摩天大楼

#2743. 「JOI Open 2016」摩天大楼

前言

此题使用的 dp 我称为线头 dp (不喜轻喷,主要是为了区分插头 dp),在我很久以前就有这个想法,不过一直没有找到题目,最近找到了,于是决定写这篇题解。

题目简意

给一个长度为 n ,元素互不相同的序列 A ,求有多少个长度为 n 的排列 p 使得 i=1n1|ApiApi+1|L ,对 109+7 取模。

n100,Ai,L1000

题解

为了方便,先将 A 排序。显然,这样做不会改变答案。

考虑把题目里限制的式子 i=1n1|ApiApi+1| 拍到数轴上,其含义就是从 Ap1 出发,依次走到 Api(2in) 经过的总长度。不妨把走的路线画出来,我们规定每“调一次头”就把路线切换到下一层,并在每次走到 Api 的时候在路线上把 Api标注出来,这样子画出来的路线和排列是一一对应的,只需要对合法的路线计数即可。

举个例子,设 A={2,3,5,6,7}p={2,1,5,4,3} ,那么画出来的路线长这样:

img

考虑对这条路线 dp 。先不考虑最终路线的端点,可以发现路线的每一个纵截面形如若干个并列的线头,并且我们只需要关心线头的数量。于是我们像扫描线一样,从左到右对线头的形态 dp ,设 fi,j,k 表示扫到 Ai ,当前纵截面有 j 个线头,截止到现在,路线总长为 k 的方案数。

img

(一个线头长这样,中间的折线省略了路线的形态)

容易发现,每个 Ai 对线头的形态的改变只有 3 种情况:

  1. 新增一个线头
  2. 合并两个线头
  3. 不改变

每种情况在原排列上其实表现为 (这里写出来帮助理解)

  1. Api1>Api,Api+1>Api
  2. Api1<Api,Api+1<Api
  3. Api1<Api<Api+1Api+1<Api<Api1

这三种情况的转移都是容易的,转移式在最后加上端点限制后一起写。

现在考虑最终路径端点的情况:首先可以发现,起点的线头一定在最上面,终点的线头一定在最下面,并且它们本质一样,所以这里只讨论起点。起点的形态显然只有两种,往左走和往右走。

往左走的情况可以转化成把最上面的线头的上端点封住。

img

而往右走的情况可以转化成在上面添加一个虚线头后,把虚线头的上端点封住。

img

自然地,我们需要在状态里加一维 d 表示边界确定了即可,或者说最上面和最下面的线头有几个的端点被封了。

现在的状态为 fi,j,k,d 表示扫到 Ai ,当前纵截面有 j 个线头,截止到现在,路线总长为 k ,有 d 个边界确定了的方案数。

转移:

  1. 新建一个线头:

    fi+1,j+1,k+(Ai+1Ai)(2jd),d(j+1d)fi,j,k,d

    解释两个地方:

    1. 总共有 j 个线头,每个线头两个端点,但有 d 个端点被封了,所以路径延长的总数是 (Ai+1Ai)(2jd) ,下面的同理。
    2. 关于转移系数 j+1d 即是新建的线头可以插在任意两个相邻的线头中间或者最上面和最下面,总共 j+1d 个可以插的位置,但每确定一个边界会少一个可以插的位置。
  2. 合并两个线头:

    fi+1,j1,k+(Ai+1Ai)(2jd),d(j1)fi,j,k,d(j>1)

    显然需要至少两个线头才能合并,选择相邻两个线头合并,所以总共有 j1 种方式。

  3. 不改变:

    fi+1,j,k+(Ai+1Ai)(2jd),d(2jd)fi,j,k,d

    Ai+1 对线头的形态不产生影响,它只需要放在 2jd 个端点处的其中一个即可。

  4. 作为其中一个边界:

    1. 往左走:

      fi+1,j,k+(Ai+1Ai)(2jd),d+1(2d)fi,j,k,d

    2. 往右走:

    fi+1,j+1,k+(Ai+1Ai)(2jd),d+1(2d)fi,j,k,d

边界:f0,0,0,0=1

答案:k=0Lfn,1,k,2

特判:n=1 时需要特判,因为上述转移中默认两个最终路线的端点不同。

时间复杂度:O(n2L)

代码

#include <bits/stdc++.h>
#define re(i, x, y) for (int i = (x); i < (y); ++i) 
#define rep(i, x, y) for (int i = (x); i <= (y); ++i) 
#define repp(i, x, y, d) for (int i = (x); i <= (y); i += (d)) 
#define reep(i, x, y) for (int i = (x); i <= (y); i <<= 1) 
#define pe(i, x, y) for (int i = (x) - 1; i >= (y); --i) 
#define per(i, x, y) for (int i = (x); i >= (y); --i) 
#define rep_e(i, u) for (int i = head[(u)]; i; i = e[i].nxt) 
#define vi vector<int> 
#define vL vector<LL>
#define vii vector<pii> 
#define viL vector<piL>
#define vLi vector<pLi> 
#define vLL vector<pLL>
#define eb emplace_back
#define pb pop_back
#define mp make_pair
#define pii pair<int, int>
#define piL pair<int, LL>
#define pLi pair<LL, int>
#define pLL pair<LL, LL>
#define lowbit(x) ((x) & (-(x)))
#define fi first
#define se second
#define all(x) x.begin(), x.end()
#define debug(x) cout << #x << " = " << x << endl
using namespace std;
typedef unsigned int ui;
typedef long long LL;
typedef unsigned long long ULL;
typedef double db;
#define getchar() (SB == TB && (TB = (SB = BB) + fread(BB, 1, 1 << 16, stdin), SB == TB) ? EOF : *SB++)
char BB[1 << 16], *SB = BB, *TB = BB;
template<typename T> void read(T &n) {
    T w = 1;
    n = 0;
    char ch = getchar();
    for ( ; !isdigit(ch); ch = getchar()) {
        if (ch == '-') {
            w = -1;
        }
    }
    for ( ; isdigit(ch); ch = getchar()) {
        n = n * 10 + (ch & 15);
    }
    n *= w;
}
template<typename T> void chkmn(T &a, const T &b) { 
    a = a > b ? b : a; 
}
template<typename T> void chkmx(T &a, const T &b) { 
    a = a < b ? b : a;  
}

const int MOD = 1e9 + 7;
int adt(const LL &a) { 
    return (a % MOD + MOD) % MOD; 
} 
int inc(const int &a, const int &b) { 
    return a + b >= MOD ? a + b - MOD : a + b; 
}
int dec(const int &a, const int &b) { 
    return a - b < 0 ? a - b + MOD : a - b; 
}
int mul(const int &a, const int &b) { 
    return 1LL * a * b % MOD; 
}
int sqr(const int &a) { 
    return 1LL * a * a % MOD; 
}
void Adt(LL &a) {
    a = (a % MOD + MOD) % MOD;
}
void Inc(int &a, const int &b) { 
    a = a + b >= MOD ? a + b - MOD : a + b; 
}
void Dec(int &a, const int &b) { 
    a = a - b < 0 ? a - b + MOD : a - b; 
}
void Mul(int &a, const int &b) { 
    a = 1LL * a * b % MOD; 
}
void Sqr(int &a) { 
    a = 1LL * a * a % MOD;
}
int fsp(int a, int x = MOD - 2) {
    int res = 1;
    for ( ; x; x >>= 1, Sqr(a)) {
        if (x & 1) {
            Mul(res, a);
        }
    }
    return res;
}

int n, L;
int a[105], f[2][105][1005][3];

int main() {
#ifdef sword 
    freopen("test.in", "r", stdin);
#endif
    
    read(n), read(L);
    rep(i, 1, n) {
        read(a[i]);
    }
    if (n == 1) {
        puts("1");
        return 0;
    }
    sort(a + 1, a + n + 1);
    int now = 1, las = 0;
    f[1][0][0][0] = 1;
    rep(i, 1, n) {
        swap(now, las);
        memset(f[now], 0, sizeof(f[now]));
        rep(j, 0, n) {
            rep(k, 0, L) {
                rep(t, 0, 2 * (j != 0)) {
                    int nk = k + (a[i] - a[i - 1]) * (j * 2 - t);
                    if (nk <= L && f[las][j][k][t]) {
                        const int x = f[las][j][k][t];
                        if (t < 2) {
                            if (j) { 
                                Inc(f[now][j][nk][t + 1], mul(2 - t, x));
                            }
                            Inc(f[now][j + 1][nk][t + 1], mul(2 - t, x));
                        }
                        Inc(f[now][j][nk][t], mul(j * 2 - t, x));
                        Inc(f[now][j + 1][nk][t], mul(j + 1 - t, x));
                        if (j) {
                            Inc(f[now][j - 1][nk][t], mul(j - 1, x));
                        }
                    }
                }
            }
        }
    }
    int ans = 0;
    rep(k, 0, L) {
        Inc(ans, f[now][1][k][2]);
    }
    printf("%d\n", ans);
    return 0;
}
posted @   Sword_K  阅读(117)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示