CF专项

CF1567D:

  要求十进制数转化为十一进制数后的最大值

首先考虑十进制转化为十一进制的收益,也就是10^i转化为11^i

能够发现,位数越高所造成的额外贡献越大,因此不难想到贪心

能够分配高位就分配高位,最后不够分配则降位即可

CF1567E:

  首先看到操作一可以想到动态维护线段树

考虑如何求操作二,能够想到对于一个子串,其所能造成的贡献为

n * (n + 1) / 2,那么可以将序列划分为若干段,其中每一段为一个

最长上升子串,那么问题转化为如何动态维护子串长度(方案数)

  考虑要求一段区间的方案数,能够想到的是维护出以该下标开始

的最长上升子串利,维护的关键在于合并过程,显然只需要判断左右

区间的右左两端是否能合并即可,那么只需要再维护出以该点结尾的

最长上升子串,判断mid两侧最长上升子串长度是否相等即可,最终

区间方案数即为左右区间两侧方案数之和(若两侧区间能够合并则

减去合并的两串的方案再加上合并成的串长方案即可)

CF1567F:

  只有1,4两种数,要求和为5的倍数,同时每个位置最多只有

4个位置可以填数,能够发现只有该位置存在偶数个能填数的位置

才有解

  首先当没有位置时直接略过即可,当存在两个位置时,考虑抽象

出一种模型,发现只有两种颜色且相邻位置颜色不能相同,二分图染色

即可,通过奇环判断无解,考虑四个位置时,策略为将相对的位置

染成相同颜色,证明先咕。。。

CF1562D:

  问题即为奇数位置为+,偶数位置为-,同时每个位置上的数为+/-1

考虑这种+/-1的性质,即序列前缀和在每个时刻最多变化一次。

  分析删除操作,发现在删除一个数之后,后面所有位置的奇偶性质

全部改变,而要求的是一段前缀和为0,那么可以利用正负翻转的性质

进行操作,只需要找到形如x_x的位置即可,那么可以得到,对于区间

前缀和为0直接略过即可,对于前缀和为奇数只需一次操作(删除上述

下划线位置),而前缀和为偶数只需要随意删除一个位置即可转化为

第一种情况,考虑如何求出要删的位置,暴力O(n)的做法是显然的

显然并不需要数据结构进行维护,考虑所求物品的对称性质——x_x,

也就是通过求出x,可以确定要求位置的前缀和,然而由于存在负数,

前缀和并不具有单调性,这里可以转换一下,尽管前缀和不具有单调性

下标却具有单调性,而本题数据范围并不大,于是可以对于每种可能的

前缀和开桶,桶内记录数组下标,利用求出的值的索引,在对应桶内

二分下标即可

CF1562E:

  首先发现将若有子串以首字母分为若干组,暴力求解LIS显然不行,

考虑求解LIS的过程,本题特殊性在于所有子串按其在母串中首字母的

位置进行排序,考虑利用这个性质进行简化LIS运算,发现当在组内求解

时,由于同组首字母完全相同,于是组内LIS完全转化为下一组LIS也就是

问题子结构,这启发可以采用DP求解,考虑猜想对于同一组内若选择

位置i,则i之后的位置一定选择,证明考虑下一组在i后面的位置为j,那么

若j串长度小于i串,也定是j串字典序严格大于i串,i串之后显然可以选择

反之,考虑若i串下一个字母字典序小于j串对应字母同理,而若等于,也

就是i串为j串前缀,显然可以直接从j所在组i串位置进行选择,因此设

f[i]表示前i组的最长上升子序列,转移类比普通转移,发现问题在于求解

公共前缀,预处理LCP[i][j]表示母串i,j位置即之后的LCP

则有转移f[i] = max (f[i],f[j] + (n - i + 1) - LCP[i][j])

CF1580D:

  考虑将式子转化为人话,即一个子序列的价值为所选元素权值和乘以m

减去所选位置所有点对形成的区间中最小值之和。

  考虑到直接枚举点对,数据结构并不容易维护(枚举点对的时间复杂度

就难以接受)考虑转化为贡献,容易发现瓶颈在于点对区间最小值,考虑处理

