慢慢地发现每次打题解会很麻烦而且费时间,这篇博客留作思路汇总,给自己警醒。
游戏 (模拟36)
某天T3,思路难点在于把每个状态的点区分出来,也就是拆点,然后在给不同状态的点连边,进行最短路操作。
对于图上需要操作走动的点来说,往往不同的状态之间转移会很困难,在实现过程中也很难实现,不如把不同状态的点拆成多个状态互不影响的点,在分别考虑其转移条件,然后在区分对待,这样会使不同状态之间的考虑化简,之后考虑好状态之间的转移就可以了。
题目思路挺不错的。
影子 (模拟41)
并查集+lca找两点距离。
这题的并查集用的很巧妙,将点权从大到小排序,用并查集维护点集,点集中点权最小的点为当前枚举到的点。
然后有一个很巧妙的思路,维护当前点集的直径和该直径对应的两个端点,当我们搜到合并两个点集的时候,将端点两两结合,有四种情况,在加上两个点集本身的直径取最大,就是在当前点的最优答案。新点集的直径的端点一定属于上述四个端点之一,正确性可以反证法证明。
注意点集一定要合并,情况要考虑全。
夜莺与玫瑰 (模拟41)
莫比乌斯函数中的mu函数的妙用,4维到1维的优化,用到的等差数列求和。
玫瑰花精 (模拟41)
线段树在序列上的操作,维护了一些神奇的东西。
l,r,mid,p,l维护最左边的鸟的位置,r维护最右边的鸟的位置,mid维护距离两只相邻鸟的最长距离,p维护mid值的位置。
查询只查询根节点1的p值即可,注意与1,p,n的比较。
删点和插点操作相似,注意多种情况的枚举,也就是左儿子最右边的鸟,右儿子最左边的鸟的关系。
C (模拟43)
线段树+贪心+三分。
这几天的线段树维护一个特定的区间的题挺多的,加上模拟42的T2,可以看一看。
线段树维护的方法:先保证左端点一定满足条件,再把右端点插进去,找其中的最大or最小的右端点,实现贪心。
这道题还用到了三分,因为答案关于选特殊的个数是单谷函数。
至于贪心的证明,还需要再自己考虑考虑。
F (模拟44)
线段树优化dp。
这道题dp的思路还不错,毕竟考试的时候想了一个性能比较差的dp。
然后就是一个线段树优化了。
其实就是优化掉内层循环,找一下循环里面的性质,这道题是其中的不变量$dp_i-i$和$dp_i+i$,和给整个区间加上的相同的值。
可以拓展到更多的情况,比如区间减,区间赋值,单点查询,区间查询等。
具体依据具体性质而定。
kill (模拟45)
二分+贪心。
其实二分不难看出来,但是考场上真的有些懵了,二分没看出来,打了个傻逼set。
然后排序,贪心就行了。
我们要使每个人的花费最优,而我们在排序的基础上,枚举每个人所在的位置,从左到右找第一个符合条件的位置,因为后面的所有人都不会将我找到的这个位置左边的点作为最优点。
贪心即可,还有一个小性质,每个小怪兽的固定花费都是不变的,虽然没啥用,但是也是需要积累一下的。
beauty (模拟45)
题目转化问题很惊喜,自己在转化题目的能力方面做的还是太差了。
根据题意,很容易想到去找链找最大,但是这显然复杂度不对,然后找性质。
其实吧,就离正解不远了,但是自己没能突破这一哆嗦。
当自己卡在某一个思路的时候,想想突破,另辟蹊径,找一下意想不到的地方。
这道题在找(kan)了n年(bian)性(ti)质(jie)后,会惊喜的发现,可以把每个边对答案的贡献单独拿出来算。
然后这道题就解决了。
set (模拟46)
前缀和。
这道题前缀和不是很难,但是它要求对前缀和的理解和对模的意义有较高要求,总之考试的时候没想到。
A掉简单想到难。其实主要的还是一个在模n意义下最多只有n个不同的取值,然后就可以断定,一定有一个连续的区间符合条件,然后前缀和就行了。
read (模拟46)
思路不难但是打死想不到系列。
性质一:一个序列里最多有一种元素是整个序列的一半以上,减到合法后不会有新的元素种类不合法,可以简单证明。
然后乱搞就行了,题目卡的地方就是不让开数组,这就需要我们找一个不合法的元素。
然后“所以我们用两个变量 id, cnt, cnt 初始为 0. 然后生成每一个 A[i], 如果 cnt==0, 那么就令 id=A[i], cnt=1, 否则如果 id==A[i], 则 cnt++, 如不等于 , cnt--. 最后只要再扫一遍求出 id 的出现次数即可 .
因为如果有书超过 (N+1)/2, 那么就以为这它比其他所有书的和都多 , 那么 cnt 怎么减都不会小于0, 如果没有那 id 随便是哪个都没有关系了 . 最后再求这种书要减少多少个就可以了 .”
题解抄下来了。
蔬菜 (模拟50)
二维莫队。
这道题其实也不是很难,但是考场上确实没想到,一个比较板子的东西。
莫队的计算也比较简单,总之练了一下莫队。
联盟 (模拟50)
树的直径综合。
首先性质,断边肯定在直径上。
然后就是各种求树的直径的方法了,两遍Dfs求直径,还有就是dp求直径,dp的方法适用与有根树的直径。
u (模拟53)
矩阵上的差分,挺好的一道题。
自己的一点理解吧,这个差分利用了题中所说的下直角三角型的特性,差分的链只会沿两种路线走,我们可以分别维护这两种走法,然后两个数组分别差分,然后把两个差分数组合并起来,在扫一边整个矩阵。
差分好题。
v (模拟53)
记忆化搜索+状压$DP$。
位运算一定要用好,尽量适合自己的下一步转移。
复杂度是可以证明的,每个状态只会被枚举一次,复杂度不是很高。
记忆化状态,然后用$HASH$表存起来,注意$HASH$表查询的限制条件,如果限制条件很多的话,$HASH$就不怎么适用了,$HASH$数我觉得$19260817$就不错。
记忆化一定要好好分析复杂度,不然很容易写挂。
w (模拟53)
树形DP好题,很大程度上在于$DP$的定义和转移。
不难看出题目的限制在于2边是否要翻转,一个小性质就是每条需要翻转的边只会被翻转一次,贡献就只是2边的贡献就行了。
$f_{0/1,i}$表示点i是否向上连一条边,$w_{0/1}$表示他的儿子是否有一条边与父亲相连,也就是儿子中有奇数条边与当前点相连(其他的偶数条边可以经过当前点而不会对答案产生贡献),还能从中发现,我的链的条数就是奇点(有奇数条需要翻转的与该点相连的边的点)的个数/2。
$f$和$w$都是二元组,第一维是当前点的子树中最小的奇点的个数,第二维是当前状态下的最小的翻转长度。
转移用$w$转移$f$,转移不是很难想,重要的还是$DP$定义,自己$DP$定义能力还是不够。
这道题转移挺麻烦的,而我挺懒的,咋办呢。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<iostream> 3 #define LL long long 4 #define HZOI std 5 using namespace HZOI; 6 const int N=1e5+3; 7 const int INF=0x3f3f3f3f; 8 int n; 9 int tt,first[N],vv[N<<1],nx[N<<1],ww[2][N<<1]; 10 int co[N],go[N],ans[2]; 11 pair<int,int> dp[2][N]; 12 void Dfs(int ,int ); 13 inline void Add(int ,int ,int ,int ); 14 inline int read(); 15 int main() 16 { 17 n=read(); 18 tt=1; 19 for (int i=1,a,b,c,d; i<n; ++i) 20 { 21 a=read(),b=read(),c=read(),d=read(); 22 Add(a,b,c,d),Add(b,a,c,d); 23 } 24 Dfs(1,0); 25 printf("%d %d ",dp[0][1].first/2,dp[0][1].second); 26 return 0; 27 } 28 void Dfs(int k,int father) 29 { 30 pair<int,int> w[2]; 31 w[0]=make_pair(0,0); 32 w[1]=make_pair(INF,INF); 33 for (int i=first[k]; i; i=nx[i]) 34 { 35 int ver=vv[i]; 36 if (ver==father) continue; 37 co[ver]=ww[0][i],go[ver]=ww[1][i]; 38 Dfs(ver,k); 39 pair<LL,LL> temp0,temp1; 40 temp0=w[0],temp1=w[1]; 41 w[0]=min(make_pair(dp[1][ver].first+temp1.first,dp[1][ver].second+temp1.second), 42 make_pair(dp[0][ver].first+temp0.first,dp[0][ver].second+temp0.second)); 43 w[1]=min(make_pair(dp[0][ver].first+temp1.first,dp[0][ver].second+temp1.second), 44 make_pair(dp[1][ver].first+temp0.first,dp[1][ver].second+temp0.second)); 45 } 46 if (go[k]!=2 && co[k]!=go[k]) 47 { 48 dp[0][k]=make_pair(INF,INF); 49 dp[1][k]=min(make_pair(w[1].first,w[1].second+1),make_pair(w[0].first+1,w[0].second+1)); 50 } 51 if (co[k]==go[k]) 52 { 53 dp[1][k]=make_pair(INF,INF); 54 dp[0][k]=min(make_pair(w[1].first+1,w[1].second),w[0]); 55 } 56 if (go[k]==2) 57 { 58 dp[0][k]=min(make_pair(w[1].first+1,w[1].second),w[0]); 59 dp[1][k]=min(make_pair(w[1].first,w[1].second+1),make_pair(w[0].first+1,w[0].second+1)); 60 } 61 return ; 62 } 63 inline void Add(int u,int v,int color,int goal) 64 { 65 vv[++tt]=v,nx[tt]=first[u],first[u]=tt,ww[0][tt]=color,ww[1][tt]=goal; 66 } 67 inline int read() 68 { 69 int nn=0; char cc=getchar(); 70 while (cc<'0' || cc>'9') cc=getchar(); 71 while (cc>='0' && cc<='9') nn=(nn<<3)+(nn<<1)+(cc^48),cc=getchar(); 72 return nn; 73 }
溜了。
y (模拟54)
状压$DP$。
一道好题,将我的状压思路开阔了一点。
将通常的枚举起点或终点的思路转化成枚举中间点,有利于减少状态转移。
把状压的状态一分为二,形成两个状压,最后拼凑成一个完整的状态。
其中要注意状态的完整和始末状态。
x国的军队 (模拟b「六人AK」)
牛逼题。
要剩下的人最少,把b-a从大到小排序。
排列组合 (模拟b「六人AK」)
牛逼题,打表找规律(我都不知道自己咋$A$的)。
正解:把$C_n^i$换成$C_n^{n-i}$,就会发现这是把一个大小为$2n$的集合里,在前$n$个选$i$个,后$n$个选$n-i$个,然后$Σ$,就是$C_{2n}^n$了。
回文 (模拟b「六人AK」)
牛逼题,前缀和。
注意一下转移的方向,一定要从有值的地方转移到无值的地方,这个要记一下。
Equation (模拟56)
$DFS$序+线段树+分类讨论。
式子很好化,都转化成有$x_1$的形式,然后分类讨论就行了。
考试的时候脑子有点不好使,本来是前缀和,区间修改的写成单点修改了,然后暴力分都没拿到。
这个暴力思路就是$DFS$暴力改,然后想一下优化这个过程。
我们发现每次修改的都是一个子树中的信息,这样的话就可以想到$DFS$序优化,把树上信息转化成链上,然后用线段树维护,区间修改即可。
不过这样会T。。SB卡常题。。
然后把线段树换成树状数组差分即可。
嘟嘟嘟 (模拟60)
约瑟夫问题,优化循环次数。
考虑二分,二分出$cnt$,表示经过了$cnt+1$次操作,$ans+(cnt+1)*m>=i+cnt$,二分出答案就好了。
时间复杂度是调和级数级别的。
其实$cnt$可以直接求出来。。
天才绅士少女助手克里斯蒂娜 (模拟60)
推式子,数据结构板子题。
推式子可以考虑容斥的思想,总方案减去不合法方案,还有就是可以将式子中的$i,j$瞎转化一下,同种变量的下标尽量相同,更有利于推导和对式子的理解,然后瞎搞就行了。
线段树的话,懒标记都不用,裸的单点修改区间查询。
Tree (模拟62)
结论题,答案为边权和相加。
感性理解一下,没条边至少有一个贡献,而当经过的边最少时,也就是每条边只经过一次时,结果最小。
证明一下:我们在整个树中选一条边权最大的边,每次类似于$BFS$在边的两端扩展,每次走向次大边,这样走下去等遍历完整颗树,答案为最优也就是边权和。
证毕。
trade (模拟64)
反悔贪心,堆维护一下就好了,注意加入元素的含义。
sum (模拟64)
公式推导,莫队。
最主要的是公式,手玩一下会发现上一行对当前行会产生2倍的贡献,画一个杨辉三角就出来了。
递推式:$S_{n,m}=S_{n,m-1}+C_n^m$
$S_{n,m}=2*S_{n-1,m}-C_{n-1}^m$
幻魔皇 (模拟67)
又是一个神仙打表题。
树很有特点,有很多树是相同的,然后可以找其中的规律。
对于$LCA$为黑点跟白点的情况我们可以分开考虑,然后直接上$fib$就好了,主要还是找这种图的规律,合并相同情况,不同情况分开讨论,想到这些以后,$fib$的使用不算很难。