[UOJ391] 鸽举选仕

我把这题推荐给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\to 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 \neq 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) \to 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\to 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") ; 

posted @ 2019-02-21 13:40  GuessYCB  阅读(414)  评论(0编辑  收藏  举报