区间最值一是数据结构,二则是单调处理出每个点控制的范围(然而本题并不

适用,因为随着所选序列的不同,区间并不一定),三则是笛卡尔树,对于

本题,建立笛卡尔树能够找到每个点最小值的范围,又因为发现一个点的贡献

实际上就是它做最小值的区间对数,也就是左右两侧端点数,因此可以直接

做树上背包,设f[i][j]表示i子树选择j个元素的最大值即可

  原来之前一直做的是假的树上背包,子树合并时间复杂度分析考虑将合并

转化为枚举树上点对即可,因此应先合并在更新size,否则时间复杂度会退化

代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define I long long
 4 #define C char
 5 #define B bool
 6 #define V void
 7 #define S short
 8 #define D double
 9 #define LL long long
10 #define LD long double
11 #define UI unsigned int
12 #define UL unsigned long long
13 #define P pair<I,I>
14 #define MP make_pair
15 #define a first
16 #define b second
17 #define lowbit(x) (x & -x)
18 #define debug cout << "It's Ok Here !" << endl;
19 #define FP(x) freopen (#x,"r",stdin)
20 #define FC(x) freopen (#x,"w",stdout)
21 #define memset(name,val,typ,len) memset (name,val,sizeof (typ) * len)
22 const I N = 4005;
23 I n,m,a[N],f[N][N];
24 I cnt,sta[N],c[N][2],siz[N];
25 inline I read () {
26     I x(0),y(1); C z(getchar());
27     while (!isdigit(z)) { if (z == '-') y = -1; z = getchar(); }
28     while ( isdigit(z))  x = x * 10 + (z ^ 48), z = getchar();
29     return x * y;
30 }
31 inline V Max (LL &a,LL  b) { a = a > b ? a : b; }
32 inline V Min (LL &a,LL  b) { a = a < b ? a : b; }
33 inline I max (I &a,I &b) { return a > b ? a : b; }
34 inline I min (I &a,I &b) { return a < b ? a : b; }
35 inline V swap (I &a,I &b) { a ^= b, b ^= a, a ^= b; }
36 inline I abs (I &a) { return a >= 0 ? a : -a; }
37 #ifdef mod 
38 inline V Mod1 (I &a,I b) { a = a + b > mod ? a + b - mod : a + b; }
39 inline V Mod2 (I &a,I b) { a = a - b <  0  ? a - b + mod : a - b; }
40 #endif
41 inline P operator + (const P &a,const P &b) {
42     return MP (a.a + b.a,a.b + b.b);
43 }
44 inline P operator - (const P &a,const P &b) {
45     return MP (a.a - b.a,a.b - b.b);
46 }
47 V Dfs (I x) {
48     f[x][1] = (m - 1) * a[x], siz[x] = 1;
49     if (c[x][1]) {
50         Dfs (c[x][1]);
51         for (I i(min (siz[x],m)); ~i ; -- i) 
52             for (I j(min (siz[c[x][1]],m - i)); ~j ; -- j) 
53                 Max (f[x][i + j],f[x][i] + f[c[x][1]][j] - 2 * i * j * a[x]);
54         siz[x] += siz[c[x][1]];
55     }
56     if (c[x][0]) {
57         Dfs (c[x][0]);
58         for (I i(min (siz[x],m)); ~i ; -- i) 
59             for (I j(min (siz[c[x][0]],m - i)); ~j ; -- j)
60                 Max (f[x][i + j],f[x][i] + f[c[x][0]][j] - 2 * i * j * a[x]);
61         siz[x] += siz[c[x][0]];
62     }
63 }
64 signed main () {
65     n = read (), m = read ();
66     for (I i(1);i <= n; ++ i)
67         a[i] = read ();
68     for (I i(1);i <= n; ++ i) {
69         while (cnt && a[i] < a[sta[cnt]]) cnt -- ;
70         c[i][1] = c[sta[cnt]][0], c[sta[cnt]][0] = i;
71         sta[++cnt] = i;
72     }
73     Dfs (c[0][0]), printf ("%lld\n",f[c[0][0]][m]);
74 }
View Code

 CF1292B:

  一类套路题,考虑数据范围极大,然而发现给出的一次函数中的k至少为2

