#P2676. 第1题-小苯排列数组
-
1000ms
Tried: 107
Accepted: 32
Difficulty: 4
所属公司 :
阿里
时间 :2025年3月9日-阿里云(开发岗)
-
算法标签>动态规划
第1题-小苯排列数组
题面描述
给定一个长度为 n 的数组 a,其中每个元素 ai 满足 1≤ai≤n。定义一个子序列的权值如下:
- 如果这个子序列不是严格递增的,那么它的权值为 0。
- 如果这个子序列是严格递增的,并且恰好是一个“排列子序列”(即:子序列本身的元素恰好构成 {1,2,…,k} 的一个排列,且由于必须严格递增,因此只能是 [1,2,…,k] 的形式),那么它的权值定义为该子序列的长度 k。
现在,我们要计算:数组 a 中,所有满足上述“排列子序列”条件的子序列,它们的权值之和。最后结果需对 109+7 取模。
思路与问题本质分析
问题本质
-
什么是“排列子序列”?
若子序列长度为 k,要满足它能表示为 {1,2,…,k} 的某个排列。但题目额外要求子序列必须严格递增,因此这样的子序列只能是 严格递增的 [1,2,…,k]。也就是说,一个子序列想要贡献权值 k,它的内容必须正好是按顺序出现的 1,2,…,k。 -
如何统计?
要数清数组 a 中,出现了多少次子序列 [1,2,…,k]。子序列不要求连续,但顺序不能乱:- 先从 a 中某些位置选出一个“1”,
- 再从它之后的某些位置选一个“2”,
- 再选“3”,以此类推,直到“k”。
只要能按顺序找出 1,2,…,k 这些值,我们就得到一个有效的子序列 [1,2,…,k]。
-
动态规划思路
我们定义一个 DP 数组 dp[x],表示在读到当前位置之前,能形成子序列 [1,2,…,x] 的总个数。我们从左到右遍历数组 a 的每个元素 v:- 如果 v=1,那么就可以新增一种只包含“1”的子序列,所以 dp[1] 要加 1。
- 如果 v>1,那么它可以把所有 [1,2,…,v−1] 的子序列继续扩展出一个 [1,2,…,v]。因此 dp[v]+=dp[v−1]。
- 每一步都对结果取模,以免溢出。
遍历结束后,dp[x] 就是数组中能找到的子序列 [1,2,…,x] 的数量。然后权值和就是
∑x=1nx×dp[x].
最终再对 109+7 取模。
cpp
#include <bits/stdc++.h>
using namespace std;
static const long long MOD = 1000000007;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T; // 测试组数
cin >> T;
while(T--){
int n;
cin >> n;
vector<int> a(n);
for(int i = 0; i < n; i++){
cin >> a[i];
}
// dp[x] 表示子序列 [1,2,...,x] 的个数
vector<long long> dp(n+1, 0LL);
for(int i = 0; i < n; i++){
int v = a[i];
if(v == 1){
// 可以新开一个子序列,只含 "1"
dp[1] = (dp[1] + 1) % MOD;
} else {
// 扩展之前的 [1,2,...,v-1] 子序列
// 注意这里 v > 1
dp[v] = (dp[v] + dp[v-1]) % MOD;
}
}
// 计算权值总和
long long ans = 0;
for(int x = 1; x <= n; x++){
ans = (ans + x * dp[x]) % MOD;
}
cout << ans << "\n";
}
return 0;
}
python
MOD = 10**9 + 7
def main():
import sys
input = sys.stdin.read
data = input().split()
idx = 0
T = int(data[idx]) # 测试组数
idx += 1
for _ in range(T):
n = int(data[idx]) # 数组长度
idx += 1
a = list(map(int, data[idx:idx + n])) # 数组 a
idx += n
# dp[x] 表示子序列 [1,2,...,x] 的个数
dp = [0] * (n + 1)
for v in a:
if v == 1:
# 可以新开一个子序列,只含 "1"
dp[1] = (dp[1] + 1) % MOD
else:
# 扩展之前的 [1,2,...,v-1] 子序列
# 注意这里 v > 1
dp[v] = (dp[v] + dp[v - 1]) % MOD
# 计算权值总和
ans = 0
for x in range(1, n + 1):
ans = (ans + x * dp[x]) % MOD
print(ans)
if __name__ == "__main__":
main()
java
import java.util.*;
import java.io.*;
public class Main {
static final long MOD = 1000000007;
public static void main(String[] args) throws IOException {
// 为了快速输入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringBuilder sb = new StringBuilder();
int T = Integer.parseInt(br.readLine().trim()); // 测试组数
while(T-- > 0){
int n = Integer.parseInt(br.readLine().trim());
String[] tokens = br.readLine().trim().split(" ");
int[] a = new int[n];
for(int i = 0; i < n; i++){
a[i] = Integer.parseInt(tokens[i]);
}
long[] dp = new long[n+1];
// dp[x] 表示子序列 [1,2,...,x] 的个数
for(int i = 0; i < n; i++){
int v = a[i];
if(v == 1){
// 新的子序列仅包含 "1"
dp[1] = (dp[1] + 1) % MOD;
} else {
if(v <= n){
dp[v] = (dp[v] + dp[v-1]) % MOD;
}
}
}
long ans = 0;
for(int x = 1; x <= n; x++){
ans = (ans + x * dp[x]) % MOD;
}
sb.append(ans).append("\n");
}
// 输出所有结果
System.out.print(sb);
}
}
题目内容
小苯定义一个排列的权值为:
-
如果排列不满足严格上升,则权值为0。
-
否则,严格上升的排列其权值为:排列的长度。
现在小苯有个长为n的a数组,他想知道a中所有“排列”子序列 (即:此子序列是一个排列)的权值之和,请你帮他算一算吧。
输入描述
每个测试文件内都包含多组测试数据。
第一行一个正整数 T(1<T<100),表示测试数据的组数
接下来对于每组测试数据,输入包含两行。
第一行一个正整数n(1≤n≤2×105),表示数组a的长度。
第二行n个整数ai(1≤ai≤n),表示数组a。
(保证所有测试数据中n的总和不超过3×105。)
输出描述
输出T行,每行一个整数表示当前测试用例的答案,即:所有“排列”子序列的权值之和。
(注意:由于答案可能很大,因此你只要输出答案对1000000007取模的值即可。)
样例1
输入
1
3
1 1 2
输出
6
说明
所有的“排列”子序列有:
{1},{1},{1,2},{1,2}权值求和为6。