题目大意
给定 n n n 个整数,如果为正则表示他知道前面的编号,否则不知道,要求第 p o s pos pos 个人的所有可能位置。
思路
我们可以根据已有的信息将这些人先排成一个一个确定的小队伍。
考虑选择一种最终的排法:选择那个人所在的队伍,再选择一些其它小队伍(或不选择)排在这个小队伍的前面,那么的位置便是之前的小队伍的总人数在他的小队伍中的位置。
最后使用背包来求出那个人前面所有可能的人数即可,参考如下:
f[0] = 1; // f 数组表示下标可不可以成为答案
// 前面没有队伍肯定可以
for (int i = 0; i < v.size(); ++i) {
if (v[i].size()) {
for (int j = n; j >= v[i].size(); --j) { // 倒着枚举,避免前面的修改影响到后面
if (f[j - v[i].size()]) { // 如果没有这一队可以,那么加上这一队也可以
f[j] = 1;
}
}
}
}
代码
#include <bits/stdc++.h>
using namespace std;
vector<int> vec[1005];
int pre[1005]; // 前面的人
bool vis[1005]; // 是否考虑过每一队
vector<vector<int>> v; // 队集
void add(int x, int y) {
for (auto it : vec[y]) {
vec[x].push_back(it);
}
}
void gx(int x) {
if (vec[x].size()) return;
if (pre[x]) {
gx(pre[x]);
add(x, pre[x]);
}
vec[x].push_back(x);
}
bool search(int no, int x) {
for (auto it : vec[no]) {
if (it == x) return 1; // 找到了
}
return 0; // 找不到
}
int f[1005];
int main() {
int n, x;
cin >> n >> x;
for (int i = 1; i <= n; ++i) {
cin >> pre[i];
}
for (int i = 1; i <= n; ++i) {
gx(i); // 预处理出以 i 结尾的一条已知连续的序列
}
for (int i = 1; i <= n; ++i) {
if (vis[i]) continue;
int no = i, max_size = vec[i].size();
for (int j = 1; j <= n; ++j) {
if (vis[j]) continue;
if (search(j, i)) { // vec[i] 是 vec[j] 的子序列
if (vec[j].size() > max_size) { // 找出包含 vec[i] 的最长序列
no = j;
max_size = vec[j].size();
}
}
}
for (int j = 0; j < vec[no].size(); ++j) {
vis[vec[no][j]] = true; // 考虑过了它,再考虑没有意义
}
v.push_back(vec[no]); // 将最长序列加入序列集
}
int fst, snd;
for (int i = 0; i < v.size(); ++i) { // 确定 x 的位置
bool cmp = 0;
for (int j = 0; j < v[i].size(); ++j) {
if (v[i][j] == x) {
fst = i; // first -> i
snd = j; // second -> j
cmp = 1; // 找到了
break;
}
}
if (cmp) break; // 不做多余运算
}
vector<int>().swap(v[fst]);
f[0] = 1; // f 数组表示下标可不可以成为答案
for (int i = 0; i < v.size(); ++i) {
if (v[i].size()) {
for (int j = n; j >= v[i].size(); --j) { // 倒着枚举,避免前面的修改影响到后面
if (f[j - v[i].size()]) {
f[j] = 1;
}
}
}
}
for (int i = 0; i <= n; ++i) {
if (f[i]) {
cout << i + snd + 1 << endl; // 输出结果
}
}
return 0;
}