我把这题推荐给yyb让他把这题做它的T2他竟然不要QwQ.......
题目大意:
下发八个题目和对应的八份代码,请构造数据Hack下发代码。
Task1
下发代码用了一些神奇做法实现A + B = C这个操作,由于 |A|,|B| <= 100,所以暴力for即可。
for(int a = -100; a <= 100; a ++)
for(int b = -100; b <= 100; b ++) {
if(ADD(a ^ b, (a & b) << 1) != a + b){
cout << a << " " << b << endl , exit(0) ;
}
}
Task2
下发代码是按照边数分治的做法,当边数(leq 10^6)时跑(O(m))暴力,当边数(>10^6)时跑(O(n^2))暴力。
先构造一个大小为(1000)的团让边数超过限制,然后构造一条链每次询问链的两端。
这样它的复杂度就变成了(O(Qn^2)),(5sec)时限妥妥的(TLE)。
n = 5000 ; Q = 10000 ;
cout << n << " " << Q << endl ;
for(int i = 2; i <= 1000; i ++) cout << 1 << " " << 1 << " " << i << endl ;
cout << 1 << " " << 1 << " " << 1001 << endl ;
for(int j = 1002; j <= 5000; j ++) cout << 1 << " " << j - 1 << " " << j << endl ;
for(int j = 5001; j <= 10001; j ++) cout <<2<<" "<< 5000 << " " << 1 << endl ;
Task3
下发代码就是据说当场(AC)了[CTSC2018]暴力写挂的迭代乱搞做法。
迭代的做法是:每次选中一个点,找到其最优点,然后跳到那个最优点,接着递归加卡时。
首先构造一个答案点,然后想办法让迭代的过程中不碰到那两个点。
我的做法是建两颗形态和边权都一样的树,
先固定答案点为(x,y),连边((x,z,inf),(y,z,inf),(z,1,-inf)),(z)的作用是使得(x,y)深度变为(0)。
然后我们给其他所有点找一个迭代最优点(c),连边:((1,a,0),(a,b,inf-1),(b,c,inf-1))。
为了保证(x,y)不在第一次找点时就被找到,在(1)下面连一堆((t,1,-1))的儿子(t)即可。
这样不管从哪里出发,迭代若干次后都会到达(c),而(x,y)始终都不会被碰到,自然也就得到了错误答案(2inf-2)。
n = 100000 ;
fa[2] = 1 ; e[2] = -inf ; fa[3] = 2 ; e[3] = inf ; fa[4] = 2 ; e[4] = inf ;
for(int i = 5; i <= 99997; i ++) fa[i] = 1 , e[i] = -1 ;
fa[99998] = 1 ; e[99998] = 0 ;
for(int i = 99999; i <= 100000; i ++) fa[i] = i - 1 , e[i] = inf - 1 ;
cout << 100000 << endl ;
for(int i = 2; i <= n; i ++) cout <<i <<" "<< fa[i] <<" " << e[i] << endl ;
for(int i = 2; i <= n; i ++) cout <<i <<" "<< fa[i] <<" " << e[i] << endl ;
Task4
先看懂下发程序在干啥,它维护单调栈,对于每一个本质不同的最大值,维护最靠前的位置进行转移。
它是用单调队列维护的,即若插入决策优于队尾决策,则把队尾弹掉。
然而对于(j o i),若决策(j)对(i)不优,不能说明(j)对之后的决策不优,即没有单调性。
考虑构造一组数据,让决策点回弹时决策点已经被弹出。
我们有两个决策点(A,B)和两个转移点(i_1,i_2),我们令(i_1)的最优决策为(B),(i_2)的最优决策为(A)。
由于(A,B)要能够同时存在在单调队列中,所以有:(a_A > a_B > a_{i_1,i_2})。
那么我们要做的就是当加入(f_B + a_{i_1})这个决策时,把(f_A + a_B)这个决策弹掉。
形式化的:(f_A + a_B ge f_B + a_{i_1})、(f_A + a_B < f_B + a_{i_2})。
先考虑(f_B),若(f_B = f_A + a_B),则第一个限制显然假了,所以(f_B)一定是从更靠前的转移点转移过来。
不妨考虑最简单的一种情况,即到(i_1)时,决策队列中只有(A,B)两个决策。
要实现这种情况,最简单的办法就是让之前元素都小于等于(A)。
我们直接让(A,B,i_1,i_2)相邻,那么由于(a_A > a_B > a_{i_1,i_2}),所以到了(A)时队列应该是空的。
这种情况下,(f_A = f_{A - m} + a_A)。
我们已知(f_B
eq f_A + a_B),所以(f_B = f_{B - m} + a_B) 。
我们现在的限制变为:(f_{A-m} + a_B ge f_{B - m} + a_{i_1})、(f_{A-m} + a_B < f_{B -m} + a_{i_2})。
既然现在的数组形如(...ABi_1i_2),一个直接想法就是(m = 3)。
但是有一个问题,我们在程序末尾有一个(f_{i-m} + max(i-m+1...,i-1,i) o f_i)的转移。
这会导致即时我们把决策(A)弹掉了,(f_{i_2})依旧会用(f_A)更新。
解决方案就是让(m=4),把(A' = A)设的特别大,然后放形如(...A'A'ABi_1i_2)的序列。
由于(A'=A)特别大,那么最优情况下一定是把三个(A)划分成一段,那么从(i-m)转移一定不优。
现在我们只需要让(x = f_{A-m}),(y = f_{B-m}),然后放(xyA'A'ABi_1i_2)就行了。
我们不妨让(x leq y),那么(f_{B-m} = y)、(f_{A-m} = x)。
所以限制条件变为:(x + a_B > y + a_{i_1})、(x + a_B < y + a_{i_2}),(x<y<A>B>a_{i_1,i_2})。
确定了上述限制和序列结构(xyA'A'ABi_1i_2)后就相当好构造了,手玩一下即可。
n = 8 ; m = 4 ;
cout << n << " " << m << endl ;
puts("50 100 1000 1000 1000 500 400 490") ;
Task5
选若干区间实际上只需要选两个区间,选更多的显然没用。
观察一下下发代码,它是几个贪心拼在一起。
首先把区间去重变为左右端点递增,然后第一个贪心是随机选区间,这个可以直接当作没看见。
先观察第二个贪心,它是一个不断缩小区间范围的算法。
再看一下第三个贪心,它选第二个贪心中答案最大的几个右端点,暴力(check)该右端点的所有区间。
我们的任务:让第二个贪心得不到答案,且答案区间的右端点不是第二个贪心中最大的几个。
令(n = 10^5),固定答案区间为([n-1,n])。
先让前面的区间长度较小,
然后把第二个贪心右端点(=n)的询问区间打表打出来,发现最小大概([99995,99998])。
任务变为:让(calc(b_n,b_{99998}) < calc(b_n,b_{99997}) < calc(b_n,b_{99996}) < calc(b_n,b_{99995}))。
先让(calc(b_{n-1},b_n) = (10^8)^2 = 10^{16})的级别,那么之前区间的答案不能超过这个数量级。
显然越大越好处理,那么我们让(b_{99998})到(b_{99995})左端点每次加(10^7)。
考虑为了满足上述不等式,这些区间与(n)的交为多少。
由于向左移动式,区间并在不断增加,所以区间交一定要不断减少。
设(99998)与(100000)的交为(Cross),我们考虑每向左一下区间交减少(1)。
那么从(99999)向左(c)个,则(calc(b_{99999-c},b_n) = (len_n + 10^7c)(Cross - c))。
所以(f(c) = -10^7c^2 + (10^7Cross - len_n)c)要单调递增,构造一下(Cross = 10^5)就差不多了。
我们还不能让右端点为(n)成为第二个贪心中的最大值,让前面的区间答案大概(10^{14})左右数量级即可。
n = 100000 ; m = 10000000 ; L = 1000000000 ;
Cross = 100000 ;
sl = L - m * 10 ; sr = L ;
cout << n << endl ;
for(int i = 1; i <= 99994; i ++) cout << i <<" "<<i + m <<endl ;
cout << sl - 5*m << " " << sl + Cross - 5 << endl ;
cout << sl - 4*m << " " << sl + Cross - 4 << endl ;
cout << sl - 3*m << " " << sl + Cross - 3 << endl ;
cout << sl - 2*m << " " << sl + Cross - 2 << endl ;
cout << sl - 1 << " " << sr - 1 << endl ; cout << sl << " " << sr << endl ;
Task6
旋转卡壳的经典错误,旋转卡壳不能对凸包上的每一个点求距离其的最远点。
构造一个宝石形状的凸包即可。
n = 6 ; cout << n << endl ;
puts("-8 0") ; puts("-6 -1") ; puts("6 -1") ;
puts("8 0") ; puts("6 1") ; puts("-6 1") ;
Task7
你发现这货不就类似什么珂朵莉树,每隔(32)个操作把相同的段都(merge)起来。
先用(10^4)个区间赋值操作把整个区间变成互不相同的数,
然后再用(10^4)个区间加法操作让他疯狂(for(i=1 o n))直接卡爆它。
n = 10000 ; m = 20000 ;
cout << n << " " << m << endl ;
for(int i = 1; i <= n; i ++) cout << 0 << " " << i << " " << i << " " << i << endl ;
for(int i = 1; i <= n; i ++) cout << 1 << " " << 1 << " " << n << " " << 1 << endl ;
Task8
观察良久发现这个乱搞是这样的:对每个点分别找到(x,y)最近的(frac{60000000}{n})个点,然后建最小生成树。
那么我们令(n = 10^5),也就是每次找(x,y)相邻的(60)个点。
首先建两个点((0,0))、((61,61))。
接着在它们中间插入(60)个((1,inf))、((inf,1))。
然后就做完了?把剩下的点放到((inf,inf))来凑数,最后询问一下((0,0))和((61,61))就行了。
n = 100000 ; cout << n << endl ;
cout << "0 0" << endl ; cout << "61 61" << endl ; n -= 2 ;
for(int i = 1; i <= 600; i ++, -- n) cout << 1 << " 100000" << endl ;
for(int i = 1; i <= 600; i ++ , --n) cout << "100000" << " " << 1 << endl ;
while(n --) puts("100000 100000") ;
Q = 1 ; cout << Q << endl ; puts("1 2") ;