这道题用到了很多知识点, 是一道好题目.      第一用了状态压缩, 因为这里最多只有20位, 所以可以用二进制来储存状态 (要对数据范围敏感), 然后 涉及到了一些位运算.     第二这里是隐式图搜索, 和之前有一道bfs倒水的有点像, 就是题目和图论没有半毛钱关系, 但是却可以自己建 图来做, 把状态看作点, 把状态转移看作边.    第三因为求最短时间, 所以用了堆优化dijsktra. #include<cstdio> #include<queue> #define R…
不可多得的好题目啊,我看了别人题解才做出来的,这种题目一看就会做的实在是大神啊,而且我看别人博客都看了好久才明白...还是对状态压缩不是很熟练,理解几个位运算用了好久时间.有些题目自己看着别人的题解做出来完全不是一个味,毕竟别人给你提供了思路,比如这道题,刚看题目,怎么就能转移到是用最短路搜索呢..其次,好多注意事项这些自己想出来才真正是锻炼思维.否则总是踩着别人的脚印在行走 还有就是不得不说一说UVA上的题目,又长又难懂...实在是弄得我好烦. 说说这个题目,能够发现是个隐式图是第一个难点,然…
这道题写了两个多小时-- 首先讲一下怎么建模 我们的目的是让所有点的出度等于入度 那么我们可以把点分为两部分, 一部分出度大于入度, 一部分入度大于出度 那么显然, 按照书里的思路,将边方向后,就相当于从出度大于入度的运一个流量到 入度大于出度的点. 紫书 例题 11-13 UVa 10735(混合图的欧拉回路)(最大流) 所以我们可以把源点S到所有出度大于入度的点连一条弧, 弧的容量是出度-入度的一半 为什么容量是这样呢,等一下说 同理, 把所有入度大于出度的点和汇点T连一条弧, 弧的容量是入…
链接: https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=599 题意: 补丁在修正bug时,有时也会引入新的bug.假定有n(n≤20)个潜在bug和m(m≤100)个补丁,每个补丁用两个长度为n的字符串表示,其中字符串的每个位置表示一个bug.第一个串表示打补丁之前的状态(“-”表示该bug必须不存在,“+”表示必须存在,0表示无所谓),…
题意:有N个潜在的bug和m个补丁,每个补丁用长为N的字符串表示.首先输入bug数目以及补丁数目.然后就是对M个补丁的描述,共有M行.每行首先是一个整数,表明打该补丁所需要的时间.然后是两个字符串,第一个字符串是对软件的描述,只有软件处于该状态下才能打该补丁该字符串的每一个位置代表bug状态("-"代表该位置没bug,"+"代表该位置有bug,"0"表示该位置无论有没有bug都可打补丁).然后第二个字符串是对打上补丁后软件状态的描述"-…
这道题要逆向思维, 就是求出答案的一部分, 然后反过去去寻找答案存不存在. 其实很多其他题都用了这道题目的方法, 自己以前都没有发现, 这道题专门考这个方法.这个方法可以没有一直往下求, 可以省去很多时间.紫书里面把这叫做中途相遇法,双向广搜有点这个方法的味道.这里用到了二分查找, 总的时间复杂度是n的二次方乘logn #include<cstdio> #include<vector> #include<algorithm> #define REP(i, a, b) f…
紫书上有很明显的笔误, 公式写错了.g(k, i)的那个公式应该加上c(k-1)而不是c(k).如果加上c(k-1)那就是这一次 所有的红气球的数目, 肯定大于最下面i行的红气球数 我用的是f的公式, 我觉得这个稍微比f好理解一点.f(k, i) 表示k小时之后最上面i行红气球的个数. 分两种情况 如图所示 左上角的正方形的上面i行的红气球个数和前一个小时(也就是k-1)的整个正方形的上面i行的红气球个数是一样的, 因为右上角还有一个, 所以要乘2, 也就是f(k - 1, i) = 2 * f…
 这道题目可以把问题分解, 因为x坐标和y坐标的答案之间没有联系, 所以可以单独求两个坐标的答案 我一开始想的是按照左区间从小到大, 相同的时候从右区间从小到大排序, 然后WA 去uDebug找了数据, 发现这组数据过不了 3 1 1 3 3 1 1 3 3 2 2 2 2  正确输出是 1 1 3 3 1 1 2 2  我输出 IMPOSSIBLE 我发现当有包含关系的时候, 会先处理大区间而把小区间应该放的点覆盖掉了.所以我这个方法是不行滴, 然后就暂时不知道怎么改了.  之后我去看了他人的…
