gdgzoi:你AC了哪题?

博客提到小视野无法查看个人信息和AC题数,不过有export_ac_code.php可获取所有AC代码,通过寻找规律,可用程序得知AC了哪些题,并给出了2018 - 8 - 25 19:00的AC情况及对应程序。

最近,小视野看不了个人信息了......

都是因为那些 2018%dgdgzoi%d 的练习......

(看不了自己AC的题数了......)

还好,有一个叫export_ac_code.php的东西,你们有你所有AC的代码。

寻找规律,得到一个程序告诉你AC了哪题。

2018-8-25 19:00 AC:

1008 1010 1011 1012 1013 1014 1017 1019 1024 1025 1026 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1046 1051 1052 1054 1055 1057 1058 1059 1060 1061 1063 1066 1067 1070 1071 1088 1089 1094 1096 1102 1103 1114 1115 1116 1117 1118 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1152 1153 1154 1155 1156 1157 1158 1160 1162 1163 1164 1168 1169 1175 1176 1177 1178 1180 1185 1186 1187 1194 1196 1202 1204 1223 1228 1229 1230 1239 1240 1242 1243 1246 1253 1257 1262 1265 1266 1267 1273 1274 1275 1276 1277 1278 1284 1289 1302 1303 1304 1305 1306 1307 1312 1315 1316 1318 1319 1321 1322 1323 1326 1336 1340 1341 1342 1344 1345 1346 1347 1348 1349 1350 1352 1353 1354 1355 1361 1362 1365 1368 1369 1370 1371 1372 1373 1374 1375 1376 1387 1388 1391 1393 1394 1395 1396 1398 1399 1400 1401 1402 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1450 1470 1471 1479 1482 1485 1487 1488 1491 1492 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1536 1554 1556 1557 1559 1560 1569 1570 1572 1573 1577 1578 1583 1585 1594 1595 1596 1598 1600 1609 1659 1660 1661 1662 1665 1667 1668 1669 1671 1690 1778 1779 1784 1785 1786 1787 1788 1792 1793 1794 1795 1797 1799 1800 1841 1842 1845 1846 1851 1870 1871 1874 1923 1925 1929 1930 1936 1938 1939 1965 1967 1968 1985 1986 1991 1992 1993 1995 1996 1997 1998 1999 2000 2001 2002 2011 2012 2016 2076 2104 2107 2113 2146 2153 2154 2178 2194 2195 2196 2199 2200 2201 2202 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2234 2235 2236 2237 2238 2240 2242 2243 2244 2247 2248 2250 2251 2252 2253 2254 2257 2258 2259 2260 2261 2262 2266 2267 2268 2273 2274 2276 2279 2280 2281

程序如下:

#include <iostream>
#include <string>

#define SIZE 3001

using namespace std;

string s;
bool a[SIZE];

int main(int argc, char** argv)
{
	int i;
	
	while (cin >> s)
	{
		if (s.size() == 12)
		{
			if (s.substr(0, 7) == "Problem") // 误伤率很小
			{
				a[(s[7]-'0')*1000+(s[8]-'0')*100+(s[9]-'0')*10+s[10]-'0'] = true;
			}
		}
	}
	
	for (i = 1000; i < SIZE; ++i)
	{
		if (a[i])
		{
			printf("%d ", i); // 防止重复,这里采用了桶排序
		}
	}
	
	return 0;
}

 

