CSP-J2021第二轮 解题分析

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] [lnl/n,rnl/n⌋]块糖果。奖励的糖果数量最多为 n − 1 n - 1 n1块,如果 n − 1 < = r n - 1 <= r n1<=r,则 k k k可以取值为 n ∗ ⌊ l / n ⌋ + n − 1 n * \lfloor l / n \rfloor + n - 1 nl/n+n1,奖励糖果数量为 n − 1 n - 1 n1块。否则 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==n1时退出,有些点会造成超时。如 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=5108l=n+2,r=n+8108。直接枚举官方数据卡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 n1

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+Q5000),超时。注意到在第一次插排之后,每次只更新一个数据项,没有必要重新花费 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[j1].data∣∣(a[j].data==a[j1].data&&a[j].pos<a[j1].pos

除了第一次用快排外,其余每次更新 O ( n ) O(n) O(n),整体复杂度 O ( n 2 + 5000 n + Q − 5000 ) O(n^2 + 5000n+ Q - 5000) O(n2+5000n+Q5000)

#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 abcde得在范围之内,否则非法。
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 abcde中,变量用 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 abcde的取值范围是否合法。然后把变量 a 、 b 、 c 、 d 、 e a、b、c、d、e abcde按照标准格式组成地址串,判断和原地址串是否一样即可。也可以照顾到前导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 p2,因为有头和尾结点,数据项编号从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就可以退出了。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值