题意

在一个长度为$n$的排列中选择$k$个数,使得$S=k_1+k_2+\dots+k_n$。

思路

首先,这是一道构造题。

然后,思考怎么样才能凑出来$k$个数,使其和为$S$。

先考虑不合法情况。

最大能凑出的数为$n+(n-1)+\dots+(n-k+1)$;

最小能凑出的数为$1+2+\dots+k$;

考虑最大值和最小值中间的数字,是否所有的数字都能被凑出来?

尝试了一下$n=5,k=3$的情况,$minn=6, maxx=12$,有:

  • $1+2+3=6$
  • $1+2+4=7$
  • $1+2+5=8$
  • $1+3+5=9$
  • $1+4+5=10$
  • $2+4+5=11$
  • $3+4+5=12$

可以发现,存在一种构造方式,能够表示出最大最小值中间所有的数字。

这种构造方法具体为:

  1. 先找出最小值对应的序列,如$(1,2,3)$,求和为$6$
  2. 再找出最大值对应的序列,如$(3,4,5)$,求和为$12$
  3. 也就是说,每个数字最大的变化为$(12-6)/3=2$
  4. 我们从原先较大的数$(3)$开始做加法,加到这个数字的极限值为止$(3 + 2) = 5$
  5. 以此类推,就能够$O(n)$构造出符合要求的答案

在构造出这$k$个数后,可以新建一个$used$数组,标记哪些数字被使用过,以帮助我们输出结果。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <bits/stdc++.h>
#define maxn 100005
using namespace std;
int t, n, l, r, s, ans[maxn], used[maxn];
int main(void)
{
scanf("%d", &t);
while(t--)
{
scanf("%d %d %d %d", &n, &l, &r, &s);
for(int i = 1; i <= n; i++) ans[i] = used[i] = 0;
int minn = 0, maxx = 0, num = r - l + 1;
for(int i = 1; i <= num; i++)
minn += i;
for(int i = n; i >= n - num + 1; i--)
maxx += i;
if(s >= minn && s <= maxx)
{
int div = (maxx - minn) / num;
for(int i = l; i <= r; i++)
ans[i] = i - l + 1;
s -= minn;
int pos = r;
while(s)
{
if(s > div)
{
ans[pos] += div;
s -= div;
pos--;
}
else
{
ans[pos] += s;
s = 0;
}
}
for(int i = l; i <= r; i++)
used[ans[i]] = 1;
for(int i = 1; i <= n; i++)
{
if(ans[i]) continue;
for(int j = 1; j <= n; j++)
{
if(used[j] == 0)
{
ans[i] = j;
used[j] = 1;
break;
}
}
}
for(int i = 1; i <= n; i++)
printf(i == n ? "%d\n" : "%d ", ans[i]);
}
else
{
printf("-1\n");
}
}
return 0;
}

后来,我在题解评论区看到有大佬(amar277)使用动态规划来解决问题。

我尝试了一下,这个动态规划还是比较需要剪枝的,否则很容易$TLE$最后一个点。

设$dp[n][m]$为,当前有$n$个元素,总和为$m$,$-1$代表未访问过,$0$代表不合法,$1$代表存在合法情况。

转移使用记忆化搜索,带三个参数$(now,num,sum)$:

$now$ :当前我可以选择使用的数字;

$num$:当前还剩多少个数字需要选择;

$sum$:当前还剩多少$S$需要被凑出来;

分为两个操作:

一个是$dfs(now-1,num, sum)$,代表什么也不干,使用$now-1$这个数字再开始决策。

另一个是$dfs(now-1,num-1,sum-now)$,代表选取当前的数字$now$。

然后,利用一个全局变量,存放答案,最后用与上面类似的处理方法输出答案即可。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <bits/stdc++.h>
#define maxn 505
using namespace std;
int t, n, l, r, s;
int ans[maxn], used[maxn];
int stk[maxn], top;
int dp[maxn][maxn * maxn / 2];
int dfs(int now, int num, int sum){
//printf("debug: %d %d %d\n", now, num, sum);
if(num == 0 && sum == 0)
return dp[num][sum] = 1;
else if(now == 0 || num == 0 || sum == 0 ||
sum > now * (now + 1) / 2 || now < num)
return 0;

if(~dp[num][sum])
return dp[num][sum];

//printf("gg");
int res = dfs(now - 1, num, sum);
if(res) return dp[num][sum] = res;
stk[++top] = now;
res = dfs(now - 1, num - 1, sum - now);
if(res) return dp[num][sum] = res;
top--;
return dp[num][sum] = res;
}
int main(void)
{
scanf("%d", &t);
while(t--)
{
top = 0;
scanf("%d %d %d %d", &n, &l, &r, &s);
for(int i = 0; i <= n; i++)
{
ans[i] = used[i] = stk[i] = 0;
for(int j = 0; j <= n * (n + 1) / 2; j++)
dp[i][j] = -1;
}
dfs(n, r - l + 1, s);
if(top == 0)
{
printf("-1\n");
continue;
}
for(int i = l; i <= r; i++)
{
ans[i] = stk[i - l + 1];
used[ans[i]] = 1;
}
for(int i = 1; i <= n; i++)
{
if(ans[i]) continue;
for(int j = 1; j <= n; j++)
{
if(used[j]) continue;
ans[i] = j;
used[j] = 1;
break;
}
}
for(int i = 1; i <= n; i++)
printf(i == n ? "%d\n" : "%d ", ans[i]);
}
return 0;
}

End