这道题用构造法, 就是自己依据题目想出一种可以得到解的方法, 没有什么规律可言, 只能根据题目本身来思考. 这道题的构造法比较复杂, 不知道刘汝佳是怎么想出来的, 我想的话肯定想不到. 具体思路紫书上讲得非常清楚了, 就不讲了.代码有详细注释 #include<cstdio> #include<vector> #define REP(i, a, b) for(int i = (a); i < (b); i++) using namespace std; const int M…
总的来说就是价值为1,时间因物品而变,同时注意要刚好取到的01背包 (1)时间方面.按照题意,每首歌的时间最多为t + w - 1,这里要注意. 同时记得最后要加入时间为678的一首歌曲 (2)这里因为要输出时间,也就是重量,那么这个时候初始化就要注意了. 因为如果只是输出价值的话就全部初始化为0,但是要输出重量,那就意味着 当前这个时间是恰好由几首歌组合,那么初始化的时候就要注意全部初始化为 -1,f[0] = 0,同时判断条件要f[j-w] != -1,这里要注意 (3)这里时间很坑!我一开…
紫书365 题目大意:给你n个全都是bug的东西,然后每次可以修复,给你修复前后的状态,问最后如果能把bug全都修复,最少需要多少时间. 思路:从最初状态开始,然后枚举bug即可. 表示priority里面的bool operator和单纯的sort的定义的大小于号是不一样的啊,如果你想用sort来计算struct从小到大的的话是这样的 struct Node{ int bugs, dist; bool operator < (const Node &a) const{ return dis…
很明显的状态压缩思想了.把全集分组,枚举每个集合的子集,看一个子集是否能覆盖所有的点,若能,则f[s]=max(f[s],f[s^s0]+1).即与差集+1比较. 这种枚举集合的思想还是第一次遇到,果然太弱了....~~~~ 其中枚举集合 for(s0=s;s0;s0=(s0-1)&s) #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> usin…
首先做一个转化,这种转化很常见. 题目里面讲要来回走一遍,所以就转化成两个从起点到终点,路径不重合 那么很容易想到用f[i][j]表示第一个走到i,第二个人走到j还需要走的距离 但是这里无法保证路径不重合,所以这里怎么设计状态很关键. 我们设f[i][j]是1到max(i, j)全部走过,同时第一个在i,第二人在j, 还需要走的距离,可以看出f[i][j] = f[j][i],所以我们可以规定i > j 那么这么规定有什么好处呢?我们可以让两个人走的路径是1, 2, 3, 4-- 换句话说,当前…
这道题想了很久不知道怎么设置状态,怎么拓展,怎么判重, 最后看了这哥们的博客 终于明白了. https://blog.csdn.net/u014800748/article/details/47400557 这道题的难点在于怎么设置联通的状态,以及怎么拓展判重 . (1)状态:这里状态先定义了一个格子cell, 有x和y坐标.然后set<cell>表示一个联通块, 再用set<set<cell>>表示n个连块可以组成的所有联通块, 这里是集合套集合. (2)拓展:每个格…
这里用到了一些数论知识 首先素因子都大于M等价与M! 互质 然后又因为当k与M!互质且k>M!时当且仅当k mod M! 与M!互质(欧几里得算法的原理) 又因为N>=M, 所以N!为M!的倍数 所以只要求1到M!中与M!互质的数的个数,在乘上N!/M! 可以理解为每一块M!有这么多,然而N!中有很多块M!,所以乘上N!/M! 然后根据phifac[n] = phi[n!] = n!(1-1/p1)(1-1/p2)......(1-1/k)的定义可以得出 当n为质数的时候 phifac[n]…
就是暴力枚举a, b然后和题目给的数据比较就ok了. 刘汝佳这道题的讲解有点迷,书上讲有x1和a可以算出x2, 但是很明显x2 = (a * x1 +b) 没有b怎么算x2?然后我就思考了很久,最后去看他的代码发现他的代码和他讲的是两回事 他的代码里直接是枚举a和b,不是按照书上的思路来的. 有点迷 #include<iostream> #define REP(i, a, b) for(int i = (a); i < (b); i++) using namespace std; con…
首先可以用扫描法处理出一个height数组, 来保存从当前行开始, 每一个格子可以向上延伸的最大长度. 这种"延伸"的问题用扫描法, 因为往往这个时候可以利用前一次的结果来更新当前的值 然后这道题的关键就是是维护一个单调栈, 栈顶的元素就是当前状态所求的答案. 这个单调栈满足的性质是:c从小到大增加, h从小到大增加, h-c从小到大增加.c表示当前列, h表示height[c] 因为遍历的时候是从左到右的, 所以c就是一直增大的, 然后加入的时候有个while循环, 保证h是一直增大…
以9元组来代表当前状态,每一元是每一堆剩下的牌数 枚举当前状态所有可以拿掉牌的情况,然后递归下去求 概率,当牌拿完的时候概率为1 那么这里的实现非常的秀,用到了vector来代表9元组 然后还用到了map来实现记忆化搜索 #include<cstdio> #include<vector> #include<map> #define REP(i, a, b) for(int i = (a); i < (b); i++) using namespace std; ch…
这里的状态定义的非常的巧妙,d(i, 1)表示以i为根节点且选i的子树的最大独立子集 d(i, 0)表示以i为根节点且不选i的子树的最大独立子集 d(i, 1) = sum{ d(v, 0) | v是i的儿子} d(i, 0) = sum{ max(d(v, 0), d(v, 1)) | v是i的儿子} 答案为 max(d(0, 0), d(0, 1)) 至于唯不唯一,很好推,当子树中有一个是不唯一的,那么当前节点就不唯一,或者有两个子树答案是一样的,也是不唯一的. #include<cstdi…
当前区间f(i, j)分两种情况,一种是s[i]于s[j]符合要求,那么可以转移到f[i + 1][j - 1] 这样答案只会更小或者相等 第二种是直接分成两个部分, 即f[i][j] = f[i][k] + f[k + 1][j],这个时候要取min 同时要注意第一种情况未必是最优的,要从一二两种情况里面取最优值 然后输出方面,按照答案反推,如果当前状态刚好等于其中一种情况,那么就递归下去,边界是 一个字符的时候输出两个字符. 另外学会用fgets,不用gets.fgets头文件cstdio,…
区间dp,可以以一个区间为状态,f[i][j]是第i个切点到第j个切点的木棍的最小费用 那么对于当前这一个区间,枚举切点k, 可以得出f[i][j] = min{dp(i, k) + dp(k, j) | i < k < j} + a[j] - a[i](这一段的长度,也就是这一刀的费用) 然后记住要人为的加入两个切点头和尾 然后因为长区间依赖于短区间,所以要从短区间渐渐推到长区间. 如果是记忆化搜索,那么就是左端点和右端点不断减少,递归,满足. 如果是递推,那么注意区间长度要不断变长,具体看…
很明显可以根据放不放建边,然后最一遍最长路即是答案 DAG上的动态规划就是根据题目中的二元关系来建一个 DAG,然后跑一遍最长路和最短路就是答案,可以用记忆化搜索的方式来实现 细节:(1)注意初始化数组 (2)搜索的过程中最后记住加入状态本身的值,不然会答案全部为0 #include<cstdio> #include<algorithm> #include<cstring> #define REP(i, a, b) for(int i = (a); i < (b)…
影响到状态的只有时间和在哪个车站(空间),所以可以设f[i][j]是时刻i的时候在第j个车站的最少等待时间 因为题目中的等待时间显然是在0时刻1车站,所以答案为f[0][1],那么就提醒我们从大推到小,然后可以发现 d[T][n]一定等于0,所以这个可以作为边界条件.同时时刻0的每一个车站都是正无穷,相当于把i = n的时候 全部初始化好了. 然后有三种决策 (1)在当前车站等一分钟 f[i][j] = f[i+1][j] + 1; (2)坐往左开的车 if(j + 1 <= n &&…
这道题方法非常的巧妙, 两层的n*n, 第一层第I行全是第I个国家, 第二层的第j列全是第j个国家.这样能符合题目的条件.比如说第1个国家, 在第一层的第一行全是A, 然后在第二层的第一行就有ABCDE--这样A就和所有的国家都连接了,其他国家也是一样的.只能说这种方法非常巧妙吧,答案讲出来很简单,但是不容易想到. #include<cstdio> #define REP(i, a, b) for(int i = (a); i < (b); i++) using namespace st…
#include<cstdio> #include<iostream> #include<sstream> #include<algorithm> #define REP(i, a, b) for(int i = (a); i < (b); i++) using namespace std; const int MAXN = 50; int a[MAXN], n; void filp(int pos) //学习这里翻转的写法 { REP(i, 0, p…
这道题发现一个性质就解决了 如果以i为起点, 然后一直加油耗油, 到p这个地方要去p+1的时候没油了, 那么i, i+1, --一直到p, 如果以这些点 为起点, 肯定也走不完. 为什么呢? 用反证法, 假设以q(i  < q <= p)这个点为起点可以走完的话, 那么i这个点也一定可以走完 首先, i是可以达到q的, 因为i可以达到p, 而q是在p前面的, 而且从i开始走到q这个点剩下的油量肯定大于等于0, 而如果单纯从q开始走的话, 油量会等于0, 也就是说从i过来所有的油量反而会更多,…
解法和合并果子是一样的, 每次取最小的两个, 更新答案, 加入队列 #include<cstdio> #include<queue> #define REP(i, a, b) for(int i = (a); i < (b); i++) using namespace std; int main() { int n, x; while(~scanf("%d", &n) && n) { priority_queue<int, v…
这道题让最大值最小, 显然是二分答案 当题目求的是最大值最小, 最小值最大, 这个时候就要想到二分答案 为什么可以二分答案呢, 因为这个时候解是单调性的, 如果简单粗暴一点 就全部枚举一遍, 验证答案.但是因为答案满足单调性, 可以用二分的方法 来"枚举", 复杂度可以从n降到logn 开始我自己写了一个, 但是WA, 后来看了刘汝佳的代码, 发现要注意三点 (1)这道题的和的最大值会爆int, 要用long long. 养成看到题目的时候计算最大值看会不会爆int的习惯(int最大大…
这道题用了数形结合, 真的牛逼, 完全想到不到还可以这么做 因为题目求的是平均值, 是总数除以个数, 这个时候就可以联系 到斜率, 也就是说转化为给你一堆点, 让你求两点之间的最大斜率 要做两个处理 (1)去掉上凸点, 因为上凸点是无论如何都不会为最优解的 (2)去掉之后每两个点之间的斜率是单调递增的, 这个时候要求切点. 切点即最大斜率, 所以就枚举终点, 然后找该终点对应的最大斜率 (也就是找到切点), 然后更新答案. #include<cstdio> #define REP(i, a,…
设切割的区间为(j, i), 注意两边都是开区间. 然后可以预处理出以i为起点的最长连续递增的长度和以j为终点的最长连续递增的长度. 大致思路就是枚举i,右边这一侧的最优值就知道了, 然后这道题的关键就是就是j取哪里. (1)去掉干扰元素, 这一步非常的关键, 设题目给的数组为a, g(i)表示以i为结尾的最长递增序列长度 在j < i中, 如果 a[j'] <= a[j] 同时 g(j') > g(j), 那么 j这个元素肯定不是最优的.因为如果j可以取的话 j'就一定可以取, 而且更…