追逐学长的背影 - 2015年10月
https://www.cnblogs.com/DaD3zZ-Beyonder/archive/2015/10.html
加油啊布丁酱!
跳过了一个LIS的随笔。(LIS类似升序的单调队列,有模板的题先不看了)
2704:寻找平面上的极大点
先按x的大于序排序,再按y的大于序排序。从第一个点开始找,记录当前最大的y,每次新的y比当前y大的时候更新y。 $O(nlogn)$
2469:电池的寿命
首先看到这里的直觉就是总电量的一半(因为是连续的,所以可以通过几颗电池去削减大电池的电量,构成两个相等的电池之后可以轮流削减两个相等的电池的电量指直到自己的电量耗完,再把两颗剩余的相等的电池耗完,这样就证明每三颗(比较平均的)电池都可以完全利用,类似的思路可以猜测都是可以完全削减的),但是我少考虑了一种情况,就是有一颗超大的电池,要把其他的电池都和他匹配。
1768:最大子矩阵
这么小的数据量可以用前缀和,暴力枚举每个子矩阵查询 $O(n^4)$ 。学长也是用暴力枚举的,不知道他怎么卡过去的。搜了一下解法,可以压缩一个维度,比如先用前缀和支持了 $O(1)$ 查询,然后 $dp[i][j][k]$ 表示以 $(i,j)$ 为右下角,高度为 $k$ 的最大子矩阵,那么每次从左到右转移的时候, $dp[i][j][k]=dp[i][j-1][k]>=0?(dp[i][j-1][k]+sum(i,j,k)):sum(i,j,k))$ ,也就是最长上升子串的dp方法,这样是 $O(n^3)$ 。
7617:输出前k大的数
数据量太小了导致可以直接sort,而先用nth_element()之后还是要sort(因为数据量太大不能桶排序)。
7622:求排列的逆序数
可以使用归并排序的修改来统计逆序数,也就是先分治,然后右边的每次出队的时候,这个元素对应的逆序数就是左边队列中剩余的元素的数量,之前的那道题突然就会解决了!布丁酱加油!
#include<iostream> #include<cstdio> using namespace std; int a[100010]= {0},L[100010]= {0},R[100010]= {0}; long long ans=0; #define M 1000000000; void gb(int left,int mid,int right) { int ll=mid-left+1; int lr=right-mid; for (int i=1; i<=ll; i++) L[i]=a[left+i-1];//此操作方便处理 for (int i=1; i<=lr; i++) R[i]=a[mid+i]; L[ll+1]=M;//将最后一个赋给一个极大值,可以当作一个“哨兵”非常的精妙,然而经测试似乎不加这条语句会出错 R[lr+1]=M; int l=1,r=1; for (int i=left; i<=right; i++) { if (L[l]<=R[r]) { a[i]=L[l]; l++; } else { a[i]=R[r]; r++; ans+=ll-l+1;//求逆序对的唯一多出来的语句 } } } void gbsort(int left,int right) { int mid=(left+right)/2; if (left<right) { gbsort(left,mid);//不断分治 gbsort(mid+1,right); gb(left,mid,right); } } int main() { int n; scanf("%d",&n); for (int i=1; i<=n; i++) scanf("%d",&a[i]); gbsort(1,n); printf("%lld",ans); return 0; }
7891:一元三次方程求解
我的想法是,先求导变成二次方程,然后用求根公式求出二次方程的根,那么三次方程的极大值点和极小值点就确定了。根据极大值和极小值是否包夹着x轴可以判断有多少个根,在有根的区间内三次方程是单调的可以用二分法求出零点。类似的思路我们可以解出四次方程的交点……虽然讨论一定很复杂就是了。注意单调性对二分的影响。
7909:统计数字
惯性思维想用map,但其实sort一下就可以了。
树上倍增求LCA
学长真强,我是入坑一年多才学的。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> using namespace std; vector <int> g[100010]; int father[100010][40]= {0}; int depth[100010]= {0}; int n,m; bool visit[10010]= {false}; int root; void dfs(int u) { int i; visit[u]=true; for (i=0; i<g[u].size(); i++) { int v=g[u][i]; if ( !visit[v] ) { depth[v]=depth[u]+1; dfs(v); } } }//深搜出各点的深度,存在depth中 void bz() { int i,j; for (j=1; j<=30; j++) for (i=1; i<=n; i++) father[i][j]=father[father[i][j-1]][j-1]; }//倍增,处理father数组,详情参照上述讲解 int LCA(int u,int v) { if ( depth[u]<depth[v] ) { int temp=u; u=v; v=temp; }//保证深度大的点为u,方便操作 int dc=depth[u]-depth[v]; int i; for (i=0; i<30; i++) { //值得注意的是,这里需要从零枚举 if ( (1<<i) & dc)//一个判断,模拟一下就会很清晰 u=father[u][i]; } //上述操作先处理较深的结点,使两点深度一致 if (u==v) return u;//如果深度一样时,两个点相同,直接返回 for (i=29; i>=0; i--) { if (father[u][i]!=father[v][i]) { //跳2^j步不一样,就跳,否则不跳 u=father[u][i]; v=father[v][i]; } } u=father[u][0];//上述过程做完,两点都在LCA下一层,所以走一步即可 return u; } int main() { int i,j; scanf("%d",&n); for (i=0; i<=n; i++) g[i].clear(); for (i=1; i<n; i++) { int a,b; int root; scanf("%d%d",&a,&b); g[a].push_back(b); father[b][0]=a; if (father[a][0]==0) root=a; } depth[root]=1; dfs(root); bz(); int x,y; scanf("%d%d",&x,&y); printf("%d",LCA(x,y)); return 0; }
BZOJ 1088 扫雷Mine
这个肯定是可以搜索解法的,每次枚举有没有雷然后验证。感觉搜索很快就会返回。然后我们知道,假如已知两个方格和第二个方格正下方的数字,可以唯一确定第三个格的数量,所以dfs是只会一路向前搜索的,不会再有分支。
BZOJ 1034 泡泡堂BNB
由鹏哥赛马的启发,最菜的肯定是去消耗对方最厉害的。最菜的那个平局的情况,有两种思路,要么去消耗对方最厉害的,有可能避免自己队得0分……还是太乱了。
https://www.cnblogs.com/ljh2000-jump/p/5699153.html:这道题就是田忌赛马,策略很简单,如果当前最差的能比对方当前最差的强,就让当前最差的与对方最差的比(显然当前已经是最差的了,在能战胜对方最差的前提下,肯定是出动己方越差的越好);如果不满足,则比较当前最强的和对方最强的,如果比对方强则直接对比。如果都不满足,就考虑用己方最差的直接与对方最强的比,这显然是可行的,反正己方对上对方最强的都不能胜利,那么还不如用自己最差的去换掉对方最强的。
还有一种情况,就是存在相同的时候呢?强的相同,那看看弱的那个会不会输,弱的那个会输则交换。否则不交换。弱的相同,则看看强的会不会输,强的会输则交换,否则不交换。但是这为什么一定是最优的呢?
其实假如先把最大的最小的能打的都打了,剩下的交换就一定不会更差。
链式前向星
前向星:可能比vector好的写法,但是可能会比vector难写。把所有边按 $(u,v)$ 排序,那么所有同起点的边都在一起了,然后遍历一次找到同起点的边的右边界,保存left[i]和right[i],那么 $[edge[left[i]],edge[right[i]])$ 就是所有从 $i$ 出发的边。
更简单的是next数组。
链式前向星:head[i]表示以i为结尾的最后一条边的位置,e.next[j]表示第j条边的下一条兄弟边的位置。在add_edge(u,v)的时候只需要 e.to=v;e.next[cnt]=u;head[u]=cnt;cnt++; 初始化为-1,然后搜索的时候 for(int i=head[u];i!=0;i=edge[i].next) 就可以了,这里默认edge[0]不放任何东西!也就是边从1开始计数。
BZOJ 1029 建筑抢修
自己做题多累啊,看别人的题解轻松又愉快,之前我为什么一直要坚持不看呢?先看别人做提升一定实力再说吧。
这个看学长的思路是贪心,先按结束时间排个序(再按耗时短排序可以减少后面的比较),然后假如当前的建筑可以修好,就修,否则从已经修好的一个建筑中选一个耗时最长的(假如比当前这栋更长)放回等待的队列(的队首,所以要双端队列),因为这样修好的建筑数量不变但是结束时间提前了所以更好。已经修好的建筑就用堆来维护就好了。
BZOJ 1064 假面舞会
学长说是dfs判环……的确没毛病。假如出现了(不从外面进来的)环,环就是最大/最小值。假如没有出现环,那就是几条链,链全部连起来就是最大值,最长的链就是最小值。
上面的分析是错的。环中的某个位置也可以是同类的人!从外面进来的环,也可以贴到环里面(指向同一个节点的两个节点必定收缩),最后会收缩出一些环和一些链。链可以任意折叠还可以连到环上,毫无影响。
所以k必然是每个环的约数,那就是最大公约数和>=3的最小公约数啦!还有一些细节先不想了。