1.分糖果
算法分析
糖果总数k属于 [ l , r ] [l, r] [l,r],其中 l > = n l >= n l>=n,每个小朋友最少会分到 ⌊ l / n ⌋ \lfloor l / n \rfloor ⌊l/n⌋块糖果。分完这 ⌊ l / n ⌋ \lfloor l / n \rfloor ⌊l/n⌋块糖果后,还剩余 [ l − n ∗ ⌊ l / n ⌋ , r − n ∗ ⌊ l / n ⌋ ] [l - n * \lfloor l / n \rfloor, r - n * \lfloor l / n \rfloor] [l−n∗⌊l/n⌋,r−n∗⌊l/n⌋]块糖果。奖励的糖果数量最多为 n − 1 n - 1 n−1块,如果 n − 1 < = r n - 1 <= r n−1<=r,则 k k k可以取值为 n ∗ ⌊ l / n ⌋ + n − 1 n * \lfloor l / n \rfloor + n - 1 n∗⌊l/n⌋+n−1,奖励糖果数量为 n − 1 n - 1 n−1块。否则 k k k取到 r r r,奖励糖果数量为 r r r块。
由于数据范围太大 2 < = n < = l < = r < = 1 0 9 2 <= n <= l <= r <= 10^9 2<=n<=l<=r<=109,直接枚举 k k k,然后取 k k % n k的最大值,当 k % n = = n − 1 k \% n == n - 1 k%n==n−1时退出,有些点会造成超时。如 n = 5 ∗ 1 0 8 , l = n + 2 , r = n + 8 ∗ 1 0 8 n = 5 * 10^8,l = n + 2, r = n + 8 * 10^8 n=5∗108,l=n+2,r=n+8∗108。直接枚举官方数据卡1个点。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
int n, L, R;
scanf("%d%d%d", &n, &L, &R);
int t = L / n;
L = L - t * n;
R = R - t * n;
if (n - 1 <= R) printf("%d\n", n - 1);
else printf("%d\n", R);
return 0;
}
算法拓展
如果 l % n l\%n l%n和 r % n r\%n r%n相等,说明 l l l和 r r r之间的距离小于 n n n,无论怎么分,小朋友只能分到 l % n l\%n l%n块糖果,最多奖励糖果数量为 r % n r\%n r%n块。如果 l % n l\%n l%n和 r % n r\%n r%n不相等,说明两者的距离大于等于 n n n,奖励糖果数量可以取到最大值 n − 1 n-1 n−1。
2.插入排序
算法分析
对于相同的数据项,要保持原有的顺序。因此,排序要注意其稳定性。 s o r t sort sort排序不稳定,要用题目描述的插入排序。对于每个数据项开结构体,记录值和位置,数组 n u m [ i ] = k num[i] = k num[i]=k表示原位置为 i i i的数据项排序后的位置为k。在查询的时候直接调用 n u m [ x ] num[x] num[x]即可。难点在于更新。
最多更新5000次,每次更新后都插排一遍,总体时间复杂度为
O
(
5000
n
2
+
Q
−
5000
)
O(5000n^2 + Q - 5000)
O(5000n2+Q−5000),超时。注意到在第一次插排之后,每次只更新一个数据项,没有必要重新花费
O
(
n
2
)
O(n^2)
O(n2)的代价排序。更新的数据要么变大了,要么变小了。如果变大了,说明其应该向后进行邻项交换,如果变小了,则向前进行邻项交换。对于相同的元素,我们要保证其原先的顺序,交换条件可设为:
a
[
j
]
.
d
a
t
a
<
a
[
j
−
1
]
.
d
a
t
a
∣
∣
(
a
[
j
]
.
d
a
t
a
=
=
a
[
j
−
1
]
.
d
a
t
a
&
&
a
[
j
]
.
p
o
s
<
a
[
j
−
1
]
.
p
o
s
a[j].data < a[j-1].data || (a[j].data == a[j-1].data \&\& a[j].pos < a[j-1].pos
a[j].data<a[j−1].data∣∣(a[j].data==a[j−1].data&&a[j].pos<a[j−1].pos
除了第一次用快排外,其余每次更新 O ( n ) O(n) O(n),整体复杂度 O ( n 2 + 5000 n + Q − 5000 ) O(n^2 + 5000n+ Q - 5000) O(n2+5000n+Q−5000)。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
struct node
{
int data, pos;
}a[8010];
int num[8010]; // num[i]表示原位置为i的元素排序后的位置
void sinssort(int n) // 由小到大
{
for (int i = 1; i <= n; ++i)
for (int j = i; j >= 2; --j)
if (a[j].data < a[j-1].data)
{
num[a[j].pos] = j - 1;
num[a[j-1].pos] = j;
node t = a[j]; a[j] = a[j-1]; a[j-1] = t;
}else break;
}
void schange(int x, int v, int n)
{
int val = a[num[x]].data; // 原值
a[num[x]].data = v;
if (v >= val)
{
for (int j = num[x] + 1; j <= n; ++j)
if (a[j].data < a[j-1].data || (a[j].data == a[j-1].data && a[j].pos < a[j-1].pos))
{
num[a[j].pos] = j - 1;
num[a[j-1].pos] = j;
node t = a[j]; a[j] = a[j-1]; a[j-1] = t;
}else break;
}else
{
for (int j = num[x]; j >= 2; --j)
if (a[j].data < a[j-1].data || (a[j].data == a[j-1].data && a[j].pos < a[j-1].pos))
{
num[a[j].pos] = j - 1;
num[a[j-1].pos] = j;
node t = a[j]; a[j] = a[j-1]; a[j-1] = t;
}else break;
}
}
int main()
{
int n, q; scanf("%d%d", &n, &q);
for (int i = 1; i <= n; ++i)
{
scanf("%d", &a[i].data);
a[i].pos = i;
num[i] = i;
}
sinssort(n);
int c, x, v;
while (q--)
{
scanf("%d%d", &c, &x);
if (c == 1)
{
scanf("%d", &v);
schange(x, v, n);
}else
{
printf("%d\n", num[x]);
}
}
return 0;
}
3.网络连接
算法分析
模拟题,思路简单,但代码不好实现。服务器建立连接,客户机加入连接,都需要比对服务器的地址串。因为数据规模 n < = 1000 n <= 1000 n<=1000,可以直接用二维数组存储服务器地址串,用字符串的 s t r c m p strcmp strcmp逐项比对。然后根据提供的输出规则判断输出就好。难点在于判断地址串的合法性。
一个合法的字符串行如 a . b . c . d : e a.b.c.d:e a.b.c.d:e,字符串长度小于等于25。以下几种形式非法,题面提供的非法情况不够全面,算是一个坑,得自己补全。
1.得有3个点和1个冒号,否则非法。
2.三个点在前,冒号在后,否则非法。
3.符号之间必须有数字,否则非法。如:192.3…4:606非法。
4.
a
、
b
、
c
、
d
、
e
a、b、c、d、e
a、b、c、d、e得在范围之内,否则非法。
5.数字不能有前导0,否则非法。但是判断的时候得注意为0的情况,如192.168.0.2:78,很容易误判断为前导0。
可以用 p o s pos pos数组记录点和冒号的位置,后续方便统计符号的个数和组数。有个细节。比如 1.1.1.1 : e 1.1.1.1:e 1.1.1.1:e, e e e最多有17位,超 i n t int int,得开 l o n g l o n g long long longlong。数据点卡这个。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
char sserver[1010][30];
int num[1010];
char dn[20], id[30];
int sjudeg(char s[])
{
int len = strlen(s);
int sum1 =0, sum2 = 0;
int pos[10], k = 0;
memset(pos, 0, sizeof(pos));
for (int i = 0; i < len; ++i)
{
if (s[i] == '.')
{
++sum1;
pos[++k] = i;
}else if (s[i] == ':')
{
++sum2;
pos[++k] = i;
if (sum1 != 3) return 0;
}
}
if (sum1 != 3 || sum2 != 1) return 0;
// 比如192.123..4:98这种情况
if (pos[1] == 0) return 0;
if (pos[2] -1 - pos[1] == 0) return 0;
if (pos[3] -1 - pos[2] == 0) return 0;
if (pos[4] -1 - pos[3] == 0) return 0;
if (len -1 - pos[4] == 0) return 0;
// 前导零
if (s[0] == '0' && pos[1] > 1) return 0;
if (s[pos[1]+1] == '0' && pos[2] -1 - pos[1] > 1) return 0;
if (s[pos[2]+1] == '0' && pos[3] -1 - pos[2] > 1) return 0;
if (s[pos[3]+1] == '0' && pos[4] -1 - pos[3] > 1) return 0;
if (s[pos[4]+1] == '0' && len -1 - pos[4] > 1) return 0;
// 不在范围内
long long a, b, c, d, e;
a = b = c = d = e = 0;
for (int i = 0; i < pos[1]; ++i) a = a * 10 + s[i] - '0';
for (int i = pos[1] + 1; i < pos[2]; ++i) b = b * 10 + s[i] - '0';
for (int i = pos[2] + 1; i < pos[3]; ++i) c = c * 10 + s[i] - '0';
for (int i = pos[3] + 1; i < pos[4]; ++i) d = d * 10 + s[i] - '0';
for (int i = pos[4] + 1; i < len; ++i) e = e * 10 + s[i] - '0';
if (!(a >= 0 && a <= 255)) return 0;
if (!(b >= 0 && b <= 255)) return 0;
if (!(c >= 0 && c <= 255)) return 0;
if (!(d >= 0 && d <= 255)) return 0;
if (!(e >= 0 && e <= 65535)) return 0;
return 1;
}
int tot = 0; // 服务机台数
int sfind(char s[])
{
for (int i = 1; i <= tot; ++i)
if (strcmp(sserver[i], s) == 0) return num[i];
return 0;
}
void sadd(char s[], int i)
{
++tot;
int len = strlen(s);
for (int i = 0; i < len; ++i) sserver[tot][i] = s[i];
num[tot] = i;
}
int main()
{
int n; scanf("%d", &n);
for (int i = 1; i <= n; ++i)
{
scanf("%s%s", dn, id);
if (dn[0] == 'S')
{
if (!sjudeg(id)) printf("ERR\n");
else
{
if (sfind(id)) printf("FAIL\n");
else
{
sadd(id, i); // 加入这台服务机
printf("OK\n");
}
}
}else
{
if (!sjudeg(id)) printf("ERR\n");
else
{
int t = sfind(id);
if (t) printf("%d\n", t);
else printf("FAIL\n");
}
}
}
return 0;
}
服务器地址串也可以改用标记方法。设 P = 2887777 P = 2887777 P=2887777,将地址串的数字从左到右看成10进制数,然后 % P \% P %P,结果用 v i s vis vis标记。最多1000个地址串,冲突的可能性很小。空间换时间,理论上更快一些。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int P = 2887777;
int vis[2887780], num[2887780];
char dn[20], id[30];
int sjudeg(char s[])
{
int len = strlen(s);
int sum1 =0, sum2 = 0;
int pos[10], k = 0;
memset(pos, 0, sizeof(pos));
for (int i = 0; i < len; ++i)
{
if (s[i] == '.')
{
++sum1;
pos[++k] = i;
}else if (s[i] == ':')
{
++sum2;
pos[++k] = i;
if (sum1 != 3) return 0;
}
}
if (sum1 != 3 || sum2 != 1) return 0;
// 比如192.123..4:98这种情况
if (pos[1] == 0) return 0;
if (pos[2] -1 - pos[1] == 0) return 0;
if (pos[3] -1 - pos[2] == 0) return 0;
if (pos[4] -1 - pos[3] == 0) return 0;
if (len -1 - pos[4] == 0) return 0;
// 前导零
if (s[0] == '0' && pos[1] > 1) return 0;
if (s[pos[1]+1] == '0' && pos[2] -1 - pos[1] > 1) return 0;
if (s[pos[2]+1] == '0' && pos[3] -1 - pos[2] > 1) return 0;
if (s[pos[3]+1] == '0' && pos[4] -1 - pos[3] > 1) return 0;
if (s[pos[4]+1] == '0' && len -1 - pos[4] > 1) return 0;
// 不在范围内
long long a, b, c, d, e;
a = b = c = d = e = 0;
for (int i = 0; i < pos[1]; ++i) a = a * 10 + s[i] - '0';
for (int i = pos[1] + 1; i < pos[2]; ++i) b = b * 10 + s[i] - '0';
for (int i = pos[2] + 1; i < pos[3]; ++i) c = c * 10 + s[i] - '0';
for (int i = pos[3] + 1; i < pos[4]; ++i) d = d * 10 + s[i] - '0';
for (int i = pos[4] + 1; i < len; ++i) e = e * 10 + s[i] - '0';
if (!(a >= 0 && a <= 255)) return 0;
if (!(b >= 0 && b <= 255)) return 0;
if (!(c >= 0 && c <= 255)) return 0;
if (!(d >= 0 && d <= 255)) return 0;
if (!(e >= 0 && e <= 65535)) return 0;
return 1;
}
int shash(char s[])
{
int len = strlen(s);
int sum = 0;
for (int i = 0; i < len; ++i)
if (s[i] >= '0' && s[i] <= '9') sum = (sum * 10 + s[i] - '0') % P;
return sum;
}
int main()
{
int n; scanf("%d", &n);
for (int i = 1; i <= n; ++i)
{
scanf("%s%s", dn, id);
if (dn[0] == 'S')
{
if (!sjudeg(id)) printf("ERR\n");
else
{
int t = shash(id);
if (!vis[t])
{
printf("OK\n");
num[t] = i;
vis[t] = 1;
}
else printf("FAIL\n");
}
}else
{
if (!sjudeg(id)) printf("ERR\n");
else
{
int t = shash(id);
if (vis[t]) printf("%d\n", num[t]);
else printf("FAIL\n");
}
}
}
return 0;
}
算法拓展
1.判断地址串是否合法。 s s c a n f ( s t r , " 格式符 " , 变量地址列表 ) sscanf(str, "格式符", 变量地址列表) sscanf(str,"格式符",变量地址列表),从字符串 s t r str str中读取数据存到变量中,返回值是成功读取的数据个数。 s p r i n t f ( s t r , " 格式符 " , 变量列表 ) sprintf(str, "格式符", 变量列表) sprintf(str,"格式符",变量列表),将变量数据输出存储到字符串 s t r str str中,返回值是成功输出的数据个数。头文件 c s t d i o cstdio cstdio。
按照地址串的标准格式 a . b . c . d : e a.b.c.d:e a.b.c.d:e用 s s c a n f sscanf sscanf读入,存到变量 a 、 b 、 c 、 d 、 e a、b、c、d、e a、b、c、d、e中,变量用 l o n g l o n g long \, long longlong,返回值为 t t t。如果 t t t不等于5,则非法。然后判断变量 a 、 b 、 c 、 d 、 e a、b、c、d、e a、b、c、d、e的取值范围是否合法。然后把变量 a 、 b 、 c 、 d 、 e a、b、c、d、e a、b、c、d、e按照标准格式组成地址串,判断和原地址串是否一样即可。也可以照顾到前导0的问题。
2.标记地址串。除了上述题解的标记方法,还可以用
m
a
p
map
map或
u
n
o
r
d
e
r
e
d
_
m
a
p
unordered\_map
unordered_map。
m
a
p
map
map是自动有序的,基于红黑树实现,查询复杂度为
l
o
g
(
n
)
log(n)
log(n)。
u
n
o
r
d
e
r
e
d
_
m
a
p
unordered\_map
unordered_map的实现基于哈希表,查询的时间复杂度平均为
O
(
1
)
O(1)
O(1),是
C
+
+
11
C++11
C++11标准,本地如果无法编译需要加相应编译选项
−
s
t
d
=
c
+
+
11
-std=c++11
−std=c++11。头文件为
u
n
o
r
d
e
r
e
d
_
m
a
p
unordered\_map
unordered_map。
u
n
o
r
d
e
r
e
d
_
m
a
p
<
s
t
r
i
n
g
,
i
n
t
>
m
unordered\_map<string,int> m
unordered_map<string,int>m
m a p map map和 u n o r d e r e d _ m a p unordered\_map unordered_map并无好坏之分,它们都有各自应用的场景。在需要元素有序性或者对单次查询性能要求较为敏感时,请使用 m a p map map,其余情况下应使用 u n o r d e r e d m a p unordered_map unorderedmap。
4.小熊的果篮
算法分析
对于一个序列,涉及到删除数据项问题,容易想到用链表维护。数组模拟双向链表。朴素模拟的话就是输出一个删除一个,时间复杂度不好推算,最坏的情况下是所有数据项都一样为 O ( n 2 ) O(n^2) O(n2)。有一个很明显的优化,对于相同的块,可以用一个数据结构维护,从一个块的第一个数据项,迅速跳到下一个块的第一个数据项。如果两个块相同了,还可以合并。也可以用数组模拟链表维护。
l i s 1 lis1 lis1维护所有的水果, l i s 2 lis2 lis2维护的是相同的水果块。两个链表用的是同一个编号,即相同编号的指向的是同一个水果。取完一轮后,对 l i s 2 lis2 lis2中能合并的块进行合并。合并的操作单独拿出来做,不要边取边合并,那样需要考虑的细节太多,容易出错。要注意模块化编程。
在 l i s 2 lis2 lis2中取。对于当前结点 p p p,直接输出 p − 2 p - 2 p−2,因为有头和尾结点,数据项编号从3开始的,所以减2。设 p 1 _ 2 = l i s 1 [ p ] . n e x t p1\_2 = lis1[p].next p1_2=lis1[p].next, p 2 _ 1 = l i s 2 [ p ] . p r e , p 2 _ 2 = l i s 2 [ p ] . n e x t p2\_1 = lis2[p].pre, p2\_2 = lis2[p].next p2_1=lis2[p].pre,p2_2=lis2[p].next。输出之后,先在 l i s 1 lis1 lis1中删除p结点。然后准备让 p 1 _ 2 p1\_2 p1_2结点插入到 l i s 2 lis2 lis2中原先 p p p的位置。这里有两种情况:
1. p 1 _ 2 = p 2 _ 2 p1\_2 = p2\_2 p1_2=p2_2,此时 p 1 _ 2 p1\_2 p1_2不能插入到 l i s 2 lis2 lis2中,在 l i s 2 lis2 lis2中直接删除 p p p点。然后令 p = p 2 _ 2 p = p2\_2 p=p2_2。
2. p 1 _ 2 ≠ p 2 _ 2 p1\_2 \neq p2\_2 p1_2=p2_2,此时让 p 1 _ 2 p1\_2 p1_2结点插入到 l i s 2 lis2 lis2中原先 p p p的位置。然后令 p = p 2 _ 2 p = p2\_2 p=p2_2。
p = lis1[shead].next;
while (lis2[p].next != 0)
{
printf("%d ", p - 2); ++cnt;
int p1_2 = lis1[p].next;
int p2_1 = lis2[p].pre, p2_2 = lis2[p].next;
sdel(p);
if (p1_2 == p2_2)
{
lis2[p2_1].next = p2_2;
lis2[p2_2].pre = p2_1;
p = p2_2;
}else
{
lis2[p1_2].data = lis1[p1_2].data;
lis2[p2_1].next = p1_2; lis2[p1_2].next = p2_2;
lis2[p2_2].pre = p1_2; lis2[p1_2].pre = p2_1;
p = p2_2;
}
}
取完后,再合并。
// 合并lis2中相同的块
p = lis2[shead].next;
while (lis2[p].next != 0)
{
if (lis2[p].data == lis2[lis2[p].pre].data)
{
int p1 = lis2[p].pre, p2 = lis2[p].next;
lis2[p1].next = p2; lis2[p2].pre = p1;
p = p2;
}else p = lis2[p].next;
}
完整代码:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
struct node
{
int data, next, pre;
}lis1[200010], lis2[200010];
int tot, shead, stail;
void sinit()
{
tot = 2;
shead = 1;
stail = 2;
lis1[shead].data = lis1[stail].data = -1;
lis1[shead].next = stail;
lis1[stail].pre = shead;
lis2[shead].data = lis2[stail].data = -1;
lis2[shead].next = stail;
lis2[stail].pre = shead;
}
void sadd1(int x)
{
++tot;
lis1[tot].data = x;
int p = lis1[stail].pre, q = stail;
lis1[p].next = tot; lis1[tot].next = q;
lis1[q].pre = tot; lis1[tot].pre = p;
}
void sadd2(int tot, int x)
{
lis2[tot].data = x;
int p = lis2[stail].pre, q = stail;
lis2[p].next = tot; lis2[tot].next = q;
lis2[q].pre = tot; lis2[tot].pre = p;
}
void sdel(int p)
{
int p1 = lis1[p].pre, p2 = lis1[p].next;
lis1[p1].next = p2;
lis1[p2].pre = p1;
}
int main()
{
sinit();
int n, x; scanf("%d", &n);
for (int i = 1; i <= n; ++i)
{
scanf("%d", &x);
sadd1(x);
}
// lis1:包含所有数据的链表,lis2:连接所有块第一个数据的链表
int p = lis1[shead].next, spre;
spre = p;
sadd2(p, lis1[p].data);
p = lis1[p].next;
while (lis1[p].next != 0)
{
if (lis1[p].data == lis1[spre].data) p = lis1[p].next;
else
{
sadd2(p, lis1[p].data);
spre = p;
p = lis1[p].next;
}
}
int cnt = 0;
while (cnt < n)
{
p = lis1[shead].next;
while (lis2[p].next != 0)
{
printf("%d ", p - 2); ++cnt;
int p1_2 = lis1[p].next;
int p2_1 = lis2[p].pre, p2_2 = lis2[p].next;
sdel(p);
if (p1_2 == p2_2)
{
lis2[p2_1].next = p2_2;
lis2[p2_2].pre = p2_1;
p = p2_2;
}else
{
lis2[p1_2].data = lis1[p1_2].data;
lis2[p2_1].next = p1_2; lis2[p1_2].next = p2_2;
lis2[p2_2].pre = p1_2; lis2[p1_2].pre = p2_1;
p = p2_2;
}
}
// 合并lis2中相同的块
p = lis2[shead].next;
while (lis2[p].next != 0)
{
if (lis2[p].data == lis2[lis2[p].pre].data)
{
int p1 = lis2[p].pre, p2 = lis2[p].next;
lis2[p1].next = p2; lis2[p2].pre = p1;
p = p2;
}else p = lis2[p].next;
}
printf("\n");
}
return 0;
}
算法拓展
1.链表+队列。对于相同的块,用队列存储。所有队列的编号用双向链表维护。属于链表套队列。相邻的队列存储的是不同的水果。从前到后挨个输出。如果一个队列里的数据项输出完毕,在链表中删除它。遍历链表,对于前后数据项相同的队列,则合并。然后再次做一遍上述过程。直到链表为空。
这种方法写起来还是比较容易的,队列用queue,链表用数组维护。
#include<iostream>
#include<cstdio>
#include <queue>
using namespace std;
struct node
{
int val;
int next,pre;
}slis[200010];
queue<int> q[200010];
int shead, stail, tot;
void sinit()
{
tot = 2;
shead = 1; stail = 2;
slis[shead].next = stail;
slis[stail].pre = shead;
}
void sinsert(int p, int val)
{
int q = ++tot;
// p q slis[p].next
slis[q].val = val;
slis[ slis[p].next ].pre = q;
slis[q].next = slis[p].next;
slis[p].next = q;
slis[q].pre = p;
}
void sdel(int p)
{
// slis[p].pre p slis[p].next
slis[ slis[p].pre ].next = slis[p].next;
slis[ slis[p].next ].pre = slis[p].pre;
}
int main()
{
int n, v, t = -1;
scanf("%d", &n);
sinit();
for (int i = 1; i <= n; ++i)
{
scanf("%d", &v);
if (v != t)
{
t = v;
sinsert(slis[stail].pre, v);
}
q[tot].push(i);
}
int cnt = 0;
while (cnt != n)
{
// 从前到后输出每个块的第一个编号
int spos = slis[shead].next;
int u;
while (spos != stail)
{
u = q[spos].front(); q[spos].pop();
printf("%d ", u); ++cnt;
spos = slis[spos].next;
}
printf("\n");
// 从前到后检查有没有大小为0的块需要删除
spos = slis[shead].next;
while (spos != stail)
{
if (!q[spos].size()) sdel(spos);
spos = slis[spos].next;
}
// 从前到后检查有没有相邻的块相同的,则合并
spos = slis[shead].next;
while (spos != stail)
{
int s;
while (1)
{
s = slis[spos].next;
if ( slis[spos].val == slis[s].val && s != stail ) // 相同,需要合并
{
while (q[s].size())
{
q[spos].push(q[s].front()); q[s].pop();
}
sdel(s);
}else break;
}
spos = slis[spos].next;
}
}
return 0;
}
2.数据块做法。和1类似,是用一个区间 [ l , r ] [l, r] [l,r]表示相同的块,和1中的队列的功能相似。建结构体双向链表,存相同的块的区间和数据项。后面的过程就是一遍遍扫和合并。
3.并查集。对于一个块中的数据项,假如是3、4、5这四个。当输出3号之后,下一次要输出的就是4号,输出4号之后,下一次要输出的就是5号。如何快速找到下一个要输出的数据项?用并查集维护这种关系。当输出3号之后,让3和4相连,4作为父节点,即大的作为父节点, f a [ 3 ] = 4 fa[3]=4 fa[3]=4,下次直接输出 f a [ 3 ] fa[3] fa[3]即4号点。当输出4号点之后,在4和5之间相连,并查集路径压缩之后,可知 f a [ 3 ] = 5 fa[3]=5 fa[3]=5,直接输出 f a [ 3 ] fa[3] fa[3]即5号点。依次类推。假如下一个块是6、7、8、9。当3、4、5这个块输出完毕后,即5号点已经输出,则让 f a [ 5 ] = 6 fa[5]=6 fa[5]=6,和下一个块自动合并了。不需要删除结点。可以看出森林中会有合并的操作发生。合并的时候要让编号大的点作为父节点,这点很重要。已经存在的树,树根是等待输出的,其余都是已经输出的一段。
记录每个块的第一个数据编号和每个数据分别属于哪个块,设最初共有 t t t个块。可以看作有 t t t棵树。对于第 i i i块,输出 f a [ i ] fa[i] fa[i],然后输出第 f a [ i ] fa[i] fa[i]这个数据属于的块的下一个块的信息。当输出一遍后,因为可能有树的合并操作,所以要对值相等的相邻的树再次进行合并。从前到后扫一遍即可。整理完数据后,再次输出。当输出的个数等于 n n n就可以退出了。