T1:
考场思路为树剖维护查询,问题在于如何求复活次数,想到题解中给出的结论,并场上证明(证明采用调整法(贪心常见证明思路)或模拟)
但由于思路狭窄,树剖下考虑如何用线段树维护复活次数,而结论并未起到启发正解的作用,而是考虑如何分治(合并)解决
正解在于的出结论后,可以采用倍增死亡次数的做法,即问题之际上是将一段序列划分成若干段,其中每一段权值和大于等于k
那么分别从s,t向上倍增死亡点并记录死亡点数量,最终处理LCA处是否需要再复活一次即可
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define I int 4 #define C char 5 #define B bool 6 #define V void 7 #define LL long long 8 const I N = 2e5 + 3; 9 I n,k,q,s,t,num; 10 I lg[N],deep[N],f[N][18],MA[N],g[N][18]; 11 I tot,to[N << 1],nxt[N << 1],wgt[N << 1],head[N]; 12 LL len[N]; 13 struct S { I a; LL b; } sta[N]; 14 inline I read() { 15 I x(0),y(1); C z(getchar()); 16 while (!isdigit(z)) { if (z == '-') y = -1; z = getchar(); } 17 while ( isdigit(z)) x = x * 10 + (z ^ 48), z = getchar(); 18 return x * y; 19 } 20 inline V found (I z,I y,I x) { 21 to[++tot] = y, nxt[tot] = head[x], wgt[tot] = z, head[x] = tot; 22 to[++tot] = x, nxt[tot] = head[y], wgt[tot] = z, head[y] = tot; 23 } 24 inline I Bin (I l,I r,LL k) { 25 while (l < r) { I mid (l + r + 1 >> 1); 26 sta[mid].b <= k ? l = mid : r = mid - 1; 27 } 28 return sta[l].a; 29 } 30 V Dfs (I x,I father) { 31 g[x][0] = Bin (0,num,len[x] - k); sta[++num] = (S) {x,len[x]}; 32 for (I i(1);i <= lg[deep[x]]; ++ i) f[x][i] = f[f[x][i - 1]][i - 1]; 33 I p(1); while (g[g[x][p - 1]][p - 1]) g[x][p] = g[g[x][p - 1]][p - 1], MA[x] = p ++ ; 34 for (I i(head[x]),y(to[i]); i ;i = nxt[i],y = to[i]) if (y != father) 35 f[y][0] = x, len[y] = len[x] + wgt[i], deep[y] = deep[x] + 1, Dfs (y,x); 36 num -- ; 37 } 38 I LCA (I x,I y) { 39 if (deep[x] < deep[y]) swap (x,y); 40 while (deep[x] > deep[y]) x = f[x][lg[deep[x] - deep[y]]]; 41 if (x == y) return x; 42 for (I i(lg[deep[x]]); ~i ; -- i) if (f[x][i] != f[y][i]) 43 x = f[x][i], y = f[y][i]; 44 return f[x][0]; 45 } 46 signed main () { 47 n = read(), k = read(); deep[0] = -1; 48 for (I i(2);i <= n; ++ i) lg[i] = lg[i - 1] + (1 << lg[i - 1] + 1 == i); 49 for (I i(0);i < n - 1; ++ i) found (read(),read(),read()); Dfs (1,0); 50 q = read(); 51 while (q -- ) { s = read(), t = read(); I p(LCA (s,t)),sum(0); 52 for (I i(MA[s]); ~i ; -- i) if (deep[g[s][i]] >= deep[p]) 53 s = g[s][i], sum += 1 << i; 54 for (I i(MA[t]); ~i ; -- i) if (deep[g[t][i]] >= deep[p]) 55 t = g[t][i], sum += 1 << i; 56 if (len[s] + len[t] - len[p] * 2 >= k) sum ++ ; 57 printf ("%d\n",sum); 58 } 59 }View Code
T2:
首先,n <= 2e5 并且要求 sigma(l,r)f(l,r),很套路的转化,将区间贡献转化为单点贡献,这样将O(n^2)将为O(n)的枚举复杂度,其次,考虑问题的性质:
对于一段区间,选出权值和最大的一组数,并且不能选择相邻的数,又是非常套路的分治题,考虑区间合并的条件即可。
(首先考场上很RZ的做法为采用log函数确定取模意义下的最大值进行转移,错误点很显然,根据log函数的性质,其仅对乘法有转化为加法的放缩效应,
并且本题的数据范围并不需要放缩,可以直接采用long long)
考场考虑采用线段树维护(事实上线段树也是基于分治的思想,然而线段树是一种支持修改的动态分治结构),然而本题事实上并不需要修改
于是直接采用朴素分治即可。
有点累,先咕掉
T3:(心态一定要稳)
数据随机,O(n^2)暴力可过,具体做法为枚举当前消到第几回合,枚举每一行,通过i与i+1,i-1的l,r取max,min确定当前回合i的l,r,过程中记录f[i]
正解:考虑题目中消的性质,即每一回合消去外围一圈,考虑对于每一行如何消并不关心,只需要考虑每一行在第几回合消完
转化问题发现,由外向内逐层相消下,第i行完全消完的回合数等价于在该行所等放置的最大菱形对角线的一半(图上模拟即可)
考场上想到类似思路,但是未完全转化为菱形,而是(脑补扫描线)通过计算上下左右同时最大距离计算,很接近,然而是假的,
菱形才能保证回合数,可以构造边角缺失的菱形卡掉考场做法,
考场上由于时间与代码等,在证假后心态差点爆炸,补了一个暴力,然而中间截断的情况未考虑(刚做题时考虑到然而心态问题打暴力时忘了)分数挂没了
考虑如何判断是否能放置对角线长为2*k+1的菱形,设菱形中线为y,转化问题为判断是否对于所有i-k <= j <= i + k有:
y - (k - (i - j)) >= l[j] && y + (k - (i - j)) <= r[j] (i - k <= j <= i) && y - (k - (j - i)) >= l[j] && y + (k + (j - i)) <= r[j] (i < j <= i + k)
这里学习的一种思维方式:对于具有相同结构的不等式,考虑合并整体考虑,即取不等式组具有代表性的元素(通常为最值)进行求解
对于本题而言,发现问题等价与对于L = max (l[j] + (k - (i - j)),l[j] + (k - (j - i))),R = min (r[j] - (k - (i - j)),r[j] - (k - (j - i)))
是否存在一个y满足L <= y <= R(恒等变换),于是发现可以通过为RMQ预处理区间最值进行O(1)判断
(发现l[j]与j为定值,k,i为变量,很常用的方法,维护l[j]与j即可,计算时在加入k,i即可)
然而O(nlogn)显然无法通过,考虑优化
一个显然的性质为:|f[i] - f[i - 1]| <= 1
通过消除的定义即可发现,当一行完全消除时,其相邻行只可能在其之前,同时,与之后一个时刻消除
那么f[i]可以继承f[i - 1]的状态进行转移,那么考虑如何优化RMQ
发现根据上述结论,在计算f[i]时区间只会进行少量移动,即区间左端点单调右移,右端点左右移动范围最大为3
那么f[i]可以直接继承f[i -1]的区间并进行一定的修改即可,于是可以使用单调队列替代RMQ
考虑单调队列即利用单调性避免不可能发生的冗余决策,实际通常利用将最值移动到端点便于直接询问
那么可以维护四个单调队列,对应L上,R上,L下,R下
考虑对于L上,靠右的点显然具有更大作出贡献的可能性,若此时其值更大,那么显然可以删去左侧的点(出队),其余同理
接下来就是注意点:
由于右端点并不单调,那么存在右端点左移情况,而单调队列无法反悔(代价太大),
于是显然的思路为从小向大判断(当不满足条件时直接return),避免右端点左移
考虑上述做法会导致左端点的右移,然而由于整体单调队列也是右移的,那么我们只需要维护左单调队列的左指针进行移动即可
在判断时需要特判k==1的情况(模拟)
代码尽量不要复制,很容易出锅
回溯时可以利用变量记录原值,记得回溯时要符合边界情况
详见代码注释:
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define I int 4 #define C char 5 #define B bool 6 #define V void 7 #define UL unsigned long long 8 #define len (k - 1) 9 const I N = 5e6 + 3; 10 const I mod = 998244353; 11 I n,p,x,y,L,R,ans,l[N],r[N],f[N]; 12 I h1,h2,h3,h4,t1,t2,t3,t4,q1[N],q2[N],q3[N],q4[N]; 13 UL a,b; 14 UL Rand (UL &a, UL &b) { UL T(a),S(b); a = S; 15 T ^= T << 23, T ^= T >> 17, T ^= S ^ S >> 26; b = T; 16 return T + S; 17 } 18 void Init (I n,I L,I X,I Y, UL a, UL b,I l[],I r[]) { 19 for (I i(1);i <= n; ++ i) { 20 l[i] = Rand (a, b) % L + X; 21 r[i] = Rand (a, b) % L + Y; 22 if (l[i] > r[i]) swap(l[i], r[i]); 23 } 24 } 25 inline I fp (I a,I b) { I ans(1); 26 for (; b ;b >>= 1,a = 1ll * a * a % mod) 27 if (b & 1) ans = 1ll * ans * a % mod; 28 return ans; 29 } 30 inline B Check (I x,I k) { 31 L = max (l[q1[h1]] + q1[h1] + len - x,l[q3[h3]] - q3[h3] + len + x); 32 R = min (r[q2[h2]] - q2[h2] - len + x,r[q4[h4]] + q4[h4] - len - x); 33 return (k == 1 || L <= R); 34 } 35 inline V Figure (I x) { 36 I k(f[x]),p1(h1),p2(h2),tmp1(0),tmp2(0); B jud1(0),jud2(0); 37 while (h1 <= t1 && l[x] + x >= l[q1[t1]] + q1[t1]) t1 -- ; q1[++t1] = x; //更新上单调队列合法部分 38 while (h2 <= t2 && r[x] - x <= r[q2[t2]] - q2[t2]) t2 -- ; q2[++t2] = x; //更新上单调队列合法部分 39 if (h3 <= t3 && q3[h3] == x) h3 ++ ; if (h4 <= t4 && q4[h4] == x) h4 ++ ; //更新下单调队列不合法部分 40 while (h1 <= t1 && q1[h1] < x - len) h1 ++ ; while (h2 <= t2 && q2[h2] < x - len) h2 ++ ; //上单调队列收缩 41 if (p1 < h1 && q1[h1 - 1] == x - k) h1 -- , jud1 = 1; if (p2 < h2 && q2[h2 - 1] == x - k) h2 -- , jud2 = 1; //上单调队列舒张(注意满足要求才进行舒张) 42 if (k && (h3 > t3 || l[x + k] - x - k >= l[q3[h3]] - q3[h3])) tmp1 = q3[h3], q3[h3] = x + k; //局部更改下单调队列,便于回溯 43 if (k && (h4 > t4 || r[x + k] + x + k <= r[q4[h4]] + q4[h4])) tmp2 = q4[h4], q4[h4] = x + k; //局部更改下单调队列,便于回溯 44 if (Check (x,k + 1)) { f[x] = k + 1; //更新答案与下单调队列 45 if (k) if (tmp1) t3 = h3; else { while (h3 <= t3 && l[x + k] - x - k >= l[q3[t3]] - q3[t3]) t3 -- ; q3[++t3] = x + k; } 46 if (k) if (tmp2) t4 = h4; else { while (h4 <= t4 && r[x + k] + x + k <= r[q4[t4]] + q4[t4]) t4 -- ; q4[++t4] = x + k; } 47 } 48 else { 49 if (jud1) h1 ++ ; if (jud2) h2 ++ ; //上单调队列回溯 50 if (tmp1) q3[h3] = tmp1; if (tmp2) q4[h4] = tmp2; //下单调队列回溯 51 return ; 52 } 53 tmp1 = tmp2 = 0; jud1 = jud2 = 0; 54 if (p1 < h1 && q1[h1 - 1] == x - k - 1) h1 -- , jud1 = 1; if (p2 < h2 && q2[h2 - 1] == x - k - 1) h2 -- , jud2 = 1; 55 if (h3 > t3 || l[x + k + 1] - x - k - 1 >= l[q3[h3]] - q3[h3]) tmp1 = q3[h3], q3[h3] = x + k + 1; 56 if (h4 > t4 || r[x + k + 1] + x + k + 1 <= r[q4[h4]] + q4[h4]) tmp2 = q4[h4], q4[h4] = x + k + 1; 57 if (Check (x,k + 2)) { f[x] = k + 2; 58 if (tmp1) t3 = h3; else { while (h3 <= t3 && l[x + k + 1] - x - k - 1 >= l[q3[t3]] - q3[t3]) t3 -- ; q3[++t3] = x + k + 1; } 59 if (tmp2) t4 = h4; else { while (h4 <= t4 && r[x + k + 1] + x + k + 1 <= r[q4[t4]] + q4[t4]) t4 -- ; q4[++t4] = x + k + 1; } 60 } 61 else { 62 if (jud1) h1 ++ ; if (jud2) h2 ++ ; 63 if (tmp1) q3[h3] = tmp1; if (tmp2) q4[h4] = tmp2; 64 return ; 65 } 66 } 67 signed main () { 68 scanf ("%d%d%d%d%llu%llu",&n,&p,&x,&y,&a,&b); Init (n,p,x,y,a,b,l,r); 69 f[1] = f[n] = 1; h1 = h2 = h3 = h4 = 1, t1 = t2 = t3 = t4 = 0; q1[++t1] = q2[++t2] = 1; 70 for (I i(2);i < n; ++ i) f[i] = f[i - 1] - 1, Figure (i); 71 for (I i(1);i <= n; ++ i) ans = (ans + 1ll * fp (3,i - 1) * f[i] % mod) % mod; 72 printf ("%d\n",ans); 73 }View Code