CSP冲刺刷题记录
10.14
[TJOI2017]异或和
求所有区间和的异或和
处理前缀和
对每一位而言,看是否发生相减得1的情况,总之要看几种相减借位的情况
kth | kth | ||||
---|---|---|---|---|---|
\(Si\) | 1 | 小 | $ S_i $ | 1 | 大 |
\(Sj\) | 1 | 大 | \(S_j\) | 0 | 小 |
\(S_i\) | 0 | 大 | $ S_i $ | 0 | 小 |
$ S_j $ | 1 | 小 | $ S_j $ | 0 | 大 |
用树状数组维护即可
细节题,需要维护前缀和为0,所以树状数组下标整体右移一位
noip2016组合数问题
杨辉三角+矩阵前缀和
他这个前缀和有点怪,总之wa了一次
我把第0行和第0列忽略了,总之记住递推的起始条件:
\[ C_n^0 = 1, C_0^0 = 1 \]
noip2003 加分二叉树
自己想到30%的样子
这个算法是自下而上式的,令人欣慰的有序状态空间
利用了区间dp的思想,用 $f[i][j] $表示i到j所组成的子树的最优子结构
想一下二叉树的中序遍历的性质:只有左边是右边的左儿子,和右边是左边的右儿子的情况。
二叉树的分取决于谁是根:所以枚举根,左子树和柚子树自然也确定了
\[ f[i][j] = MAX\{ f[i][k-1]*f[k+1][j] + f[k][k] \} \]
注意$ root[i][i] = i $置初值
[LnOI2019SP]龟速单项式变换(SMT)
有如下定义:若正整数序列aa中存在连续若干个正整数的和为m的倍数,则这个正整数序列a被称为“司m序列”。
给定n和m,你需要知道长度为n的任意正整数序列a是否都是"司m*序列"。
神秘居然让我试出来了,首先\(n < m\)一定能构造出司m的反例,比如全填1
正解是鸽巢原理:
长度为\(n,(n\ge m)\)的序列:求出他的前缀和\(sum[i]\)
对于这个单调增的序列,里面的取值可以有很多情况,最极端可以有n个值互不相同
由于有大于等于 m 个值,所以在模m环境下一定有两个的值相同
故一定存在\(sum[i] \equiv sum[j] (mod\ m)\)
则存在\(sum[i]-sum[j] \equiv 0 (mod\ m)\)则他是一个司马序列
10.16
[LnOI2019SP]快速多项式变换(FPT)
一道有意思的构造题,贪心利用高位,最后剩下的放到\(a_0\)就可以了
把题搞错了发现他\(n\le100\)且\(a_i < m\)这就每一项都需要构造
出题人题解:
不难发现,设\(a_na_{n-1}\cdots a_2a_1a_0\)是一个m进制数,将此数转换为10进制即为f(m)的值。因此只需要倒序输出f(m)在m进制下每一位的值即可。
满足条件的多项式可以证明只有一个
直接取模倒序输出
hanoi
n阶m塔汉诺塔问题
现在搞出了\(O(n^2m)\)的算法:
\[ f[n][m] = min\{ 2\times f[k][m]+f[n-k][m-1] \} \]
从2,3开始循环,前面的可以直接置答案,然后不会了
据说单调指针优化可以有\(O( n^2 )\)
颓了一下:把Python简单学了一下
然后A了国王游戏
定义类,自定义比较函数
class Node:
def __init__(self,a,b):
self.a = a
self.b = b
def _cmp(a,b):
if a.a*a.b>b.a*b.b: return 1
return -1
定义数组
lis = []
输入需要split,而且还要强制转类型,不然他以为这个是string
for i in range(n): #这里是从1到n
s = raw_input().split()
lis.append(Node(int(s[0]),int(s[1])))
自带指针:
for i in lis:
#访问 i.a 以及 i.b
三角形牧场
交了12次
把搜索能犯得错都犯了
枚举每个木棒在哪个边
要判三角形是否合法,而且不能一次性把6种排列一次hash,因为有的等价答案可能扩展出其他不同的答案,要加只加可行性剪枝和重复性剪枝,重复性剪枝保证减掉的是完全相同的枝
判三角形是否合法
if(a+b>c && a+c>b && b+c>a) return true;
return false;
再用海伦公式时:所有参数必须以double形式参与运算总之我出了精度的锅
神秘乱搞题:子序列
给定一个长度为N(N为偶数)的序列,问能否将其划分为两个长度为N/2的严格递增子序列
正解是dp看不懂,但是我们写了乱搞,找到了充分不必要条件,神秘总之过掉了
LDS >= 3就是no,这个好证,如果有下降的两个数必定在两个序列,来了第三个就死了。
然后有hack数据
4 4 1 2 3
6 6 1 2 3 4 5
6 1 6 2 3 4 5
容易特判 。
会议
树形dp水题
大概就是求所有点到某个点路径和最小
经典换根dp,套路就是先收回根然后下放下去得到每个点为根时的答案
\[ f[x] = \sum_{v \in son } f[v] + size[v] \\ 下放:f[v] = f[x] - size[v] + (n-size[v]) \]
还没有涉及到求最值,找时间复习一下在成都的那道题
逃离僵尸岛
图论水题
把一个点extend一下,然后点权为边权
dij一下,第一次交时候数组开小了绝了
10.17
豪华游轮
dfs20分
正解贪心配合dp
就是先用forward冲,然后转180度,那backward变成朝原来方向冲,虽然不一定凑出180度但是尽量靠近
背包凑180度,可达性01背包,我跑了两次,分别是把方向正负换了一下的,求最靠近180的dir
最后用了一下三角函数
\[ 弧度 = \frac{角度*\pi}{180} \\ \pi = acos(-1) \]
对于backward:
\[ x = x-dis\times cos(dir)\\y = y-dis \times sin(dir) \]
单词背诵
hash,hash_table,以及尺取法
初始化l=r=1,加入集合只是r指针的操作,移除集合只是l指针的操作,在合法期间更新ans
10.20
停课开始了
今天上午去医院了,下午来补考试,然后做了超级久,最后一个没摸出来,但是前面的两道题都没挂
就要保持这个心态
然后学了一下prufer
[HNOI2004]树的计数
\[ ans = \frac{(n-2)!}{\prod_{i=1}^{n} (d_i - 1)!} \]
注意乘爆,注意树不连通即di = 0,以及n=1是d1只能等于1否则无解
计数类好题
CF915E Physical Education Lessons
本来是区间赋值区间求和的裸题,但是n达到了1e9
用珂朵莉
然后相当于复习了一下
维护01的sum根本就不用写个sum函数,直接在assgin里面维护就可以了,可以做到O(assgin)维护
最后时间复杂度也是省了一倍
10.21
今天考了一套题非常好,以后要翻出来看
晚上做了一道dp
[USACO08FEB]修路Making the Grade
求把整条路通过加高变矮形成非严格单调增或者非严格单调减,最小费用
拓宽了我的视野:dp状态的 数学概况力 以及 泛化状态表示
根据贪心,所有修改后的值都是在序列里存在过的值
离散化,\(n^2\)空间,\(f[i][j]\)表示第i个,高度为j时的最优价值
由于状态一定从i-1层的最优的状态转移而来,在循环j的时候用一个变量保存当前上一行的最优值,这个思路和LCIS有点像,今天才做过
10.22
[JLOI2011]飞行路线
分层图模板题
分层图的一般模型:
在图上,有k次机会可以直接免费通过一条边,问起点与终点之间的最短路径.
用二维的dis
\(dis[i][j]\)代表到达i用了j次免费机会的最小花费.
\(vis[i][j]\)代表到达i用了j次免费机会的情况是否出现过.
裸的dijkstra转移即可:
Auto(i,x)
{
int v = e[i].v;
if(j+1<=k && dis[v][j+1] > dis[x][j])
{
dis[v][j+1] = dis[x][j];
q.push((node){dis[v][j+1],v,j+1});
}
if(dis[v][j] > dis[x][j] + e[i].w)
{
dis[v][j] = dis[x][j] + e[i].w;
q.push((node){dis[v][j],v,j});
}
}
注意最后的答案是\(dis[ed][i]\) 的最小值
(x倍经验)[BJWC2012]冻结
k次优惠变成了费用减半
(x倍经验)[USACO09FEB]改造路Revamping Trails
一样的模板,但是k等于20我只开了20数组改半天
(x倍经验)[USACO08JAN]电话线Telephone Lines
这个题要求的是最短路径上最长边,这么写就可以了
if(dis[v][j] > max(dis[x][j], e[i].w) && !vis[v][j])
{
dis[v][j] = max(dis[x][j], e[i].w);
q.push((node){dis[v][j],v,j});
}
[USACO08JAN]跑步Running
这个dp依然提倡一个思路:在合理复杂度下的泛化状态表示,能表达的状态越广泛,dp思路的成功率越高
这个题就是如此,用\(f[i][j]\)表时间为i,疲劳度为j的最优解,
同时又必须注意两大限制,所以同时用了刷表和填表法
必须从\(f[0][0]\)开始转移
而\(f[i][0]\)必须有前面的\(f[i-p][p]\)转移,所以它我就用了前面的状态刷表,然后这个状态的0可以由\(f[i-1][0]\)转移,这么写起就稍稍有点乱
否则普遍由\(f[i-1][j-1]+d[i]\)转移而来
一遍AC,rp++
memset(f,-1,sizeof f);
f[0][0] = 0;
for(int i=1;i<=n;i++)
{
f[i][0] = max(f[i][0],f[i-1][0]);
for(int j=1;j<=m;j++)
{
if(f[i-1][j-1]!=-1) f[i][j] = max(f[i][j], f[i-1][j-1] + d[i]);
if(i+j<=n) f[i+j][0] = max(f[i+j][0], f[i][j]);
}
}
io << f[n][0];
10.23
[SCOI2010]连续攻击游戏
基本上所有题解都在误导,这个题应该把点集分开成两个域,但是几乎所有题解都在把两个点集挼成一个来做。
让属性成为\(S\)集合,装备为\(T\)集合,让匈牙利从1往10000匹配,只能増广否则终止,这是因为当\(dfs(i)\)返回0时意味着i没有匹配的点。要求的匹配一定连续
然后这个边建单向的双向的都可以,但是这个题的理解是建单向边,表示属性对应的装备
总之这个题搞了超级久,加深二分图理解
10.24
最长路_NOI导刊2010提高(07)
有向图上的问题,且与环有关的问题都可以用拓扑排序搞事情
首先如果拓扑排序结束后仍然有点没有入队,证明有环
对于求指定两点的最长路,可以把所有点的\(dis\)置为\(-inf\)然后把\(dis[st]\)置为0然后开始做拓扑排序dp
它本质上是一个无后效性的dp
杂务
求拓扑序里面的最后结束工作的时间,
还是dag上最长路
点权化边权,可以加超级原点超级终点
最小函数值
和那个序列合并有点像都是用堆得前m小的值
如果弹出这个函数的值,那么把x+1的函数值加入堆,就没有了
[HNOI2003]操作系统
啊啊啊模拟题弄昏我 昏睡模拟
这个端点很迷,好像写的时候根本没管它一样
用一个优先队列模拟这个cpu的等待队列
每次时间锁定在下一个进程到来之前,然后利用时间间隙向右推进
当然优先队列按照优先级和出现时间排序
充分利用下一个进程之前的时间,把能完成的都完成了
然后有一个即将被换掉的进程,把他的时间给减了,然后放回堆里,下一优先级高的自然在堆里把他压下去了
最后剩下一堆进程,按优先级加时间即可
while(scanf("%d%d%d%d",&nxt.id,&nxt.st,&nxt.rest,&nxt.pri)!=EOF)
{
while(!q.empty() && nowtime + q.top().rest <= nxt.st)//在下一个进程来之前可以完成等待队列的进程
{
node x = q.top(); q.pop();
nowtime += x.rest;
io << x.id << ' ' << nowtime << '\n';
}
if(!q.empty())//在下一个进程到来之前
{
node x = q.top(); q.pop();
x.rest -= nxt.st - nowtime;//还有时间可用
q.push(x);
}
q.push(nxt);
nowtime = nxt.st;
}
while(!q.empty())
{
node x = q.top(); q.pop();
nowtime += x.rest;
io << x.id << ' ' << nowtime << '\n';
}
TJOI2009]开关
线段树区间异或 区间求和
靠是模板题
tag可以^=1这样两次之后相当于不反转,非常省时间
然后区间反转就是sum = len-sum
我觉得挺好看的
inline void pushdown(int rt)
{
if(tag[rt])
{
t[ls].sum = t[ls].len - t[ls].sum;
t[rs].sum = t[rs].len - t[rs].sum;
tag[ls] ^= 1,tag[rs] ^= 1;
tag[rt] = 0;
}
}
贪婪大陆
又是一道经典神题!lsq震撼我妈秒切此题
有很多不同的线段覆盖在区间上,查询一段区间有多少不同的线段覆盖在上面,注意这里的线段可以重叠覆盖。
这个模型是以前见过的,今天起给我永远记住它:求区间相异线段(区间)覆盖数
用右端点左边的起始点数 减去 左端点左边的结尾数 = 区间覆盖数
相当于用所有右端点左边的开始的区间数 - 所有未延伸到左端点内的区间数 得到所有在区间内的数
在这个数学模型里,我们把所有散乱的线段化作一条条射线,横冲直撞地冲向右边,而我们只需要截下所有冲过右端点的线段,而那些在左端点前及时停下的射线我们就可以减去了
直接用树状数组维护即可
io >> op >> l >> r;
if(op == 1) st.add(l,1),ed.add(r,1);
else io << st.query(r) - ed.query(l-1) << '\n';