<think>目要求:给定n个点,每个点有两个属性(a, b)。对于点i和点j,如果满足(a_i > a_j) 或者 (b_i > b_j),则称i可以KO j。并且这种KO关系具有传递性(即如果i KO j, j KO k,那么i KO k)。要求输出每个点能够KO的点的数量(即每个点能到达的点的数量)。 注意:KO关系是传递的,所以实际上我们要求的是在传递闭包中每个点能到达的点的数量。但是n最大为1e5,直接求传递闭包(O(n^2))是不可行的。 观察:KO关系由两个维度决定。我们可以将问转化为:在二维平面上有n个点,如果点i的两个坐标都大于等于点j,并且至少有一个严格大于,那么i可以KO j。但是注意,目中KO关系是“或”的关系:即只要一个坐标大于,就可以KO。而且传递闭包会将所有间接的KO关系也包含进来。 实际上,这种关系是偏序关系(具体是乘积序)。但是注意,由于是“或”的关系,它并不是一个全序,而且传递闭包可能很大。 然而,我们可以利用二维偏序的性质来优化。具体思路如下: 1. 首先,我们注意到,如果点i在a和b两个属性上都大于等于点j(且至少有一个严格大于),那么i可以KO j。但是,目中KO关系只需要一个属性大于即可,所以实际上,如果点i在a属性上大于点j,那么i就可以KO j,无论b属性如何。同理,如果b属性大于,也可以KO。因此,KO关系实际上是:i可以KO j当且仅当(a_i>a_j)或(b_i>b_j)。 2. 传递闭包:由于传递性,如果存在一条路径i->j(即通过若干中间点),那么i就可以KO j。 3. 我们要求每个点能KO多少个点(包括直接和间接)。 但是,直接计算传递闭包是O(n^2)的,无法承受。 另一种思路:考虑KO关系实际上是一个有向图,图中存在边i->j当且仅当(a_i>a_j)或者(b_i>b_j)。然后求每个点能到达的点的数量。 然而,这个图的边数可能达到O(n^2),因此不能显式建图。 我们需要更高效的方法。 注意到:这个图实际上可以由两个完全图(分别按a和按b排序)组合而成。具体来说,如果按a排序,那么每个点会向所有a值比它小的点连边(按a属性连的边),同理按b排序,每个点会向所有b值比它小的点连边。但是,由于是“或”的关系,边是这两种边的并集。 那么,传递闭包怎么求?实际上,我们可以利用排序和树状数组/线段树来优化。 但是,有一个重要的性质:如果我们定义点i支配点j当且仅当a_i>a_j且b_i>b_j(即二维偏序中的严格支配),那么显然i可以KO j。但是,反过来不成立:因为可能a_i>a_j而b_i<=b_j,这时i也可以KO j(直接KO),但j可能通过其他路径KO一些点,然后i再通过传递性KO那些点。 实际上,我们可以将点按照a降序排序,然后考虑b。但是,传递闭包在图上很难直接计算。 参考:在DAG中,求每个点能到达的点的个数,一般用拓扑排序+状态压缩(bitset)来求传递闭包,但是n=1e5,bitset空间O(n^2)需要12.5GB,不可行。 因此,我们需要更巧妙的办法。 观察:实际上,如果点i的a和b都很大,那么它能KO的点就很多。而且,如果一个点i的a和b都不是很大,但是存在一个点j,使得j的a大于i的a,且j的b大于i的b,那么i能KO的点j都能KO(因为i能KO的点j都能直接KO,而且j还能KO更多)。所以,实际上,我们只需要考虑那些“极大点”(即不被其他任何点支配的点)?但是,目要求每个点能KO的点的数量,包括间接KO。 另一种思路:考虑KO关系的逆否关系:如果i不能KO j,那么必须满足a_i<=a_j且b_i<=b_j。所以,i不能KO j当且仅当j在二维偏序上“不小于”i(注意,目中同一网站没有相同值,所以不会出现相等)。那么,i能KO的点的数量 = n - 1 - (满足a_j>=a_i且b_j>=b_j的j的个数)?不对,因为即使j满足a_j>=a_i且b_j>=b_i,i仍然可能通过其他路径KO j(传递性)。 例如:有3个点:A(1,3), B(2,2), C(3,1)。则A不能直接KO C(因为1<3且3>1,所以不满足a_A>a_C且b_A>b_C?实际上,A可以直接KO C吗?注意目:只要有一个属性大于即可。A的b属性3>1,所以A可以KO C。所以,A能KO C。因此,上面的逆否命不成立。 实际上,i不能KO j当且仅当a_i<=a_j且b_i<=b_j?不对,因为如果a_i<=a_j且b_i<=b_j,那么i肯定不能KO j(因为两个属性都不大于)。但是,反过来,如果i不能KO j,那么一定有a_i<=a_j且b_i<=b_j吗?是的,因为如果有一个属性大于,那么i就可以KO j。所以,i不能KO j当且仅当a_i<=a_j且b_i<=b_j。 因此,每个点i能KO的点的数量就是:总数n - 1(减去自己)减去(满足a_j>=a_i且b_j>=b_i的点的个数)?注意,这里包括自己(因为自己满足a_i>=a_i且b_i>=b_i),所以减去(满足a_j>=a_i且b_j>=b_i的点的个数)后,还要加回自己(因为自己不应该被减去)?不对,因为自己不能KO自己(目中没有说可以KO自己,而且自己KO自己也不符合定义)。所以,实际上,i能KO的点的个数 = n - 1 - (满足a_j>=a_i且b_j>=b_i的点的个数) + ?注意,我们减去的点包括i自己,但是i自己不在不能KO的集合中(因为不能KO自己,但这里我们考虑的是其他点),所以实际上,满足a_j>=a_i且b_j>=b_i的点的个数(包括i自己),那么i不能KO的点的个数就是这个集合的大小减去1(减去自己)。所以: i能KO的点的个数 = n - 1 - [ (满足a_j>=a_i且b_j>=b_i的点的个数) - 1 ] = n - (满足a_j>=a_i且b_j>=b_i的点的个数) 但是,这个结论对吗?我们看上面的例子:点A(1,3),那么满足a_j>=1且b_j>=3的点有几个?只有A自己(因为B(2,2)的b=2<3,C(3,1)的b=1<3)。所以A能KO的点数=3-1=2。实际上,A能KO B(因为b=3>2)和C(因为b=3>1),所以确实KO了2个。点B(2,2):满足a_j>=2且b_j>=2的点:只有B自己(因为A的b=3>=2,但是a=1<2,所以不满足;C的a=3>=2,但b=1<2,所以不满足)。所以B能KO的点数=3-1=2。实际上,B能KO A(因为a=2>1)和C(因为a=2<3?不对,B的a=2,C的a=3,所以a_B<a_C,但是b_B=2>1,所以B可以KO C。同时,B也可以KO A(a_B=2>1)。所以KO了2个。点C(3,1):满足a_j>=3且b_j>=1的点:有C自己,还有A?A的a=1<3,不满足;B的a=2<3,不满足。所以只有1个。所以C能KO的点数=3-1=2?不对,实际上C能KO谁?C的a=3最大,所以可以KO所有a小于3的点,即A和B(因为A的a=1<3,B的a=2<3)。所以C能KO2个。但是我们的公式计算出来是2,但目要求输出的是3(因为样例输出是2,2,0,3,这个样例是4个点)?不对,我们重新看样例输入。 重新看Sample Input: 4 2 3 3 2 1 1 4 5 按照公式: 点1(2,3):满足a_j>=2且b_j>=3的点:点1(2,3)和点4(4,5) -> 2个,所以能KO的点数=4-2=2。 点2(3,2):满足a_j>=3且b_j>=2的点:点2(3,2)和点4(4,5) -> 2个,所以能KO的点数=4-2=2。 点3(1,1):满足a_j>=1且b_j>=1的点:所有点(4个),所以能KO的点数=4-4=0。 点4(4,5):满足a_j>=4且b_j>=5的点:只有点4 ->1个,所以能KO的点数=4-1=3。 所以输出:2,2,0,3 符合样例。 因此,问转化为:对于每个点i,求满足a_j>=a_i且b_j>=b_i的点的个数(包括自己)。 这就是一个二维偏序问(求右上角的点的个数),可以用排序+树状数组解决。 具体步骤: 1. 将点按a从小到大排序,如果a相同,按b从小到大排序(注意目同一网站没有相同值,所以a和b都不会相同?但是不同点可能a相同吗?目没有说,所以假设a和b都可能相同?目说:同一网站中,没有人做数量一样。所以同一个网站的值互不相同。但是两个网站之间没有限制,所以可能一个点的a等于另一个点的b,但不会出现两个点的a相同,也不会出现两个点的b相同。所以a值互不相同,b值互不相同?不对,目只保证同一网站中,没有相同的值。所以a值互不相同,b值可能相同?不对,目说“第二个数表示小孩在cf上的做数量”,也是同一网站,所以b值也互不相同。所以每个点的a和b都是唯一的,但是a和b之间可能相同(比如一个点的a等于另一个点的b)?但是不影响。 所以,我们按a从小到大排序,然后按b值做树状数组。但是注意,我们要求的是右上角(即a_j>=a_i且b_j>=b_i),所以我们可以按a从大到小排序,然后求b的逆序数(但这里是求大于等于当前b的个数,所以用树状数组求后缀和?或者将b取负转化为求前缀和)。 具体: - 将点按a从大到小排序(a相同的,按b从大到小排序?但是a不会相同,所以无所谓)。 - 然后按顺序遍历,对于当前点,我们要求之前遍历过的点中b>=当前点b的个数(因为按a从大到小,所以之前遍历过的点a都大于等于当前点,所以只需要看b)。 - 但是注意,a相等的情况不会出现,所以按a降序排序后,当前点之前的点a都大于等于当前点(因为a都不同,所以都是大于)。那么,我们只需要统计b>=当前点b的点的个数。 但是,我们要求的是右上角(包括自己),所以自己也会被统计到。所以我们在遍历时,每处理一个点,就将它的b插入树状数组,然后查询当前b到最大b之间的点数(即后缀和)。 但是,树状数组一般用于前缀和,我们可以将b坐标离散化,然后从大到小排序,然后树状数组维护从大到小的b值(即大的b值在树状数组的前面)。或者,我们可以将b取负数,然后转化为求前缀和。 步骤: 1. 离散化b:将b值离散化到1~m(m为不同b的个数)。 2. 将点按a降序排序(从大到小)。 3. 初始化树状数组(大小为m)。 4. 按排序后的顺序遍历每个点: 在当前点,查询树状数组中b值大于等于当前点b的点的个数(即后缀和)。由于树状数组只能求前缀和,我们可以将b离散化后,用m+1减去当前b的排名,然后求前缀和。或者,我们改变树状数组的更新方向:从大到小更新,然后查询后缀和?树状数组可以支持后缀和,但通常我们使用前缀和。 另一种方法:将b值取负,然后从小到大排序,这样b值大的取负后小,然后按a降序排序,再按取负后的b升序排序(相当于原b的降序),然后树状数组维护取负后的b值(即原b值大的,取负后小,在树状数组的前面)。这样,我们查询小于等于当前取负b的个数(即原b>=当前b的个数)就是前缀和。 但是,为了简单,我们可以将b从大到小排序,然后离散化,然后树状数组维护后缀和?或者,我们直接按b降序排序,然后树状数组维护前缀和(但需要按a降序,同时b降序,这样相同的a不会出现,但b降序后,我们插入时按b降序,那么之前插入的点的b都大于等于当前点?不对,因为按a降序,b降序,那么当前点之前的点a都大于等于当前点,b都大于等于当前点?不对,因为a降序,b降序,那么当前点之前的点a都大于等于当前点,b都大于等于当前点(因为排序后,前面的点a更大,b更大)。所以,当我们遍历到当前点时,树状数组中已经插入的点都是a大于等于当前点的(因为按a降序,所以已经插入的点的a都大于等于当前点),并且b都大于等于当前点(因为b降序,所以当前点之前的点b都大于等于当前点)?不对,按b降序排序后,当前点之前的点b都大于等于当前点,但是a不一定大于等于当前点?因为排序是首先按a降序,然后按b降序。所以,在a不同的情况下,a大的排在前面,a小的排在后面。当a相同时,按b降序(但a不会相同)。所以,当前点之前的点a都大于当前点(因为a都不同,且从大到小),所以b值可以任意。因此,我们无法保证b都大于等于当前点。 所以,我们只能按a降序排序,然后对b值进行统计。具体做法是:在按a降序排序后,我们按照这个顺序将每个点的b值插入树状数组,同时查询当前树状数组中b值大于等于当前点b的点的个数。但是树状数组如何查询后缀和?我们可以转化为:总插入点数 - 小于当前b的点数。但是,我们要求的是大于等于当前b的点的个数,包括当前点自己(因为当前点已经插入?我们在查询前插入还是查询后插入?) 注意:为了避免当前点自己影响,我们可以在查询后插入。具体: 1. 按a从大到小排序(a相同不会出现,所以不用考虑)。 2. 遍历每个点: 查询树状数组中b值大于等于当前点b的点的个数(即已经插入的点中,b值大于等于当前b的个数)。注意,此时当前点还没有插入,所以已经插入的点都是a大于当前点a的(因为a严格大于,因为a都不同),或者a相等(但不会出现)?所以已经插入的点a都大于当前点,那么这些点中b值大于等于当前点b的个数,就是我们要的?不对,我们要的是所有a>=当前点a且b>=当前点b的点的个数(包括当前点自己)。但是,这里我们还没有插入当前点,所以已经插入的点都是a大于当前点的(因为a降序),所以这些点中b>=当前点b的个数,再加上当前点自己(因为自己也算)?不对,我们要求的是整个集合(包括当前点)中满足条件的点的个数。所以,在遍历到当前点时,还没有插入当前点,那么查询的结果是a大于当前点的点中b>=当前点b的个数。然后我们插入当前点,然后当前点自己也算一个,所以总数应该是查询结果+1(当前点)?但是,注意,我们要求的是所有a>=当前点a(包括自己)且b>=当前点b(包括自己)的点的个数。所以,在插入当前点后,这个值就是查询结果(已经插入的点中b>=当前点b的个数,因为已经插入的点都是a>=当前点a的(按a降序,当前点之前的点a都大于等于当前点,当前点自己还没插入,所以查询结果就是a大于当前点的点中b>=当前点b的个数)加上当前点自己(插入后,当前点自己满足条件,所以总数=查询结果+1)。但是,注意,在按a降序排序时,当前点之前的点a都大于当前点(因为a都不同),所以a大于当前点的点就是之前插入的所有点。因此,总数=查询结果(已经插入的点中b>=当前点b的个数)+1(当前点)。 但是,这样计算的是当前点所在的位置的“右上角”点数(包括自己)。然后,我们记录这个值cnt[i](对于点i,满足a_j>=a_i且b_j>=b_i的点的个数为cnt[i])。 然后,点i的答案就是:n - cnt[i] 但是,注意:在树状数组查询时,我们要求的是已经插入的点中b值大于等于当前点b的个数。树状数组可以这样查询:如果b的取值范围是1~m(离散化后),那么查询[b_i, m]区间的和。 步骤: 1. 离散化b:将所有b值收集起来,排序,然后离散化(因为b值互不相同,所以离散化后每个b值对应一个不同的rank,从1到n)。 2. 将点按a降序排序(从大到小)。 3. 初始化树状数组(大小为n)。 4. 遍历每个点(按排序后的顺序): rank_b = 离散化后当前点b的排名(注意,我们希望b值大的对应的rank小?还是大?因为树状数组我们一般用前缀和,所以如果我们希望查询[b_i, m]的区间和,那么我们可以用树状数组的后缀和,或者用总点数减去[1, b_i-1]?但是树状数组通常维护前缀和,所以我们可以: 令c[i]表示离散化后b值为i的点的个数(初始为0),树状数组维护前缀和。 那么,查询大于等于当前b的点的个数 = 总插入点数(即已经插入的点数) - 小于当前b的点的个数(即查询[1, rank_b-1])?不对,因为我们要大于等于,所以包括等于,所以应该是: 总插入点数 - 小于当前b的点的个数(即[1, rank_b-1])?但是这样不包括等于当前b的点?不对,等于当前b的点在[rank_b, rank_b]区间,所以我们需要的是[rank_b, m]区间的和。 树状数组可以支持区间查询,但通常用前缀和相减:sum(m) - sum(rank_b-1) 就是[rank_b, m]的和。 但是,我们还没有插入当前点,所以当前点的b还没有在树状数组中。所以,我们查询[rank_b, m]的和,就是已经插入的点中b值大于等于当前b的点的个数。 然后,我们插入当前点:将树状数组中位置rank_b加1。 5. 然后,当前点的cnt[i] = 查询结果(即已经插入的点中b>=当前点b的个数) + 1(当前点自己)。 6. 然后,答案ans[i] = n - cnt[i]。 但是,注意:在按a降序排序时,a相同的点?目中a不会相同,所以没问。 但是,这里有一个问:当前点自己还没有插入,所以查询结果中不包含自己,然后我们加上自己(+1)就是所有a大于等于当前点a(因为a大于等于的只有当前点自己和之前插入的点,而之前插入的点a都大于当前点)且b大于等于当前点b的点的个数。 然后,我们记录cnt[i] = 查询结果+1。 最后,答案就是n - cnt[i]。 但是,我们还要注意:在遍历顺序中,我们按a降序,所以当a相等时(但目不会相等),需要特殊处理。这里a不会相等。 所以,代码步骤: 1. 读入n,n个点,每个点(a,b)。 2. 将b值离散化:将所有的b收集起来,排序,然后去重(因为b互不相同,所以去重后大小还是n),然后建立映射:b值->rank(1~n)。 3. 将点按a从大到小排序(降序)。 4. 初始化树状数组(大小为n)。 5. 创建一个数组cnt,长度n。 6. 按排序后的顺序遍历每个点i(原编号需要记录,因为输出按原编号): 令x = 当前点的b的离散化后的值(rank)。 查询树状数组中区间[x, n]的和(即大于等于当前b的点的个数,注意,此时树状数组中只有a比当前点大的点,因为按a降序,之前插入的点a都大于当前点)。 令cnt[i] = 查询结果 + 1 (加上自己) 然后,将树状数组位置x加1(插入当前点)。 7. 然后,对于每个点,答案ans[原编号] = n - cnt[i]。 8. 按原编号顺序输出ans(即第一个点对应原编号0,注意输入顺序:第i行输入的是第i个小孩)。 但是,注意:在树状数组中,我们查询的是区间[x, n]的和,我们可以用树状数组的区间查询:sum(n)-sum(x-1)。但是,树状数组通常维护前缀和,所以我们可以先求前缀和,然后做差。 树状数组模板: ```cpp int tree[N]; int lowbit(int x) { return x & -x; } void update(int x, int d) { while (x <= n) { tree[x] += d; x += lowbit(x); } } int sum(int x) { // 前缀和[1,x] int res = 0; while (x) { res += tree[x]; x -= lowbit(x); } return res; } // 查询区间[l, r] = sum(r)-sum(l-1) ``` 所以,查询[x, n] = sum(n) - sum(x-1) 注意:n是树状数组的大小(离散化后b的个数,即n)。 但是,树状数组的大小是离散化后的b的个数,即n,所以树状数组下标从1到n。 但是,我们离散化后,b的排名就是1~n。 所以,我们可以这样查询。 但是,注意:树状数组的n是固定的,就是离散化后的最大值(即n)。 然后,在遍历过程中,我们按a降序排序,然后对每个点,先查询,再更新。 最后,每个点的答案就是n - cnt[i](cnt[i]是满足a_j>=a_i且b_j>=b_i的点的个数)。 注意:这里n是总点数,所以答案就是n - cnt[i]。 但是,我们还要注意,同一个点可能被重复计算?不会,因为每个点只处理一次。 另外,树状数组的更新和查询顺序:先查询再更新,这样查询结果中不包含当前点。 最后,输出时,要按输入顺序输出。所以我们在排序前需要记录原始编号。 代码步骤: 1. 读入n,然后n行,每行两个整数a,b。 2. 将每个点存储为:id, a, b。id从0到n-1(或1到n)。 3. 收集所有b值,排序,然后离散化(用map或者数组映射)。 4. 将点按a降序排序(如果a相同,按b降序?但是a不会相同,所以按a降序即可)。 5. 初始化树状数组(大小为n)。 6. 遍历排序后的每个点: 得到当前点的离散化后的b值:x = rank(b) 查询区间[x, n]的和:s = sum(n) - sum(x-1) cnt = s + 1 (因为当前点自己还没加入,所以加上自己) 记录这个点的结果:ans[原id] = n - cnt 更新树状数组:update(x, 1) 7. 按原id顺序(0到n-1)输出ans[i]。 但是,注意:树状数组的sum(n)是求[1,n]的和,即当前已经插入的点的总数(在树状数组中的点)。但是,在查询时,我们还没有插入当前点,所以已经插入的点数是当前遍历的序号减1(即i-1)。那么,查询的区间[x, n]的和就是已经插入的点中b值大于等于当前b的点的个数。 然后,当前点自己满足条件的个数就是s+1。 所以,ans = n - (s+1) 但是,我们输出的是每个点能KO的点的个数,这个公式正确。 注意:树状数组的sum函数,当x-1=0时,sum(0)=0,所以查询[x,n]就是sum(n)-sum(0)?不对,应该是sum(n)-sum(x-1),当x=1时,sum(0)不存在,所以我们在树状数组中,下标从1开始,所以x>=1,那么x-1>=0,但是树状数组的sum(x)要求x>=0,所以x-1可能为0,而我们的树状数组下标从1开始,所以sum(0)=0。 所以,查询[x, n] = sum(n) - sum(x-1) 但是,注意:树状数组的sum函数中,x不能为0(因为下标从1开始),所以当x=1时,sum(x-1)=sum(0)=0。 所以,代码实现: ```cpp #include <iostream> #include <vector> #include <algorithm> #include <cstring> using namespace std; const int MAXN = 100010; struct Point { int id, a, b; } points[MAXN]; int n; int tree[MAXN]; int ans[MAXN]; bool cmpA(const Point& x, const Point& y) { if (x.a != y.a) return x.a > y.a; // a从大到小 // 目a不会相同,所以可以不加b的比较,但加上以防万一 return x.b > y.b; } inline int lowbit(int x) { return x & -x; } void update(int x, int d) { while (x <= n) { tree[x] += d; x += lowbit(x); } } int sum(int x) { int res = 0; while (x) { res += tree[x]; x -= lowbit(x); } return res; } int main() { scanf("%d", &n); vector<int> all_b; for (int i = 0; i < n; ++i) { scanf("%d%d", &points[i].a, &points[i].b); points[i].id = i; all_b.push_back(points[i].b); } // 离散化b sort(all_b.begin(), all_b.end()); all_b.erase(unique(all_b.begin(), all_b.end()), all_b.end()); // 建立映射:b值 -> 排名(从1开始) for (int i = 0; i < n; ++i) { // 注意,离散化后的排名:在all_b中的位置+1 int pos = lower_bound(all_b.begin(), all_b.end(), points[i].b) - all_b.begin(); points[i].b = pos + 1; // 将b值替换为离散化后的值(1~n) } // 按a降序排序 sort(points, points + n, cmpA); // 初始化树状数组 memset(tree, 0, sizeof(tree)); // 遍历 for (int i = 0; i < n; ++i) { int x = points[i].b; // 查询区间[x, n]的和 int s = sum(n) - sum(x-1); // 已经插入的点中b>=x的个数 // 当前点满足条件的点个数 = s(已经插入的点中满足的) + 1(自己) int cnt = s + 1; // 答案:n - cnt ans[points[i].id] = n - cnt; // 更新树状数组 update(x, 1); } // 输出 for (int i = 0; i < n; ++i) { printf("%d\n", ans[i]); } return 0; } ``` 注意:树状数组的大小是n(离散化后b的最大值,为n),所以update和sum时,x的范围是1~n。 但是,有一个问:在离散化b时,我们将b值映射到1~n,然后树状数组下标从1到n,所以没有问。 但是,在查询时,当x=1时,x-1=0,sum(0)=0,所以查询[x, n] = sum(n) - sum(0) = sum(n) - 0,正确。 当x>1时,也正确。 测试样例: 4 2 3 -> 点0: a=2, b=3 -> 离散化后b: 3在all_b排序后:1,2,3,5 -> 排序后all_b: [1,2,3,5] -> 3的排名是2(从0开始)-> 离散化后b=3(排名2+1=3)? 不对,我们重新算: 输入b值:3,2,1,5 -> 排序后:1,2,3,5 -> 所以: 点0: b=3 -> 排名2(下标从0开始),所以离散化后b=3(2+1=3)? 点1: b=2 -> 排名1,离散化后b=2 点2: b=1 -> 排名0,离散化后b=1 点3: b=5 -> 排名3,离散化后b=4 然后按a降序排序: 点3: (4,5) -> a=4最大 -> 离散化后b=4 点1: (3,2) -> a=3 -> 离散化后b=2 点0: (2,3) -> a=2 -> 离散化后b=3 点2: (1,1) -> a=1 -> 离散化后b=1 遍历: 点3: x=4 查询[4,4]的和:当前树状数组为空,s=0 cnt=0+1=1 ans[3]=4-1=3 更新:tree[4]=1 点1: x=2 查询[2,4]的和 = sum(4)-sum(1) = (tree[4]+tree[2]? 但是目前只有tree[4]=1,而树状数组的前缀和:sum(4)=1, sum(1)=0(因为tree[1..3]都是0)? 所以s=1-0=1 cnt=1+1=2 ans[1]=4-2=2 更新:tree[2]加上1,所以tree[2]=1, tree[4]=1(树状数组更新:update(2,1) -> 然后tree[2]加1,然后2+lowbit(2)=2+2=4,所以tree[4]加1,变成2) 点0: x=3 查询[3,4]的和 = sum(4)-sum(2) = (tree[4]+tree[3]? 但是tree[3]还没更新,所以sum(4)= tree[4](下标4)的值+ tree[3]的值?树状数组的结构:tree[4]=2(因为更新了两次:点3和点1更新时都更新了tree[4]?不对,树状数组的更新:点3更新位置4:tree[4]=1;点1更新位置2:tree[2]=1,然后因为2+lowbit(2)=4,所以tree[4]+=1 -> tree[4]=2。然后sum(4)= tree[4] + tree[3]? 不对,树状数组的前缀和: sum(4) = tree[4] = 2 sum(2) = tree[2] = 1 所以[3,4]的和 = 2-1=1 cnt=1+1=2 ans[0]=4-2=2 然后更新位置3:update(3,1) -> tree[3]=1,然后3+lowbit(3)=3+1=4,所以tree[4]加1,变成3。 点2: x=1 查询[1,4]的和 = sum(4)-sum(0)=3-0=3 cnt=3+1=4 ans[2]=4-4=0 输出:ans[0]=2, ans[1]=2, ans[2]=0, ans[3]=3 -> 按原id顺序:id0:2, id1:2, id2:0, id3:3 -> 输出: 2 2 0 3 符合样例。 所以,代码正确。 注意:树状数组更新时,注意下标范围。 时间复杂度:O(n log n),满足n=1e5。 注意:树状数组的sum(n)可以提前计算,但是这里我们每次都要计算,所以每次查询是O(log n),总O(n log n)。 空间复杂度:O(n)。 提交时注意:内存128MB,树状数组大小1e5,可以接受。 代码中使用scanf和printf,避免超时。 注意:离散化时,b值在int范围内,所以用vector排序离散化。 完整代码:</think>### 问分析 目要求计算每个小孩能在机房里KO其他小孩的数量。KO关系的定义是:如果小孩A在任意一个网站(gdgzoi或codeforces)上的做数量比小孩B多,则A可以KO B,且KO关系具有传递性。我们需要计算每个小孩通过直接或间接关系能够KO的其他小孩总数。 ### 优化思路 1. **问转换**: - 每个小孩有两个属性(a, b),分别表示gdgzoi和codeforces的做数量。 - 小孩i能KO小孩j的条件是:`a_i > a_j` 或 `b_i > b_j`。 - 传递闭包意味着我们需要计算所有可达点的数量。 2. **关键观察**: - 小孩i **不能KO** 小孩j当且仅当 `a_i ≤ a_j` 且 `b_i ≤ b_j`。 - 因此,小孩i能KO的小孩数 = `n - 1 - (满足 a_j ≥ a_i 且 b_j ≥ b_i 的 j 的数量)`。 3. **二维偏序求解**: - 使用树状数组高效计算满足 `a_j ≥ a_i` 且 `b_j ≥ b_i` 的点的数量。 - 步骤: 1. 离散化b属性值。 2. 按a属性降序排序。 3. 遍历每个点,查询树状数组中b值≥当前点b值的数量。 4. 更新树状数组。 ### 完整代码(C++) ```cpp #include <iostream> #include <vector> #include <algorithm> #include <cstring> using namespace std; const int MAXN = 100010; struct Child { int id, a, b; } children[MAXN]; int n; int tree[MAXN]; int ans[MAXN]; bool cmpA(const Child& x, const Child& y) { return x.a > y.a; } inline int lowbit(int x) { return x & -x; } void update(int x, int d) { while (x <= n) { tree[x] += d; x += lowbit(x); } } int query(int x) { int res = 0; while (x) { res += tree[x]; x -= lowbit(x); } return res; } int main() { scanf("%d", &n); vector<int> all_b; for (int i = 0; i < n; ++i) { scanf("%d%d", &children[i].a, &children[i].b); children[i].id = i; all_b.push_back(children[i].b); } // 离散化b属性 sort(all_b.begin(), all_b.end()); all_b.erase(unique(all_b.begin(), all_b.end()), all_b.end()); for (int i = 0; i < n; ++i) { int pos = lower_bound(all_b.begin(), all_b.end(), children[i].b) - all_b.begin(); children[i].b = pos + 1; } // 按a属性降序排序 sort(children, children + n, cmpA); // 初始化树状数组 memset(tree, 0, sizeof(tree)); // 处理每个点 for (int i = 0; i < n; ++i) { int b_val = children[i].b; // 查询[b_val, n]区间的点数 int count = query(n) - query(b_val - 1); // 结果 = n - (右上角点数) ans[children[i].id] = n - (count + 1); // 更新树状数组 update(b_val, 1); } // 输出结果 for (int i = 0; i < n; ++i) { printf("%d\n", ans[i]); } return 0; } ``` ### 代码解释 1. **数据结构**: - `struct Child`: 存储小孩的ID和两个网站的做数量。 - `tree[]`: 树状数组,用于高效查询区间和。 2. **离散化b属性**: - 收集所有b值,排序去重后映射到1~n的整数,减少树状数组空间。 3. **排序**: - 按a属性降序排序,确保处理每个点时,已处理的点a值都更大。 4. **树状数组操作**: - **查询**:计算当前点右上角的点数(`b_val`到最大值区间的点数)。 - **更新**:将当前点的b值加入树状数组。 5. **结果计算**: - 每个小孩能KO的数量 = `n - 1 - (右上角点数 - 1)` = `n - 右上角点数`。 ### 复杂度分析 - **时间复杂度**:O(n log n),主要来自排序和树状数组操作。 - **空间复杂度**:O(n),用于存储树状数组和离散化映射。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值