一份重要文件被撕成两半,其中一半还被送进了碎纸机。我们将碎纸机里找到的纸条进行编号,如图 1 所示。然后根据断口的折线形状跟没有切碎的半张纸进行匹配,最后还原成图 2 的样子。要求你输出还原后纸条的正确拼接顺序。
图1 纸条编号
图2 还原结果
输入格式:
输入首先在第一行中给出一个正整数 N(1<N≤10^5),为没有切碎的半张纸上断口折线角点的个数;随后一行给出从左到右 N 个折线角点的高度值(均为不超过 100 的非负整数)。
随后一行给出一个正整数 M(≤100),为碎纸机里的纸条数量。接下去有 M 行,其中第 i 行给出编号为 i(1≤i≤M)的纸条的断口信息,格式为:
K h[1] h[2] ... h[K]
其中 K
是断口折线角点的个数(不超过 10^4+1),后面是从左到右 K
个折线角点的高度值。为简单起见,这个“高度”跟没有切碎的半张纸上断口折线角点的高度是一致的。
输出格式:
在一行中输出还原后纸条的正确拼接顺序。纸条编号间以一个空格分隔,行首尾不得有多余空格。
题目数据保证存在唯一解。
输入样例:
17
95 70 80 97 97 68 58 58 80 72 88 81 81 68 68 60 80
6
4 68 58 58 80
3 81 68 68
3 95 70 80
3 68 60 80
5 80 72 88 81 81
4 80 97 97 68
输出样例:
3 6 1 5 2 4
题意: 如图所示,给出一张纸被分割的状态,以及各纸条上折点坐标,求拼出这张纸的唯一方案。
分析: 题目中很重要的信息是该题存在唯一解,因此也就不存在下面这种情况
9
1 2 3 4 3 2 1 2 3
3
3 1 2 3
4 1 2 3 4
4 4 3 2 1
在放第一条纸带时既可以选1 2 3,也可以选1 2 3 4, 此时产生了不唯一的选法,不符合题意。这样就可以直接枚举区间哈希值,一旦遇到了第一个符合的就说明它一定位于这里,不会放一个以它为前缀的更长的纸带。
最后还需要注意用完纸带后要置mp[hash]为0,表示不可以再用这条纸带了,否则遇到这样的数据就会出错
8
1 2 1 3 1 2 1 4
3
3 1 2 1
3 1 3 1
4 1 2 1 4
具体代码如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <string>
#include <unordered_map>
using namespace std;
int n, a[100005], m;
unsigned long long h[100005], p[100005];
const int P = 233;
unordered_map<unsigned long long, int> mp;
signed main()
{
cin >> n;
p[0] = 1;
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
h[i] = h[i-1]*P+a[i];
p[i] = p[i-1]*P;
}
cin >> m;
for(int i = 1; i <= m; i++){
int num;
scanf("%d", &num);
unsigned long long hash = 0;
for(int j = 1; j <= num; j++){
int t;
scanf("%d", &t);
hash = hash*P+t;
}
mp[hash] = i;
}
bool flag = false;
for(int l = 1; l <= n; ){
for(int r = l; r <= n; r++){
unsigned long long hash = h[r]-h[l-1]*p[r-l+1];
if(mp[hash]){
if(!flag){
flag = true;
printf("%d", mp[hash]);
}
else
printf(" %d", mp[hash]);
l = r;
mp[hash] = 0;
break;
}
}
if(l == n)
break;
}
return 0;
}