也就是下一个点的坐标横纵至少在当前点两倍,于是有效点数只有log级别

  于是考虑直接暴力枚举以哪一个点为出发点,然后考虑选点策略,容易想到

在平面直角坐标系中先选左下角,还有空余时间的话选择右上角,证明考虑

首先显然的是左下角坐标密集,然后由于每一次坐标翻倍因此左下角距离一定不大于

当前点的下一个点

CF1304C:

  考虑模拟过程,发现若以时间为坐标轴,其实是一个点所能到达的范围的不断拓展

也就是维护处这个点在每个时刻所能到达的范围,再与限制取min即可

CF1313D:

  考虑转化题意为m个点n条线段求若干条线段进行覆盖后被奇数条线段覆盖的点的

最大值

  首先比较套路的想法为范围很大,然而n只有e5级别,离散化并不影响,另一个

突破点在于限制每个点所能被覆盖的最大线段数不超过8,考虑状压。

  发现离散化之后每相邻两点之间所代表的线段的状态一定是相同的,也就是一条

线段可以以长度(贡献)为代表抽象出来,那么直观的想法为状压每条线段的状态

  于是设f[i][j]表示当前考虑到考虑到前i条线段,第i条线段状态为j的答案,考虑k

不大于8实际上是栈内元素不超过8个,因此j的状态可以有枚举当前线段被那些线段

进行覆盖,考虑若当前线段正在入栈,那么其由不包含当前线段的状态转移过来,否则

由上一个状态转移过来,而若当前线段正在出栈,若状态中包含当前线段显然不合法,

若不包含则直接继承即可,转移过程中需要注意累加当前线段的贡献

  这里推荐__builtin_系列函数,实测复杂度接近O(1),位运算较好用

CF1322B:

  首先这类大量位运算题按位考虑,考虑问题枚举点对的形式可以转化为每个点的贡献

发现异或运算高位并不影响低位,于是只需要考虑每一位及之前的位所形成的数在该位下

是否位1即可

  于是发现具有单调性,即若a + b在二进制下第k位为1,则a + b范围为[2^k,2^(k+1)-1]

或[2^(k+1)+2^k,2^(k+2)-2](其中a + b都只考虑后k位),于是记录每个数考虑后k位的结果

排序后二分范围即可

CF1322C:

  考虑如何去考虑这个问题,发现最后所求的一定是若干集合之和的GCD,那么从

小集合到大集合考虑,那么发现(a,b) = (a,a + b),拓展到(a,b,c,a + b,a + c,b + c,a + b + c)

 = (a + b + c),考虑其实只需要关心基本元素的GCD即可

  于是直接求?并不是,发现当两个数指向的集合完全相同时,两个点的val需要被合并

这是因为(a,a,b)!= (2a,b),于是哈希合并即可

(题解中一种比较有启发意义的思考方式为:考虑从左右两侧入手显然并没有明显区别,

然而左侧考虑无法进行,右侧考虑却十分容易,本质原因在于左侧提供关系,右侧提供

权值,而集合关系在右侧又可以通过权值进行表示(容斥),大概高手可以一开始就说

不妨从考虑右侧)

CF1325D:

  考虑实际上是异或与和的关系,考虑关系式:a + b = (a ^ b) + 2 * (a & b)

发现若有解则(a ^ b) <= a + b且(a ^ b)与a + b奇偶性相同

  考虑如何构造发现v - u == 2 * (v & u),那么直接拆成{u,v & u,v & u}即可,然而考虑更优

的情况,当v & u & u == 0时可以直接合并,因为互不影响

 CF980D:

  fengwu推荐的好题

  考虑很容易想到数论,两数之积为完全平方数可以想到唯一分解,暴力做法显然为

O(n^2)暴扫,对于当前元素,扫描之前所有元素进行判断,考虑优化,我们发现对于已

知合法组,只需要判断组内一个元素与当前元素是否合法即可,显然的结论。

  于是优化为O(n^3),题解的做法是离线处理,考虑利用上述判断预处理并查集:将

