欢迎
欢迎来到我的博客!
欢迎来到我的博客!
因为本人太菜所以放一点可能相关的思考在这里(自己都不一定能记得啥时候回来看
有二元关系时可以尝试连边
字符集小就考虑枚举字符关系
位置关系不重要就先排序
多组询问,每组询问给定一对正整数 ,在闭区间 内所有整数中,找到二进制表示中 个数最少的数。如果有多个这样的数,找到其中最小的那一个。
。
考虑直接按位贪心求解。
从最高位开始,在保证 的情况下,尽量在高位放 ,并且得到的值一旦 就跳出。
此时可以保证最终得到的数可以保证在 区间内,并且 的数量最少。
再考虑如何求出最小值。
从最高位开始枚举,显然尽量要将 往低位放,故除非后续位数全部在最高位放 直到放完也 ,否则不放,显然一定最优。
复杂度 。
有一棵 个节点的树,选择两个节点将树切断,得到三个连通块。
设三个连通块的大小分别为 ,求出 的最小值。
。
三个连通块不好求解,考虑固定其中一个连通块的断点。
此时显然使另外两个连通块大小尽量接近最优,因此我们将所有点的子树大小(记为 )存入 二分求解。
然而当另一个断点是当前断点祖先时,其 包含当前点的 ,所以我们开两个 ,分别存储祖先节点和非祖先节点,祖先节点更新答案时减去当前节点的 即可。
复杂度 。
有编号为 的三个值,初始时都为 。
可以用 次操作对值进行修改,每次操作为:
求在 次操作结束后,三个值之和的最小值。
我们注意到两个性质:
因此我们可以设计状态为 ,表示进行到第 次操作时,本次操作选择的是第 个值,下一个值被搁置了 轮,下下个值被搁置了 轮。
每个状态向下一个状态的转移显然只有三种可能:
此时的转移是显然的。
注意:一个点没有被修改过之前不会产生减法,所以如果 或 为 ,其转移方向应该为 。
最后我们注意到 状态只会从 转移过来,所以采用滚动数组。
复杂度 。
给序列 ,,。
定义区间 的价值为 按位与, 按位或, 的最大公因数,这三者的乘积。
次查询,每次查询给出区间 ,查询满足 的 的价值之和。
结果对 取模。
。
考虑扫描线,扫到 时维护 表示区间 对应运算的答案, 表示左端点在 内,右端点在 内的区间的权值和(相当于 的历史和,虽然这道题并不需要用到历史和),那么询问 的答案可以拆分为扫到 时的 。
一个并不是很显然的结论是:扫描线 , 的值只有一段 会 改变,然后这个 的大小是均摊 的。具体证明可以考虑拆位,每一位会在什 么时候改变这个乘积。
于是我们就可以维护 这个数组了,对于 这一段的 没有发生改变,但是 改变了,直接历史和复杂度又太巨大了,可以考虑将 的定义式改为 ,这样只要前缀 不改变, 也不用修改,需要求值的时候算一下就行。
总复杂度 。另外本题有一个数据不变,时限 3s->1s 的版本(P9335)。
那头在出题时发现他不会写一道题的 generator,需要你进行帮助。
我们称一个对无限大网格图的黑白染色方案是合法的当且仅当所有黑色点四联通,所有白色点也四联通。
具体的,我们在 与 之间连边当且仅当 且两个点颜色相同,如果得到的图的联通块数为 或 ,那么染色方案合法,否则不合法。
初始时所有点都是白色的,你需要维护 次修改。
第 次修改有两个参数 表示尝试把 涂黑(保证这个点当前是白色)。如果涂黑后染色依然合法则执行该操作,否则放弃该操作。
你需要求出每次操作是否被执行。
。
每次新加入的点是否能让黑色保持联通是易于判断的,判断新加入的黑点上下左右是否有其他黑点即可。
考虑如何判定白色能否保持联通,注意到只需要考虑新加入点周围 九宫格内的情况即可,九宫格外的白点并不会受到影响。
因此每次检查新加入一个点后九宫格内黑色白色的连通性即可。
那头需要完成 道题目,第 道题目难度为 。那头有一个智慧值 ,他完成所有题目需要 的时间。
那头会 次调整自己的智慧值,你需要每次告诉他完成所有题目会完成的时间。即给定 ,你需要求出 的值。那头不希望等待,所以本题部分测试点要求强制在线。
。
考虑决策答案每一位是 还是 。对于第 位,需要查询有多少个 满足 的第 位为 。可以转化为统计进位次数。这相当于查询 在一个区间内的值的个数, 可以预处理 并对 排序然后二分查询。
复杂度 , 期望得分 分。
尝试优化以上算法。预处理部分容易优化,可以通过归并做到 (但这部分 不是瓶颈,不优化也可以通过)。难点在于优化询问。如果允许离线,可以对于每个 也预处理 并排序,然后双指针扫描回答询问,复杂度 ,期望得分 分。
如果要求强制在线,我们无法对 排序预处理,所以只能尝试通过本次询问第 位 的结果推出 位的结果。考虑一次询问是要求 的最大的 的位置。当 ,式子左侧的值要么不变,要么增加 。这启发我们预处理 , 辅助转移。如果可以得到以上数组,每次 可以直接通过调用 完成。 可以和 一起在归并时求出。
复杂度 ,期望得分 分。
那头住在一棵 个点的树上。
那头在树的每个节点上都放置了一个头。其中 号节点上的头为 号头,其高度为 。对于整数 ,称两个不同的头在视线高度为 时可以互相望见当且仅当:
对于每个整数 ,求有多少个有序三元组 满足:
。
考虑按编号从小到大删点。时刻维护一个 个点的无向图 。 中 有边当且仅当 在视线高度为 时可以互相望见。令 在 中的度数为 ,则答案为 。
直接建边边数过多,且维护图并不方便。考虑建一些虚点让图变成一棵树。删点开始前,对于原树上的一条边 ,在新树上连接 和 。设编号 的为白点, 的为黑点。删除 相当于把 的子白点与它的父白点合并,可以使用并查集维 护。容易发现新树上的点是黑白相间的,且对于 中的边 ,新树上一定存在恰好一个白点 满足 这两条边在新树上存在。为了方便,以 作为新树的根,这样根一定不会被删掉。
考虑如何算答案。把三元组 变成 ( 是白点)。令 为白点 的 黑儿子个数,分情况讨论:
每次删除一个点会影响其下一级儿子与上三级父亲。因此对于一个白点维护其下一级, 下三级信息,对于一个黑点维护其下两级信息即可。具体地,可以令 ,。删除 的复杂度为并查集的复杂度加上遍历 的儿子的复杂度。
给定正整数 。
请判断是否存在非负整数 ,使得对于所有 ,都有 。如果存在,请输出所有满足条件的 中最小的一个对 取模的结果;如果不存在,请输出 。
。
先考虑 的情况。
将式子两端同乘 ,得到 。
此时题目条件为 ,其中 。
即找到一个最小的 使得 ,则此时 。
我们尝试将 的质因子筛出来。 若 ,则存在 ,则 。
若 ,则根据 显然有 ,与条件 矛盾。
所以 ,即每个 仅存在 的质因子,以及有可能含有唯一的 的质因子。
对于 的质因子 ,我们只需找到最小的 使得 (这个可以 ),然后任意满足 的 一定可以表示为 。
求出了 的质因子表示后,因为有 ,我们就可以枚举 来试了。这里需要分讨 的大小以及 的正负性,具体可详见 std。
最后的问题是, 怎么办?令 ,容易发现 ,这是判断是否有解的充要条件。
然后可以求得子问题 ,最终答案为 。
有一棵由编号为 的 个节点构成的有根树,其中 为根节点, 号点的父亲为 。此外,每个节点都有一个互不相同的整数权值, 号节点的权值为 。保证 为 的一个排列。
我们从 出发,每次移动到当前节点的子节点中权值最小的一个。如此重复,直到到达某个叶子节点。这样得到的路径记作 ,我们称其为特殊路径。
定义一次删除操作如下:
我们对这棵树进行 次删除操作,你需要在每次操作前求出 号点的权值 。
。
定义删除序列 , 为这一棵树第 操作前根节点的权值。
对于一个点 ,设它操作选中了子节点 多次,那么 的变化一定形如 子树的删除序列。
那么求 子树的操作序列相当于先加入 ,然后把子节点的删除序列归并起来。
注意到这个删除序列就是 子树的一个 BFS 序,而且我们要最小化这个 BFS 序的权值的字典序。
用堆维护 BFS 即可。每次取出最小权值点后加入所有的子节点。
复杂度 。
这是一道交互题。
现有 个人,编号为 。其中恰有 个人是诚实的, 个人是不诚实的。每个人都清楚所有人的身份,但你知道的仅有 和 的值。你需要去确定每个人的身份。
如果你在已知 的情况下无法通过询问确定所有人的身份,则报告不可能。
否则,你可以进行不超过 次询问。每次给定 和 ,向 询问 的身份,交互库返回规则如下:
交互库会通过你的询问次数评分。
q | 得分 |
---|---|
40 | |
55 | |
65 | |
85 | |
100 |
且 。
一个观察是,我们能分辨身份当且仅当 。
如果 ,那么我们可以从 个假的人中选出 个人,对这 个人询问时按照这 个人为真,其他都为假来回答。然后就无法判断这 个人是真是假。
如果 ,那么对于每个人 ,我们询问其他所有人对他的评价。如果有 个人认为他是真的,那 么就是真的,否则即为假。注意到我们可以认为 声称 是真的,那么只要做 次。
考虑去找一个真的人,然后再通过他问出所有人的身份。
因为 ,考虑类似摩尔投票的思路。
如果 认为 是假的,那么 和 中有至少一个为假,可以直接删除。
否则,如果 和 都认为对面是真的,那么 和 的种类相同。
于是有一个 次的做法:
考虑优化。我们不需要维护一个身份相同的集合,而是维护一条链 ,满足 认为 是真的。
实际上, 的真实身份一定是一段前缀是假的,剩下一段后缀是真的。
那么每次我们加入 时,只要看 是否认为 是真的。如果是,则加入 ,否则删除 和 。
因为 ,而我们每次删除的 和 中至少有一个是假的,因此最终的 一定包含真人,即 一 定为真。
那么我们再拿 把所有人问一遍即可。询问次数 。
给定一个长度为 的自然数序列 ,满足 。
序列还需满足 条限制。
第 条限制为 。
请输出合法的序列 的个数,对 取模。
对于第 个限制,它首先约束了 都是不超过 的。
记 为 的上界。这个可以直接并查集预处理。
考虑对值域扫描。
对于一个 ,我们在处理 的限制时, 的点都是不用管的。因为它们不可能成为一个区间的 。因此我们可以取出所有 的点和 的限制。
那么这时限制就变成一个区间必须包含至少一个 的点。
考虑 表示 ,此时 之前的点方案数。
那么下一个 的点 需要满足不存在区间满足 ,这样的 形如一段前缀,可以差分解决。
时间复杂度 。
有一棵由编号为 的 个节点构成的有根树,边有边权。 为根节点,节点 的父亲为 。对于 , 与 之间的边权为 。
定义 为 子树内经过 的最长链长度。
进行 次操作,每次将一条边的边权加上一个正整数。
在第一次操作前和每次操作后,输出
。
时 。
每次操作 。
是 子树内以 为端点的最长链和与最长链无公共边的次长链之和。
对于每一个点 ,我们记录 为子树内与 距离最大值和次大值。这里次大值到 的链需要 和最大值的无公共边。
考虑动态维护长链剖分,那么子树的 就是实链上儿子的 ,次长链 就是虚儿子的 的最大值。
对于一次修改 ,我们先对 子树内的 和 整体加 。
然后找到 子树内最大的深度,对于 的所有祖先去更新。它对于长剖的影响是把 到根链上一段后缀 变成实链。
这个是和 LCT access 类似的,一个结论是虚实边变化次数是 的。证明考虑轻重链剖分,这 个就是在 dfs 序上颜色段均摊。
那么考虑去数据结构统一维护实边,暴力修改虚边。
一条实链,操作相当于对一条链的 加上 。
对于一条虚边,操作相当于对于单点查询 和 ,然后进行单点改。
于是我们需要支持:
第一条线段树维护,第二三条可以树状数组维护。
第四五条 std 用了倍增树状数组维护,复杂度 。常数较小,可以通过。
实际上也可以用一些办法去掉一个 ,做到 。
对于一个长度为 的正整数数列 , 是好的当且仅当对任意 。
现在有一个好的数列 ,可以对这个数列执行以下操作:
要让整个序列操作完之后只有两种不同的数值,求至少要操作多少次。
。
考虑到序列的最终形态是两个数 交替出现,于是将序列按位分为奇偶两部分。
考虑对于两个数,将序列调整到对应状态的最小次数,显然由两个部分组成:
于是考虑枚举偶数位并统计奇数位贡献,对于不存在第二种情况的数对,可以直接选择出现次数最多的奇数位,显然此时结果最小。
对于存在第二种情况的数对,因为这样的数对显然不超过 个,因此可以用 map 维护,直接枚举。
复杂度 。
放学后茶会的几人决定去毕业旅行!但是她们关于该去哪里旅行有一点小分歧,所以她们决定用阿弥陀签来抽签决定目的地。话虽这么说,但是 Yui 希望操纵抽签结果, 所以她来找你帮忙了!
上面是一个简化版的阿弥陀签的示例。一个阿弥陀签由 条编号为 的竖线和若于条高度互不相同的横线构成,每条横线在某个高度上连接相邻的两条竖线。简单来说,使用阿弥陀签抽签的流程如下:
现在,Mio 正在制作一个阿弥陀签。目前签上已经画好了 条竖线,还没有任何横线,接下来她将在这个阿弥陀签上修改 次横线。每一次她会在某一个位置增加或者减少一条横线。Yui 在每次操作之后,想知道修改后的整个阿弥陀签至少删掉多少条横线,才能使得对于 ,以第 条竖线的最上端作为起点时,到达最下端时也在第 条竖线。你能帮帮她吗?
。
可以将这个过程转化为有 个人分别从不同的起点开始走,每条横线视作将相邻的两个人交换,则我们的目标是使最终所有人回到原先的位置。
设每个人最后的位置为 ,则每次修改可以视为交换两个 ,最终的目标则转化为令 。
注意到对于所有 ,必定存在 个置换环,而每个置换环都需要 次删除操作(即删除其中一次置换对应的横线)来满足最终条件,因此最终的删除边数量就是 。
所以我们只需要开一棵线段树维护置换环数量即可。
在一个 的方格纸上的某些格子有障碍,使得剩下的所有格子可以用 的多米诺骨牌不重叠地填满。
将要在方格纸上额外选两个格子放置障碍,使得不再能用骨牌填满所有空格。有多少种选择两个格子的方案。
如果方案数超过 ,你需要输出 而非方案数。
。
假设空格数量为 ,对整个方格纸进行黑白间隔染色,那么会有 个黑空格和 个白空格。由于删除两个同色格子一定会导致方格纸无法再用多米诺骨牌填满,我们至少有 种方案,所以 时一定输出 。
考虑 的情况。我们可以把输入中的所有空格当成点建一张二分图,两个点之间连无向边当且仅当它们对应的空格在方格纸上相邻。显然这个图一定存在一个完美匹配,我们先把这个匹配找出来。然后我们考虑数有多少个点对满足删完之后仍然存在完美匹配。
对于一对异侧点 而言,假设它们在原图中分别匹配上了 ,删除这对点之后是否仍然存在完美匹配可以这样判断:我们先取消 在原图中的匹配,然后删掉这两个点,检查 是否是一条合法的增广路。具体来说就是,是否存在一条从 到 的路径 ,满足 都是原图中的匹配。我们枚举所有的 ,从 开始 bfs 所有可行的 即可。
记答案上限为 ,那么时间复杂度是 的。
有一个由 A
和 S
构成的字符串。
这群史莱姆会进行以下四种操作:
A
进行分裂:在一个 A
的两端各增加一个 S
。即将字符串中的某个 A
替换为 SAS
。S
进行分裂:在一个 S
的两端各增加一个 A
。即将字符串中的某个 S
替换为 ASA
。A
组团逃跑:连续 个 A
可以消失。即删除字符串中的连续 个 A
。S
组团逃跑:连续 个 S
可以消失。即删除字符串中的连续 个 S
。给出两个常数 和 ,有两个字符串 和 ,求是否能将 经过若干次操作转换为 。
。
记 表示空串, 表示字符串 重复 次。
经过足够的手玩,我们可以发现:
,同理 。
,同理 。
,同理 。
,同理 。
,同理 。
所以,我们可以在一个非空串的任何位置添加 和 。也就是说:
,同理,。
,同理 。
就是说,所有操作都是可逆的,那么:
。 显然答案只与 和 有关,所以下面只考虑 且 的情况。不妨设 。
对于任意的 , 和字符串 ,我们考虑以下对字符串 的操作:
显然,经过这样的操作后,R 一定是以下八个串之一:
。我们将这些串称作标准串。
当然, 之后并不能进 行任何操作,你可以把它当成 ,这并不重要。
那么,我们只需要考虑分类讨论一下,在不同的 和 下,这八个串之间哪些能互相转移即可。
对于 的情况,这八个串之间并不能互相转移。证明也很简单:显然最后得到的串只跟上述第二步操作结束之后的字符串中, 和 的个数 的值有关。 题目中的操作 不会改变这两者的值,而操作 会使得这两者同时 。无论如何都不会改变最终的结果。
是平凡的。
时,相当于所有的 都不存在,那么 也不存在。此时只有 和 两种情况。
时, 和 仍然不存在。有 四种情况。由于 时 的数量的奇偶性不会改变,所以这些情况下所有情况之间不能转移是显然的。
所以,我们只需要求出 和 对应的标准串,并看它们能否互相转移即可。
时间复杂度 。
要将 个数分为两组,第 个数的值为 。
有 对关系 表示当第 个数和第 个数被分到同一组时,会产生额外 的贡献。
求分组之后两组之间总和差的最大值是多少。
。
保证对 ,有 且 。
全相等,此时只需考虑朋友关系的贡献。
考虑如下贪心过程:设与 相关的朋友关系的默契度之和为 ,则选 从大到小排序后的前 个人分成一组,剩下的分成另一组,答案即为 。
证明:对于朋友关系 ,如果 (x,y) 在同一组,则对答案的贡献为 或 。否则,贡献为 ,即贡献被抵消掉了。\
复杂度 。
考虑每个人的力量,则将每个 额外加上 然后跑上述贪心即可。
有 张卡牌,每张卡牌背面有一个数字 ,正面空白。
初始全部正面朝上,可以做以下操作任意次:
设最后背面朝上的卡牌写着的数的总和是 ,所有操作的代价总和为 ,则最终得分为 。
求最大可能得分。
。
每个区间不交,对每个区间单独考虑,每个区间的贡献即为 。
显然每个 和每个 的区间都分别只会被操作其中一个。枚举翻转了 这个前缀,考虑每个 的区间。如果 ,则此时答案为 。
如果 ,则还需减去相交部分的和。分别维护即可。
考虑做如下转化:初始答案为 。若最后无法翻转 ,视作花费 的代价操作 这个点。
因此,初始时先将每个 加入操作中,即转化为操作若干次使所有卡片都被翻转的最小代价。\
我们考虑 建这样一张图:点集为 ,边为所有的 和 ,然后求 至 的最短路即可。易证一条 至 的路径对应了一种将所有卡片翻转的操作方案。
复杂度 。
有 个同学,第 个同学的初始位置为 ,需要将每个同学的位置向左或向右移动 个单位。
需要将同学分为若干排,设一共站成 排,第 排的同学的编号集合为 ,则定义凌乱程度为:
为给定常数,求所有可能的队形的凌乱程度的最小值。
。
当 时。我们先将题意转化为要求每个人不动或向右移动 。
为了方便计算,称第 位置被覆盖,为当前位置有人,或者存在两个位置为 的人,使得这两个人被分在同一组,且 。设 表示位置 最终是否被覆盖,则答案为 。
我们的限制形如,对于一个位置 的人,要求最后位置 和 中至少有一个被覆盖。
考虑状态压缩。设 表示考虑到位置 ,前 个位置的覆盖情况为 。设移动后最左和最右的人的位置之差为 ,则这样的状态总数是 的。由于 ,因此状态数可以接受。
考虑如何转移。设从左到右到了 ,枚举位置 是否被覆盖。如果存在一个人的初始位置为 ,则需要要求 被覆盖或者 被覆盖,这是容易判断的。贡献直接按照上述式子计算即可。复杂度 。
考虑 较大的情况。修改一下状态,设 表示位置 的覆盖情况为 从 转移到 。所需要的判断和贡献计算都是类似的。此外,我们需要额外记录下 的状态,以便在计算完后计算 和 之间产生的贡献。
这个算法的复杂度为 ,在 时可以接受。于是我们在 时运行上一个算法,在 时运行该算法,即可通过所有数据。
有一棵 n 个结点的树,初始 号结点上有一个数 。
一轮游戏的过程如下:
将进行 轮游戏。每轮游戏开始前,会交换某条边两个端点上写着的数。
对于每轮游戏,你想知道是否存在一种方案使你能获胜。注意,每轮游戏之间是独立的,但是修改是永久的。
。
时,按值从小到大操作,发现合法的充要条件是,对于当前操作到的值 ,所有点值为 的点的导出子图有完美匹配。于是从小到大模拟收缩的过程。
带修改,我们需要一个更好的判定合法的方式,即需要快速对每个操作到的 ,判定其导出子图是否有完美匹配。
这里给出一个结论:假设一个有根树的某些点被染为黑色,其余点为白色。记黑点总数为 ,子树内有奇数个黑点的子树个数记为 。此时有 。
等号是否成立即为黑点导出子图是否有完美匹配的充要条件。
严格证明可以使用归纳法。感性理解:对于一个黑点 满足它的子树黑点数为奇数,那么 的父亲必须为黑点。所以这样的子树必须有 个。
于是我们可以把这个问题拆为两个子问题:对于所有 ,求操作到 时点值为 的点的个数,以及求此时有多少个子树内部点数为奇数个 。
对于第一个子问题,由于修改并未改变数值性质,所以可以在询问前预处理。
对于第二个子问题,我们考虑计算出每个子树的点值之和 ,那么这个子树在操作到 时做贡献。可以用 set 启发式合并维护二进制位,进位数量 的,可以直接维护。
对于修改,我们发现只会影响这条边深度较大的那个点的子树内,修改形如减去一个 再加一个 。我们把操作拆到在这个点上,在 set 里维护 的连续段,即可做到 加减一个二进制位。
总复杂度 。
给出 个仅包含大写字母的字符串 。
对于每次操作,可以选择任意一个字符串,将其内部的字符任意重排,记进行完重排后的字符串为 。
字符串 的字典序小于字符串 ,当且仅当存在一个正整数 ,满足 和 相同,且 ,或 和 相同且 。
求出是否存在一个方案使得对于任意 ,。
若存在,请求出一种方案。
。
首先有一个很直接的贪心思路:从左到右考虑所有字符串,重排它使得它在字典序 前一个字符串的情况下,最小化其字典序(即留给后面的字符串更大的操作空间)。
考虑模拟这个思路,对于每个字符串尝试从左到右逐位确定某一个位置最小能填什么:从小到大枚举该位置上的字符,并检查在这一位填入该字符是否满足条件(即这个位后面的剩余字符从大到小填,能否使当前字符串字典序 前一个字符串)。
复杂度 。
有 个数 。
给出区间 ,要求在 中选择若干个数,满足其乘积对 取模的值最大。
对于每一个询问,求出得到的最大值。
。
首先考虑
定义 表示 区间,能不能凑出 ,转移是平凡的。
考虑优化掉一个 ,dp 状态的结果只有 太浪费了,重新定义 表示最大的 满足只使用 的元素能凑出 。
特别的,当 都不能凑出 时 。
对于每一个询问,考虑枚举答案,即最大的满足 的 。
复杂度 。
有一棵有 个节点的树,树上每个节点有一个权值 ,,保证每个权值都恰好出现 次。
定义得分为树上满足以下条件的节点二元组 的数量:
可以进行若干次交换操作,每次操作可以选择两个节点并交换它们的权值,每个点上的权值最多经历一次交换。
求能够达到的最大得分和对应的交换方案。
, 条边构成一棵树。
使用贪心或者 DP 求解一个树上的最大匹配 ,则答案不会超过 。
大胆猜测答案一定可以取到 (到这里已经可以获得第一问的 分),以下是构造方法:
对于上面的最大匹配 ,构造一个有 个结点的无向图 ,连边 。
由于原树上每个结点在最大匹配中最多出现一次,而 在 中恰好出现两次,所以该图中结点度数不超过 。
我们先引入一个叫做“缩二度点”的操作:假设在 中,对于结点 有边 和 ,那么意味着在最大匹配中有两条边满足 和 ;
此时交换 ,即可以将匹配中的边调整为 和 。——对应到 中,相当于删除了边 和 ( 直接满足条件,可以不管了),加入了一条新边 ,十分类似广义串并联图中的“缩二度点”操作。
考察 的性质,由于结点度数不超过 ,所以 中的每个连通块要么是一个环,要么是一条链:
使用上面的“缩二度点”操作,一个环可以被缩成一个单点(即环上所有结点全部调整完毕),而一条链最后必然被缩成 的形态:思考造成这种现象的本质,由于这两个结点度数都为 ,那么满足 的某个 必然没有被选入最大匹配中。
此时只需要将 在匹配中对应的结点和那个没被选入的 交换即可。
根据不同实现,时间复杂度为 或 。
有一棵有 个节点的树,节点编号为 ,给定常数 。
对于一个节点 ,定义 为:
在某些测试点中,只需要对于 求出 ;而在另一些测试点中,需要对于 均求出 。
,输入数据保证形成一棵树。
首先考虑如何判断一个答案是否可行。
设我们现在判断答案为 是否合法,我们可以从下往上做,每次将最深的点拿出来,此时的操作应该是其 级祖先向根节点连边,这样这一整颗子树都是合法的,删掉这个子树。
重复上述过程 次后判最深的点是否和根的距离小于等于 ,即为 是否合法。
将这棵树拍到 DFS 序上,用线段树维护最深的点,删子树可以变成一个区间减 。这一部分的时间复杂度为 。
考虑换根怎么做。首先线段树上维护的是每个点到深度的距离,设当前的根为 ,换根后为 ,只需要将 子树内到根的距离减一, 子树外到根的距离加一,这个也是容易维护的。 接下来就剩下树上 级祖先和换根后的子树,这个比较经典,分讨就可以了。
具体的,以 为根, 的树上 级祖先可以如下分讨(记 和 在原树上的 lca 为 ):
以 为根, 的子树为:
对于每个点都二分一下可以做到 ,精细实现可以通过。
但是还可以做到更优。发现换根后答案的变动不会超过 ,可以将 次判断缩到 次。
此时时间复杂度为 。
给定一个长为 的仅由小写字母组成的字符串 。
可以选择两个正整数 满足 ,对 的区间进行翻转。
求通过一次翻转,最多能得到多少种不同的字符串。
。
注意到,对于所有首尾字符相同的子串,它的反转操作都能等效为去掉其首尾字符的新子串的反转操作。
所以我们只需要统计有多少个点对互不相同即可。
给定一个长度为 的整数序列 ,对其每个子序列求和可以得到 个数,求其中前 小的数。
。
如果 非负,可以观察到我们每次增加只会是增加一个数或者替换一个数,我们用一个优先队列存储,可以 解决。
考虑有负数的情况, 设负数是 ,我们直接将 换为 ,选/不选的情况就会从 变成 ,注意到我们只需要将所有答案减少 ,此时的情况就与原本一致,按照全是正数计算即可。
有一个 的网格,第 行第 列的格子记作 。
定义一个级别 的 型为两边长(包括拐点)均为 的 型,可以旋转,特殊地,级别 的 型仅包含一个方格。
给定格子 ,考虑将整个网格划分为级别 的 型各一个的所有方法(划分时每个格子属于且仅属于一个 型),求 属于的 型的级别之和,结果对 取模后输出。
。
最简单的写法是计算有多少种方法能使 被分在 级别的 型里,设为 。
从大往小看,可以认为每次操作会使矩形上下缩一行,左右缩一列(或者可以认为矩形左上角最多往下移一行,最多往右移一列,矩形大小减 )。
如果要把这个格子分到 级别的矩形里,那么前 次缩矩形就不能把这个点缩没,分行列考虑,删掉最上方行的次数必须在 范围内,同理删掉最左边列的次数必须在 范围内,后边操作就是任意的了。
因此方案数就是 。
时,两个括号内都是 ,式子结果是 。
随着 的减小 增大,且 的项数会少,这可以利用杨辉三角观察一下这一段和的规律,先乘 推到下一行的一段区间 (会带点零碎的边界),再简单加减几项调整一下(减掉多余的贡献,如果缺项就加上缺少的项),这样可以做到 递推。
本题中应该是乘 后原来那行最左最右的两项的贡献会算多一倍,所以应该就是减原来那行首尾两项,不用加。
最后答案可以用 (也就是 )减去前边每一项,相当于初始认为每一种方案级别都是 ,然后减去级别 的方案(这些方案的级别都损失了 ),然后减去级别 的方案(这些方案的级别又损失了 ),以此类推。
也可以用 算出级别恰好为 的方案数。
甚至可以直接分这个点出现在级别恰好为 的 型的角还是边上直接列出组合数前缀和形式的式子计算,但写法不如计算 的简便。
时间复杂度 。
现在你有一个 个数的排列 ,你可以对它做两种操作:
现在你最多对这个排列执行 次操作(可以少于 次)。整个过程中,你可以根据当前的排列情况,实时决策下一步是否停止操作,以及使用 1/2 哪种操作。
请问最优策略下,最终排列的逆序对数期望的最小可能值。
记 为输入排列 中逆序对数量。
只有第 种操作时,由于交换相邻元素只能让逆序对数 ,答案为 。
只有第 种操作时,随机打乱后的逆序对数期望是一个定值,可以通过 DP 求出。设计 代表 这些数随机打乱之后恰好有 个逆序对的概率,转移为 。
利用前缀和优化可以在 时间完成。
令 代表长度为 的随机排列,进行 次打乱之后的逆序对期望。
那么每次打乱之后都可以进行决策,停下来或者继续打乱(期望为 ),设当前逆序对数为 ,若 则停下来,否则继续打乱。
设
答案为 。
两种操作都有时,需要进行决策是否先使用操作 进行打乱,令 代表长度为 的随机排列,进行 次最优操作之后的逆序对期 望,设当前逆序对数为 ,转移有2种:
两种择优转移:若 使用第1种转移,否则第2种转移。设 。
维护 和 的前缀和 s1_{i,j}$$s2_{i,j},上式可以写成
可以在 时间内预处理出 数组,答案为 ,总复杂度 。
注意空间较大,可以将 数组滚动。
统计一个字符串 中共有多少个子序列,满足:
答案对 取模。
, 中仅包含大小写英文字母和数字。
考虑设计 dp。
设计状态 表示从位置 开始且必定选择 ,形成形如 的长为 的后缀的方案数。
首先 必有一个为 ,可以使用滚动数组降一维,记录当前左边每个数剩余的数量,容斥可以得出左半部分的方案数。
复杂度为 。
有一个长度为 的序列,每次可以从最左端和最右端取出一个数,并放在当前已取出的数列的末尾。
共有 个序列,要求对每个序列求出最后得到的数列的最长可能 LIS。
。
可以发现操作过程本质上是将序列划分成两段,将后缀翻转后归并两个序列。
在划分序列后,LIS 的上界已然确定,是所有得到的序列的 LIS 长度之和,因为若总 LIS 长度更大,其位于这 个子序列中的部分必然有一个的长度超过该子序列 LIS, 矛盾。
达到这个上界是简单的,只需将求得的子序列 LIS 归并起来。进一步,发现序列的贡献是独立的,只用对每个序列都找到最优划分。
暴力枚举划分并计算左右两段 LIS 显然无法通过。发现瓶颈在于反复计算前后缀 LIS,考虑先通过扫描线+BIT 和指针的方式先处理出每个前后缀的 LIS 长度及 LIS 中离当前位置最近的数,再枚举划分并求得最优位置,找出前后缀 LIS。序列之间的归是简单的。
复杂度为 。
有一个 的棋盘,双方轮流放黑白子。
每有一行或一列不完全相同,玩家得一分,每有一行或一列完全相同,对手得一分。
最终若玩家分数为奇数且对手分数为偶数则玩家胜利,否则对手胜利。
求最终能让玩家获胜的棋盘状态得方案数。
。
可以发现你获得的分数和 AI 获得的分数相加等于 。则 为偶数时,一定不存在合法解。又因为当 为奇数时,若你获得的分数是偶数,那么 AI 一定是奇数,反之亦然。故两者一一对应,只需要求出其中一种方案数即可。
一整行或一整列颜色不完全相同的情况难以处理,所以考虑处理一整行或一整列颜色完全相同的情况。即现在需要解决这个问题:一整行或一整列颜色完全相同的数量是偶数的方案数。再反过来,先考虑为奇数的方案,再用总方案去减。
根据惯用做法,直接容斥/二项式反演。先考虑容斥系数的求解:对于答案至少有 个产生贡献的情况,恰好 是最后一次计算。
所以答案至少为 时,容斥系数为 。
考虑如何计算答案:
对第三个式子进行优化:
因此转移的复杂度就可以做到 。
共有 个数,每个数为 。
有一个序列 ,要求前 个数的中位数是 。
可以任意调整 的顺序,但是不确定序列 ,求有多少种序列 可以被满足。
答案对 取模。
先尝试找出满足 的充要条件。简化问题,先考虑 构成 到 的排列的情况。
容易知道 ,因为至少有 个数比 小或大。同时,序列中位数从 变化为 的过程中,由于只加入 和 ,所以中位数至多挪动一位。则 到 的取值不能在 与 之间。于是可推出 到 的取值不在 与 之间。
可以证明满足这两条限制是充分的。只需要构造出合法 序列。若 ,则加入目前不在序列中的最小和最大的 。若 ,则若 还不在序列中则加入 和目前不在序列中的最大数,否则加入两个最大数。 同理。容易验证这样的构造是合法的。
接下来考虑如何 dp 求解。倒过来考虑,这时第一条限制可认为是每次解锁 和 两个取值,第二条限制可认为是 到 之间的所有取值都要被删去。设 为已确定 及之后的取值,此时还未被删去的取值中还有 个比 小, 个比 大,这样的方案数。(没有必要记录具体取值,只需要记录相对位置)转移只需要考虑 的取值,并删去 与 的之间取值,并解锁最小最大两个新取值即可。
最后解决 重复的问题。这需要将每个 的取值改变为在 序列中的排名。那就希望相同的 对应的排名尽量相同(少被第二条限制删掉),且尽量早地摆脱第一条限制。所以只需要判断相同取值的 排名中哪一个最早摆脱第一条限制,并将所有 都设为这个排名即可。于是 dp 时只需要判断新解锁的取值是否和前面重复,来决定是否对 或 加一。
时间复杂度 。
有一棵有 个节点的树,树上每个节点有权值 ,每次操作可以将一个点上的权值部分转移到相邻点。
问最后是否能使每个结点的权值相等。
。
注意到一条边不会产生贡献当且仅当其两侧的头的个数平均值相等。
对于产生贡献的边,也可以计算出这条边会转移的权值的数量。
考虑得到一个合法顺序:随便选一个点为根,然后先把从深往浅把大于平均值的子树往根转移,再从浅往深向小于平均值的子树转移。
容易发现这样做任意时刻转移的 , 故方案合法。
一棵树上有 个权值不同的点,一开始有一个那头位于树上权值最大的点上。
每次可以选择在一个点上放置障碍,如果那头所在的点被放置障碍,则它会沿简单路径移动到能到达的点中权值最大的一个点上。
求能达成的那头的最大移动距离和。
分析:考虑到因为给出的是一棵树,因此一旦离开某个点,就不可能到达与该点相连的其他点。
基于这一性质,实际上每次移动都会将猫的移动范围限制在上一个点的某棵子树内。
官 sol
考虑时间倒流,用并查集将几个子树合并成一个,维护其贡献,树上 dp,复杂度
特殊做法
本质上就是搜索,传递状态为 ,搜索以 为根的子树内,从该子树内权值最大的点开始能达到的最长移动距离和。
在计算完以 为根的子树之后直接将这一子树删去,然后再对剩余部分直接计算贡献,可以发现实际上就是在计算在以 为根的子树内而在以 为根的子树外的点的贡献。
用线段树维护 dfn 进行删点(将权值更改为 )和查询区间最大值操作即可。
对于一个字符串,若其可以由多于一个相同的字符串相连构成,则称其为好的字符串。
求对于一个字符串 ,最少需要删除几个点才能使该字符串变成好的字符串。
,保证 由小写字母构成。
设最后被分为 段,考虑到对 进行分开处理。
当 较小时,直接枚举最终得到的字符串的分界点,对所有被分开的字符串求 LCS,可以使用 dp 解决,复杂度 。
当 较大时,因为单个循环节的长度不会太大,所以考虑枚举 ,即单个循环节,贪心地在 中匹配 ,复杂度为 。
对于 使用第一种,对于 使用第二种,复杂度即为 ,跑得飞快。
在一个数轴上有 个陷阱,每个陷阱占据了 的所有位置。
有两种行动方式:
第一种是向右一个单位,花费 的时间。
第二种是向右 个单位,花费 的时间。
每次移动都需要移动到的位置不能被陷阱占据, 给定。
共有 个计划,第 个计划是从初始位置 移动到位置 ,求对于每个计划,完成计划的最短时间,如果不能完成计划,输出 。
根据 和 的大小关系分类讨论。
较为简单的情况是 :此时需要最小化跳跃次数。设 表示到达 需要最少跳跃几次。转移是简单的 。尝试分析 的结构。对于一段可以到达 的区间,无法到达的位置构成一个前缀。
实际上,剩余部分的 fi 值是相等的,也就是在一 段内不存在一个位置 使得 。可以归纳证明,假设前 段每段相等且总体递增,那么第 段也满足上述条件。快速维护是简单的。
对于 ,。每段无法到达的部分依然是一个前缀。有以下结论:有值的部分形如先一段值为 ,长度 的段,而后是长度为 的值为 的段。这个结论可以类似的归纳证明。所以只需要对于每段求出有值的第一个位置和值与第一个位置不同的第一个位置就可以表示整个 dp 数组。可以用二分套二分维护做到 ,也可以精细实现做到 。
询问只需要二分找到询问的位置位于那一段内然后就可以快速求值了。
一棵树上有 个点,每个点上初始权值为 。
两人先后选择一个点清除,并使其他所有点上的权值向这个点迁移一次。
当最后一个点被清除后,清除掉最后一个点的人获胜。
问在都选择最优策略的情况下,谁能获胜。
可以将清除这一过程转化为树上所有除了所选的点之外的叶子节点被删除。
考虑树上的一条条链,每次变化会使树上的链长度减少 或 ,显然直径会最后被删完。
因此只需要考虑直径,将每次操作转化为使链长减少 或 ,这就是一道经典的小学奥数题。
有一条长为 ,由 三种字符构成的字符串。
每次操作可以删除任意一个形如 之一的子序列。
如果能删除整个序列,就可以获得 的值, 给出, 分别代表字符串中 的数量。
给出 ,求对于所有数量为 的起始字符串长度,可以得到的值的和。
对于这样的字符串删除,考虑将其转换为字符串匹配(什么神仙思路)。
观察到对于所有可删除的子序列, 都在末尾,将其转化为右括号。
因为存在 ,所以我们将 转化为一个左括号,将 转化为两个右括号。
又因为存在 ,所以考虑将 转化为两个左括号或者一个右括号,代入 发现没问题。
又因为若存在两个 将前一个当作左括号,后一个当作右括号一定是没问题的。
于是设 表示当前填充到第 位,剩余 个左括号, 目前是否出现过被当成右括号的 的情况下的最大权值和,转移是不难的。
有一个长度为 的序列 ,可以进行若干次操作,每次操作形如 ,可以将 的所有数改为 或 ,若 为 则将区间改为 ,若为 则改为 。
如果能将序列更改为给定的序列 则视为获胜,求出是否能获胜,如果能,给出操作方案,要求操作次数不超过 。
首先,根据操作的性质,我们注意到不可能将形如 的序列转换为 ,即不能改变数字间的顺序。
因此我们只需要能在 序列中找到一个顺序包含 序列中所有数的子序列则有解。
考虑如何构造方案,可以将 中的子序列和 中对应区间分类考虑,设 中位置为 , 中对应区间为 。
并且,因为 和 是单调递增的,因此构成一定是一段前缀属于情况 ,一段后缀属于情况 ,其余部分属于情况 。
因此我们构造情况 为按 递减顺序做,情况 为按 递增顺序做,情况 可以随便做。
证明操作次数:我们考虑到,对于 需要做两次的情况当且仅当 ,但此时实际上覆盖了长度至少为 的区间,而其他情况下都只需要做一次操作, 不会出现重复,所以总的操作次数一定不超过 。
有一个长度为 的序列 ,要将其划分为若干()个区间,每个划分方案的贡献是所有区间的极差之和。
每次操作将选择一段区间并将该区间内所有数增加 ,并求出每次修改后的最大划分贡献,询问不独立。
考虑原序列的差分序列,显然可以发现当取到最大值时需要使一段区间内全部同正或同负。
那么只需要考虑每段差分序列的左右两边是否被选既可。
每次操作相当于两次单点修改,使用小清新线段树即可通过。
给出一个 个点, 条边的无向图,每条边有初始颜色 和目标颜色 。
每次操作可以选择一个点将其所有连边转换成任意相同颜色。
需要给出一种操作方案使得图上所有边都变为其目标颜色 ,或者报告无解。
,其中 表示颜色个数。
首先我们注意到,每次操作的处理都要考虑后续带来的变化,而不需要考虑之前的操作。
所以我们倒序地进行操作的分析。
此时问题就被简化成需要找到每一个周围所有 都相等的点。
于是这就是一个类似于拓扑排序的问题,每次处理的时候使得一部分边不用考虑,再把新产生的符合所有 都相等的点加入。
最后当没有点可加入时,假如剩余的所有边都是 的,就可以输出答案,反之则无解。
用队列维护点,map 或诸如此类的东西维护边的类型,复杂度
对于一个 个点, 条边的简单无向图,求这张图中团的个数对 取模。
对于一张图 ,其点集的一个子集 被称为一个团当且仅当其导出子图是完全图。
首先这题是一个 #pc 问题,不存在多项式复杂度的解法,因此我们考虑指数级的算法。
因为边数不超过 ,而一个团内的边数是阶乘级别的,所以显然一个团内的边数不会太多,因此考虑让指数上为 级别。
我们考虑如何缩减每个点的度数,可以使用类似三元环计数的处理方法,将所有点按照编号从小到大排序,只保留编号较小的点到编号较大的点的连边,则新图上每个点的度数是 级别的,计算答案时枚举每个团中度数最小的点即可。
而 仍有 ,显然无法接受,但是可以考虑折半搜索,枚举前后各 个点,复杂度就会被控制在合理的范围内。
最后使用 SOSDP 即可,该算法可以在 的时间复杂度范围内计算出度数为 的点对答案的贡献,因此总复杂度约为 ,常数相当小,可轻松通过。
有一个 大小的矩形,其上每个位置都有一个值。
求有多少个子矩形,满足该子矩形内所有值都严格小于与这一位置横坐标或纵坐标相同的边界上的值。
我们注意到因为要求矩形内的数严格小于对应边界上的数,对于 的情形,合格的 构成嵌套集,即不会出现两组 和 满足它们对应的线段既不是相离关系也不是包含关系, 同理。
因此,对于每一个 ,可能成为答案的 只有 组, 同理。求出这些区间可以使用单调栈,这部分是 的。
考虑一个矩形合法的充要条件,假设 和 合法,我们相当于要让 合法(A),以及 合法(B)。
我们可以对于每个合法的 ,预处理出 表示 时 性质才满足, 性质同理,时间复杂度同样为 。
枚举矩形的左上角,相当于要求满足条件的 二元组使得 。这是一个二位数点的形式,可以使用树状数组求解,复杂度 。
在一个 个点的数轴上,第 个点可以一步到达 中的点,保证 。
可以选择一个点出发,经过一段时间后再次回到这个点,每个点可以走超过一次,设这段路程经过点的点集为 。
求有多少种不同的 是可能达到的。
考虑如何 check 一个 是否合法。首先题目中的要求等价于 对应的导出子图强连通。
结论是,我们每次将 中相邻的两个互相可达的 SCC 合并(初始时,每个点自成 一个 SCC), 合法当且仅当通过上述过程可以将 合并为一个 SCC。
证明大概就是考虑假设某次合并了不相邻的两个 SCC,那么一定也可以调整为若干次合并相邻的两个。
但是直接这样做会算重。一种处理方法是,补集转化, 不合法当且仅当最后留下 了 个 SCC 且相邻无法合并。
记 表示 ,, 的合法 的数量,同时维护一个辅助数组表示若干个相邻之间无法合并 的 SCC 拼起来的方案数即可。时间复杂度视实现在 之间,由于常数很小理论上均可通过。
给出三个整数 ,要求找到一个数对 满足 且 ,并且满足 最大。
如果有多组解,选择 的值最小的一个。
首先 肯定都是 的倍数,所以直接将 转化为 ,在该区间内求最远互质点对,设为 ,则 。
关于如何求区间内最远互质点对,可以参考 Atcoder arc137_a(站内链接)。
如果直接暴力枚举 和 ,理论复杂度达到了惊人的 ,看上去是不能通过的。
但是其实并不会遍历那么多次,参考上面给出的题解可知在题目给出的 数据范围内复杂度并没有很高。
所以尽管并未算出实际复杂度期望,但是我们暴力枚举仍然可以通过本题。
#include <bits/stdc++.h>
using namespace std;
long long T , l , r , g;
long long gcd(long long aa , long long bb) {return bb ? gcd(bb , aa % bb) : aa;}
void solve()
{
long long len = r - l;
while(len >= 0)
{
for(long long i = l ; i <= r - len ; i++) if(gcd(i , i + len) == 1) {cout << i * g << " " << (i + len) * g << '\n'; return;}
len--;
}
cout << -1 << " " << -1 << '\n';
}
int main()
{
cin.tie(nullptr)->sync_with_stdio(false);
cin >> T;
while(T--)
{
cin >> l >> r >> g;
l += g - 1; l /= g; r /= g;
solve();
}
return 0;
}
以下是不严谨的关于该代码时间复杂度的证明。
在维基百科的这一条目中提到,托马斯·雷·奈斯利使用了公式 来计算素数间隙与克拉梅尔猜想相契合的程度,对于现在已知最大的素数间隙, 的值依然保持在 左右。
因此该题目中最大的素数间隙(设其为 ,且 表示最大的质数间隙对应的质数对中较小的那一个)在 左右,因此即使我们并不确定 的具体值, 的值一定不会超过 ,而该式的值约为 。
在该题目中,能够构造出的最坏情况为 分别距离 为 ,此时上述代码的时间复杂度为 。
又因为我们已经证明了 的值并不会超过 ,所以上述代码的最坏时间复杂度为 ,不超过 ,显然可以通过本题。
考虑维护四个数组 分别表示以位置 为起点,在其上、下、左、右四个方向上,一定选择该点的最大区间和,显然可以 DP 维护。
时间复杂度为
#include <bits/stdc++.h>
using namespace std;
const int N = 1005;
int n , m;
long long a[N][N] , u[N][N] , d[N][N] , l[N][N] , r[N][N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for(int i = 1 ; i <= n ; i++)
for(int j = 1 ; j <= m ; j++)
{
cin >> a[i][j];
u[i][j] = max(u[i - 1][j] , 0ll) + a[i][j];
l[i][j] = max(l[i][j - 1] , 0ll) + a[i][j];
}
for(int i = n ; i >= 1 ; i--)
for(int j = m ; j >= 1 ; j--)
{
d[i][j] = max(d[i + 1][j] , 0ll) + a[i][j];
r[i][j] = max(r[i][j + 1] , 0ll) + a[i][j];
}
long long ans = -0x3f3f3f3f;
for(int i = 1 ; i <= n ; i++)
for(int j = 1 ; j <= m ; j++)
{
ans = max(ans , max(l[i][j] , r[i][j]) + max(u[i][j] , d[i][j]) - a[i][j]);
}
cout << ans << '\n';
return 0;
}
本题的 做法已有,不多说。
@lailai0916 在他的题解中给出了使用数学工具进行归纳的 做法,十分优雅,下面来看看人工队的表现。
设点 在第 个圆上,点 在第 个圆上,二者之间相隔 条直线(包括点 所在的直线,不包括点 所在的直线), 为 。
因此
所以此时题目要求的就是:
我们注意到, 必定小于等于 ,并且两点之间的最短路一定是经过圆心或者只经过一条线段再经过一条圆弧(仅取决于两个点所在直线的角度大小)的,于是我们就得到:
化简 :
化简 :
化简 :
化简 :
化简 :
全部代入回原式:
最后得出结论:
#include <bits/stdc++.h>
using namespace std;
long long n , m;
inline long long tw(long long x) return x * x;
inline long long th(long long x) return x * x * x;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
long long p = 2 * m / acos(-1) + 1 , p , q;
q = ((4 * m * th(n) - 8 * p * m * th(n) - 4 * p * m * n + 8 * m * n + 12 * tw(m) * th(n) + 12 * m * tw(n) + 12 * tw(m) * tw(n) - 12 * p * m * tw(n)) / 6 - ((m * n) * (n + 1)) * (m == 1));
p = ((3 * tw(n) * tw(p) - 3 * tw(n) * p + n * tw(p) - n * p + 2 * tw(p) * th(n) - 2 * p * th(n)) / 6);
cout << fixed << setprecision(12) << p * acos(-1) + q << '\n';
return 0;
}
给定 组数据,每组数据包含两个字符串 。可以将字符串 中与字符串 相同的字串删去,问最终有多少种字符串 可能的结果?
看到字符串匹配首先想到 KMP 算法。
因为某一个字符串是否替换只影响与之相交的字符串(无后效性),所以考虑 DP。
设 表示前 位的替换方案数, 为当前已匹配到的 字符串位数,则:
于是就有了一个朴素的 KMP 混合 DP 的算法,时间复杂度为 ,甚至拿到了最优解 rk3。
#include <bits/stdc++.h>
using namespace std;
const long long N = 100005 , mod = 1e9+7;
long long T , n , m , p , nxt[N] , f[N];
string a , b;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> T;
f[0] = 1;//最开始只有一种方案
for(long long k = 1 ; k <= T ; k++)
{
cin >> a >> b;
n = a.size();
a = '#' + a;
m = b.size();
b = '#' + b;
p = 0;
for(long long i = 2 ; i <= m ; i++)//预处理
{
while(b[i] != b[p + 1] && p)
{
p = nxt[p];
}
if(b[i] == b[p + 1])
{
p++;
}
nxt[i] = p;
}
p = 0;
for(long long i = 1 ; i <= n ; i++)//kmp加dp
{
while(b[p + 1] != a[i] && p)
{
p = nxt[p];
}
if(b[p + 1] == a[i])
{
p++;
}
f[i] = f[i - 1];
if(p == m)
{
f[i] = (f[i] + f[i - m]) % mod;//dp转移
}
}
cout << "Case #" << k << ": " << f[n] << endl;//别忘记输出格式
}
//system("pause");
return 0;
}
首先,我们注意到可以把运输的过程反过来,从 点开始,运送的价值初始值为 ,每走过一条边就产生一次代价,并将运送的价值加上这条边的 。
于是我们得到如下结论:
若有三个点 满足 在从 到 的简单路径上,
令 为从点 到点 的代价, 为从点 到点 的边权 之和, 为从点 到点 的边权 之和。
则有:
于是我们就可以用树链剖分加线段树维护答案。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200005 , mod = 1000000007;
int n , G , Q;
int son[N] , dep[N] , siz[N] , fa[N] , top[N] , dfn[N] , pos[N] , tot;//树剖要用的数组
int beft[N] , befd[N];//化边为点
struct edge
{
int to , D , T;//存边
};
vector <edge> g[N];
struct seg
{
int sumd , sumt , up , down;//sumd和sumt含义如上,up表示在这个区间内从下往上走的代价,down表示在这个区间内从上往下走的代价
};
seg s[N << 1] , null;//null表示一个空点
seg operator +(seg x , seg y)
{
seg res;
res.down = (x.down + y.down + (x.sumt * y.sumd) % mod) % mod;
res.up = (x.up + y.up + (x.sumd * y.sumt) % mod) % mod;
res.sumd = (x.sumd + y.sumd) % mod;
res.sumt = (x.sumt + y.sumt) % mod;
return res;
}//将区间合并重载为加法
void dfs1(int u , int pre)//建树+预处理
{
fa[u] = pre;
if(u == 1)
{
fa[u] = u;
}
siz[u] = 1;
dep[u] = dep[pre] + 1;
for(auto [v , d , t] : g[u])
{
if(v == pre)
{
continue;
}
befd[v] = d;
beft[v] = t;
dfs1(v , u);
siz[u] += siz[v];
if(siz[v] > siz[son[u]])
{
son[u] = v;
}
}
}
void dfs2(int u , int pre)//树剖
{
dfn[u] = ++tot;
pos[tot] = u;
if(!son[u])
{
return;
}
top[son[u]] = top[u];
dfs2(son[u] , u);
for(auto [v , d , t] : g[u])
{
if(v == pre || v == son[u])
{
continue;
}
top[v] = v;
dfs2(v , u);
}
}
void build(int num , int l , int r)//建线段树
{
if(l == r)
{
s[num].sumd = befd[pos[l]];
s[num].sumt = beft[pos[r]];
return;
}
int mid = (l + r) >> 1;
build(num << 1 , l , mid);
build(num << 1 | 1 , mid + 1 , r);
s[num] = s[num << 1] + s[num << 1 | 1];
}
void update(int num , int l , int r , int x , int val)//更新点权
{
if(l > x || r < x)
{
return;
}
if(l == x && r == x)
{
s[num].sumt = val;
return;
}
int mid = (l + r) >> 1;
update(num << 1 , l , mid , x , val);
update(num << 1 | 1 , mid + 1 , r , x , val);
s[num] = s[num << 1] + s[num << 1 | 1];
}
void change(int x , int y , int t)//更改操作
{
if(dep[x] > dep[y])
{
swap(x , y);
}
update(1 , 1 , n , dfn[y] , t);
}
seg get(int num , int l , int r , int x , int y)//线段树上区间查找
{
if(l > y || r < x)
{
return null;
}
if(l >= x && r <= y)
{
return s[num];
}
int mid = (l + r) >> 1;
return get(num << 1 , l , mid , x , y) + get(num << 1 | 1 , mid + 1 , r , x , y);
}
int query(int x , int y)//查询操作
{
int res1 = 0 , nowt = 0 , res2 = 0 , nowd = 0 , totd = 0;
while(top[x] != top[y])
{
if(dep[top[x]] >= dep[top[y]])
{
seg awa = get(1 , 1 , n , dfn[top[x]] , dfn[x]);
totd = (totd + awa.sumd) % mod;
res1 = (res1 + (nowt * awa.sumd) % mod + awa.up) % mod;
nowt = (nowt + awa.sumt) % mod;
x = fa[top[x]];
}
else
{
seg awa = get(1 , 1 , n , dfn[top[y]] , dfn[y]);
totd = (totd + awa.sumd) % mod;
res2 = (res2 + (nowd * awa.sumt) % mod + awa.down) % mod;
nowd = (nowd + awa.sumd) % mod;
y = fa[top[y]];
}
}
if(x != y)
{
if(dep[x] >= dep[y])
{
seg awa = get(1 , 1 , n , dfn[y] + 1 , dfn[x]);
totd = (totd + awa.sumd) % mod;
res1 = (res1 + (nowt * awa.sumd) % mod + awa.up) % mod;
nowt = (nowt + awa.sumt) % mod;
}
else
{
seg awa = get(1 , 1 , n , dfn[x] + 1 , dfn[y]);
totd = (totd + awa.sumd) % mod;
res2 = (res2 + (nowd * awa.sumt) % mod + awa.down) % mod;
nowd = (nowd + awa.sumd) % mod;
}
}
res1 = (res1 + (nowt * nowd) % mod + res2) % mod;
return (res1 + (G * totd) % mod) % mod;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
null.down = 0;
null.sumd = 0;
null.sumt = 0;
null.up = 0;//初始化
cin >> n >> G;
for(int i = 1 , a , b , D , T ; i < n ; i++)
{
cin >> a >> b;
edge e;
e.to = b;
cin >> e.D >> e.T;
g[a].push_back(e);
e.to = a;
g[b].push_back(e);
}
dfs1(1 , 0);
top[1] = 1;
dfs2(1 , 0);
build(1 , 1 , n);//预处理
cin >> Q;
int op , x , y , t;
while(Q--)
{
cin >> op >> x >> y;
if(op)
{
cout << query(y , x) << endl;//处理询问
}
else
{
cin >> t;
change(x , y , t);//处理更改
}
}
return 0;
}
充满了血泪的控诉,详情请看原帖。
设第 个人拿到的钱为 。
注意到 ,设 为 。
则 ,可得 。
所以 。
设 。
则 。
上下同乘以 并化简得到 。
对于 的任何一个因数 ,可得 也是 的因数,而 与 互质,故 不是 的因数,所以 不是 的因数,但因为 是 的因数,所以 与 互质。证毕。
因此,若要使 为整数, 必须能够整除 。
所以将 展开,得到 。
注意到 已经出现了两次,所以不做考虑,只需要验证 能否整除 即可。
因为 ,所以可以直接枚举 和 的值。
同时 需要不大于 ,数据范围实际上并不大,只需要用 __int128
确保在运算过程中不会超出范围即可。
#include <bits/stdc++.h>
#define int long long
using namespace std;
static const int INF = 2e18;
int n , m;
int mul(int a , int b)
{
return min(((__int128_t)a) * b , (__int128_t)INF);
}
int fpow(__int128 a , int b)
{
int res = 1;
while(b)
{
if(b & 1)
{
res = mul(res , a);
}
a = mul(a , a);
b >>= 1;
}
return res;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for(int q = 2 ; fpow(q , n - 1) <= m && fpow(q , n - 1) != 2147483647 ; q++)
{
for(int r = 1 ; r < q ; r++)
{
int num = 0;
for(int i = 0 ; i < n ; i++)
{
num = min(num + mul(fpow(r , i) , fpow(q , n - i - 1)) , INF);
}
if(m % num == 0)
{
cout << q - r << " " << q << '\n';
return 0;
}
}
}
cout << "impossible" << '\n';
return 0;
}
因为只需要不小于零就可以了,所以想到每次可以选中完整的对角线。
于是代价就变成了每条从左上到右下的斜线上最小的值的绝对值(如果最小值是整数则为零)。
之后只要遍历每条斜线找出代价并累积就好了。
#include <bits/stdc++.h>
#define N 505
using namespace std;
int T , n , a[N][N] , ans = 0;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> T;
while(T--)
{
ans = 0;
cin >> n;
for(int i = 1 ; i <= n ; i++)
{
for(int j = 1 ; j <= n ; j++)
{
cin >> a[i][j];
}
}
for(int i = 2 ; i <= n ; i++)
{
int minn = 0;
for(int j = 1 ; j <= i ; j++)
{
minn = min(minn , a[i - j][n - j + 1]);
}
ans -= minn;
}
for(int i = 0 ; i < n ; i++)
{
int minn = 0;
for(int j = 1 ; j <= n - i ; j++)
{
minn = min(minn , a[i + j][j]);
}
ans -= minn;
}
cout << ans << endl;
}
//system("pause");
return 0;
}
因为可以随便转换,所以每对点的交换是对其他点对没有任何影响的。
所以考虑贪心,对于每个点对想办法减少代价。
设两个点分别为 (),
故一定产生代价的情况可以分成以下四种:
且 或 (产生 的代价)
(产生 的代价)
且 或 (产生 的代价)
最后特判中间两个人会不会产生代价,这道题就这么做完了~
#include <bits/stdc++.h>
#define N 200005
using namespace std;
int T , n , a[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> T;
while(T--)
{
cin >> n;
for(int i = 1 ; i <= n ; i++)
{
cin >> a[i];
}
int ans = 0;
for(int i = 1 ; i <= (n - 1) / 2 ; i++)
{//第二种情况会被前后各判一次,故不需要特判
if(a[i + 1] == a[n - i])
{
if(a[i] == a[i + 1] || a[n - i + 1] == a[i + 1])
{
ans++;
}
}
if(a[i] == a[n - i + 1])
{
if(a[i] == a[i + 1] || a[i] == a[n - i])
{
ans++;
}
}
}
if(n % 2 == 0 && a[n / 2] == a[n / 2 + 1])
{
ans++;
}
cout << ans << endl;
}
return 0;
}
其实就是匈牙利板子
题意翻译一下就是每一行每一列都只能选一个点。
所以我们就可以建一张二分图,将行和列转化为点存储。
对于每一个可选的点,在行和列对应的点之间连一条边。
因为每行只能对应一列,所以能放的最大个数就是二分图的最大匹配。
#include <bits/stdc++.h>
#define N 205
#define M 205
using namespace std;
int n , m , t , p[M] , ans = 0;
bool able[N][M] , vis[M];
vector <int> g[N];
bool match(int x)
{
for(int i = 1 ; i <= m ; i++)
{
if(able[x][i] && !vis[i])
{
vis[i] = 1;
if(!p[i] || match(p[i]))
{
p[i] = x;
return 1;
}
}
}
return 0;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
memset(able , true , sizeof able);
cin >> n >> m >> t;
for(int i = 1 , x , y ; i <= t ; i++)
{
cin >> x >> y;
able[x][y] = 0;
}
for(int i = 1 ; i <= n ; i++)
{
memset(vis , 0 , sizeof(vis));
if(match(i))
{
ans++;
}
}
cout << ans << endl;
return 0;
}