所有元素进行分组,正确性是显然的,将所有元素进行划分等价于一段区间进行划分,

最后O(n^2)统计区间集合数即可

 CF620F:

  正解莫队+Trie树(暴力+卡常=碾标算)

  考虑O(n^2)暴扫,问题转化为判断元素与集合的关系,于是我们设f[i]表示右端点为i的

答案,由于暴扫一定能枚举到所有点对,我们不妨设当前左端点必定为答案端点之一,于是

有f[i] = max (f[i - 1],sum[min (a[i],a[j]) - 1] ^ sum[max (a[i] ^ [j]))),卡常笔记中有一些卡常技

巧,下面附上代码(1887ms):

 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 S short
 8     #define D double
 9     #define LL long long
10     // #define LLL __int128
11     #define LD long double
12     // #define LLD __float128
13     #define UI unsigned int
14     #define UL unsigned long long
15     #define lowbit(x) (x & -x)
16     #define ot(x) cout << #x << " = " << x << "  ",
17     #define debug cout << "It's Ok Here !" << endl;
18     #define FP(x) freopen (#x,"r",stdin)
19     #define FC(x) freopen (#x,"w",stdout)
20     #define Tem template<typename T>
21     #define memset(name,val,typ,len) memset (name,val,sizeof (typ) * (len))
22     #define memcpy(name1,name2,typ,len) memcpy (name1,name2,sizeof (typ) * (len))
23      
24     struct A1 {I a,b;};
25     inline I read () {
26         I x (0), y (1); C z (getchar ());
27         while (!isdigit (z)) { if (z == '-') y = -1; z = getchar (); }
28         while ( isdigit (z))  x = x * 10 + (z & 15), z = getchar ();
29         return x * y;
30     }
31     // I top; C prt[40];
32     // inline V print (LLL x,C z) {
33     //     if (x == 0) putchar ('0');
34     //     if (x <  0) putchar ('-'), x = -x;
35     //     while (x) prt[++top] = x % 10, x /= 10;
36     //     while (top) putchar (prt[top --]);
37     //     putchar (z);
38     // }
39     Tem inline T abs (T &a) { return a >= 0 ? a : -a; }
40     Tem inline V Max (T &a,T  b) { a = a > b ? a : b; }
41     Tem inline V Min (T &a,T  b) { a = a < b ? a : b; }
42     inline V swap (I &a,I &b) { a ^= b, b ^= a, a ^= b; }
43     #ifdef mod 
44     Tem inline V Mod1 (T &a,T b) { a = a + b > mod ? a + b - mod : a + b; }
45     Tem inline V Mod2 (T &a,T b) { a = a - b <  0  ? a - b + mod : a - b; }
46     Tem inline T Mod3 (T  a,T b) { return a + b > mod ? a + b - mod : a + b; }
47     Tem inline T Mod4 (T  a,T b) { return a - b <   0 ? a - b + mod : a - b; }
48     #endif
49     inline A1 operator + (const A1 &a,const A1 &b) { return {a.a + b.a,a.b + b.b}; }
50     inline A1 operator - (const A1 &a,const A1 &b) { return {a.a - b.a,a.b - b.b}; }
51     inline B operator < (const A1 &a,const A1 &b) { return a.a == b.a ? a.b < b.b : a.a < b.a; }
52     const I N = 5e4 + 3, M = 1e6 + 3, Q = 5e3 + 3;
53     I n,m,a[N],b[M],c[N],e[N],ans[Q];
54     A1 d[Q];
55     signed main () {
56         n = read (), m = read ();
57         for (I i(1);i <= 1000000; ++ i)
58             b[i] = b[i - 1] ^ i;
59         for (I i(1);i <= n; ++ i)
60             a[i] = read (), c[i] = b[a[i]];
61         for (I i(1);i <= m; ++ i)
62             d[i] = {read (),read ()};
63         for (I i(1);i <= n; ++ i) {
64             I tmp1 (0);
65             for (I j(i);j <= n; ++ j) 
66                 e[j] = tmp1 = max (tmp1,c[i] ^ c[j] ^ (a[i] < a[j] ? a[i] : a[j]));
67             for (I j(1);j <= m; ++ j) if (i >= d[j].a && i <= d[j].b)
68                 Max (ans[j],e[d[j].b]);
69         }
70         for (I i(1);i <= m; ++ i)
71             printf ("%d\n",ans[i]);
72     }
View Code

 CF1572E:

  确实是神仙DP,容易想到子状态为i凸多边形划分j次,初始思路为记忆化搜索,然而仔

细分析发现本质上是暴力,时间复杂度O(2^n),事实上要少一点
  考虑二分最小矩形面积为x,利用DP判断可行性

  正解为区间DP,考虑仍然定义f[i][j]表示区间i~j的答案,考虑枚举分界点k,于是可以转

化为子问题,发现合并过程需要判断中间部分面积,于是定义辅助数组g[i][j]表示区间i~j的剩

余面积,有f[i][j] = max (f[i][j] , g[i][k] + g[k][j] + S(i,k,j) >= x ? f[i][k] + f[k][r] + 1 : f[i][k] + f[k][r]

  注意由于方程定义为区间l~r划分一个三角形,因此区间两侧三角形一定与当前三角形相邻

  时间复杂度要算对

CF1578L:

  刚看确实没有直接的思路,思考问题的形式,发现与生成树类似,原因在于我们只需要

关注两点之间路径边权的最小值,那么两点之间路径的最优选择显然是最大生成树上的路径

权值的最小值,可以通过反证法证明两点之间所有路径上权值最小值的最大值为最大生成树

上两点路径权值的最小值。

  考虑构建最大生成树,那么首先断掉的边显然是边权最小的边,显然的子结构,考虑此

边两侧的答案,容易想到反复横跳显然不优于先走完一个子树,那么可以两颗子树分别考虑

设f[i]表示走完i子树的最大体宽,那么有f[i] + sum[i] <= w && f[i] + sum[i] <= f[j],于是对于该

边两侧DP即可。

  需要注意的问题是如何快速求出联通快内最小权值,容易通过重构树解决在kruskal过程

中当前边一定为左右两联通块的最小边权,因此边建立最小生成树边DP即可

CF1575M:

  一开始我的思路为观察问题的形式,可以转化为每个点控制一个范围,于是想到多源最

短路,以黑点为源跑最短路,由于求欧式距离,因此记录每个点的前驱节点比较距离

  然而是假的,考虑dijkstra求最短路的原理为贪心,即该点出队时意味这该点的最短距离

已经确定,并完成对其他点的更新,而对于此题该做法假的原因在于dijkstra过程中按连边遍

历节点,然而距离计算却并不如此,因此当一个点出队时并不一定确定了该点的最小值,并

且之后会影响真正最小值的其他点的更新

  正解为斜率优化DP,考虑对于点(i,j),不妨将其最小距离转化为四部分进行考虑以左上

部分为例,发现对于每一列只有最下一行可能成为最优解,因此只需要考虑i个点,一种常用

的斜率优化/CDQ套路为钦定更优法,不妨假设(x1,y1)优于(x2,y2),那么有(x1 - i)^2 + (y1 - j)^2 <

(x2 - i)^2 + (y2 - j)^2,拆开,发现只有j为变量,将j提取到一侧,可得斜率优化是,单调队列

维护即可

CF1575B:
  仍然是比较套路与巧妙的转化,考虑类似于序列一题,由于对于每个点分别计算向左向

右前缀和复杂度无法接受,于是设法将其转化为整个序列的前缀和做差来避免数据的特殊性

  考虑本题,若暴力做圆显然需要Check 平面上每个点,于是不妨将其转化,考虑以平面

上每个点为圆心做圆,不妨设当前半径为r   那么问题转化为判断以原点为圆心的圆上是否存

在一个点被至少k  个圆覆盖,考虑套路的计算角度,线段树上区间修改即可,对于圆的半径

显然可以二分答案

posted @ 2021-10-28 19:54  HZOI_LYM  阅读(72)  评论(0编辑  收藏  举报