$ 2019 $ 暑期刷题记录 $ 2 $ (基本算法专题)
$ by~~wch $
$ BZOJ~1958~Strange~Towers~of~Hanoi $ (动态规划,递推)
题目大意:
求有 $ n $ 个盘子和 $ 4 $ 座塔的汉诺塔问题。
$ solotion: $
- 首先需要参考一下三座塔的汉诺塔问题: $ g[i]=2*g[i-1]+1 $
- 然后我们将四座塔转换为三座塔问题:将前 $ i $ 个盘子一道2号塔,剩下的 $ n-i $ 个为三汉诺问题移到4号塔,再将前 $ i $ 个用四汉诺塔问题移到4号塔。
- $ f[n]=min_{1\leq i < n}{2*f[i]+g[n-i]} $
$ code: $
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
$ BZOJ ~1218 ~ $ 激光炸弹(二维前缀和)
$ solotion: $
- 比较明显的二维前缀和问题
- 就是这一题有点卡常,如果暴力转一维前缀和会超时
- 我们需要自己寻找每个点前缀和的关系:
- $ s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j] $
$ code: $
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
$ POJ~3263~Tallest~Cow $(前缀和,贪心)
$ solotion: $
- 很久以前就做过,一道比较有意思的前缀和做法
- 我们首先可以贪心想到牛的身高每次只减1,然后求出最少的减少次数
- 每头牛的身高减少次数是确定的,我们对于每一对可以互相看见的牛,他们中间的牛的身高都要减少一次
- 所以我们在 $ A_i $ 处加一,在 $ B_i+1 $ 处减一然后求前缀和就可以知道每一头牛的减少次数
- 然后这题不用担心每头牛都要比两端点的牛矮,所有区间一定是包含关系,而不会相交
$ code: $
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
$ POJ~1845~Sumdiv $(约数和定理)(分治)
题目大意:
求 $ A^B $ 的所有约数之和
$ solotion: $
- 首先要知道约数和定理: $ (1+p_1+p_1^2+...+p_1^{c_1})(1+p_2+p_2^2+...+p_2^{c_2})...*(1+p_k+p_k^2+...+p_k^{c_k}) $
- 然后本题就比较好做了, $ A^B $ 的所有约数之和就是让上式中所有 $ c_i $ 乘上 $ B $ 即可
- 然后括号里面的东西直接用分治求即可
$ code: $
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
$ POJ~2018~Best~Cow~Fences $(二分答案构造新权值)
单独的一篇题解
$ POJ~3889~Fractal~Streets $(模拟)
单独的一篇题解
$ Codeforces~670C~Cinema $(离散化)
$ solotion: $
- 很傻的一道题目,直接离散化求出每种语言对应的人
- 然后枚举电影,求出其权值,最大的那个就是答案
$ code: $
#include
#include
题目大意:
依次读入数列,求每一个数加入时的中位数。
$ solotion: $
- 直接用小根堆加大根堆维护一下就行
- 每次记录两边总数,判断加到哪边即可
$ code: $
#include
#include
#include
#include
#include
#include
using namespace std;
int n,t,x,a,b,cnt;
inline void read(int &x){
char ch=getchar();
char c=ch;
x=0;
while(ch<'0' || ch>'9') {
c=ch;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
if(c== '-') x=-x;
}
std::priority_queue lq,sq;
int main(){
read(t);
while(t--){
read(cnt);
read(n);
while(!lq.empty()) lq.pop();
while(!sq.empty()) sq.pop();
cout<
$ POJ~2299~Ultra-QuickSort $(逆序对)
题目大意:
求一个序列里的逆序对。
$ solotion: $
- 没想会在基本算法里碰见这个,挺怀念的。
- 我们用树状数组维护一下(需要离散化),和最长上升子序列里一个板子
$ POJ~3614~Sunscreen $(贪心,优先队列)
$ solotion: $
- 这个题目比较容易想到贪心
- 我们将奶牛和防晒霜分开,都以阳光强度(最小值)排序
- 然后我们可以想到枚举防晒霜
- 用一个优先队列依次存下当前防晒霜能保护的牛
- 然后我们用优先队列求出右端点(最大阳光强度)最小的牛用这个防晒霜
- 这样肯定比用最大阳光强度的好,因为我们将最大阳光强度大的留给了后面的防晒霜。
- 而最小阳光强度在枚举时得到了保障(我们是依次加入防晒霜能保护的牛的)。
$ code: $
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
$ POJ~3190~Stall~Reservation $(前缀和,优先队列)
$ solotion: $
- 这道题的最小畜栏数是确定的
- 我们直接用差分约束前缀和的方法即可得出答案
- 但是这道还需要我们输出畜栏序号,这就需要我们动态维护那几个畜栏没有用过
- 我们用优先队列优化,按左端点枚举每一头牛,然后用优先队列记录它的右端点以及它用的哪一个畜栏,(畜栏的使用状态也需要用一个优先队列维护(因为我们要最小化畜栏数目))
- 然后在枚举过程中还要对优先队列进行取出操作,将用完的畜栏记录下来,病房会优先队列里去
$ code: $
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
$ POJ~1328~Radar~Installation $ (贪心,单调队列)
$ Solution: $
- 很早之前就做过了,好像之前叫稻草人来着
- 就是算出每一个点在x轴上可以允许的最左端和最右端
- 然后按最左然后按最左端排序,挨个在优先队列里加入最右端信息
- 一旦到达一个最右端,将它覆盖的所有点删去(包括优先队列的某些信息也会失效,用一个bool数组记录每个是否已被覆盖即可)
$ code: $
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
$ POJ~2054~Color~a~Tree $ (复杂的贪心)
单独的一篇题解
$ POJ~2965~The~Pilots~Brother'~Refrigerator $ (状态压缩,结论)
$ Solution: $
- 数据范围很小,可以直接状态压缩,然后暴力枚举
- 但是这一题还有一个结论可以更快:
- 对于一个处于闭合的开关将它所在行列上每一个元素异或1(初始为0)
- 最后还为1的格子需要改变状态。
- 这个结论博主也不知道怎么证,所以还是第一种方案保险
$ Code: $
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
$ POJ~2083~Fractal $ (模拟)
$ Solution: $
- 最近总是碰到这种模拟题,直接 $ bitset $ 模拟就行了。
- 置于转移,我们直接用二进制的或运算和左右移模拟复制粘贴就好
- 然后判断输出
$ Code: $
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
$ POJ~3741~Raid $ (平面最近点对)
单独的一篇题解
$ CH~0805~ $ 防线 (二分,前缀和,特殊性质)
单独的一篇题解
$ POJ~3197~Corral~the~Cows $ (二分,最大子矩阵)
单独的一篇题解
$ BZOJ~1045~ $ 糖果传递 (中位数,环形均分纸牌)
$ Solution: $
- 对于一个环形,我们总要去想有没有断环的方法
- 而这道题目有一个性质:最优答案一定可以存在两个相邻的人之间没有联系,相当于一条链
- 于是我们用前缀和来计算每个人需要操作的次数: $ f[i]=|s[i]-s[k]| $ , $ s[k] $ 为 链的左端
- 我们发现只要 $ s[k] $ 为 $ s[i] $ 的中位数时,总和最小,于是我们找到最小的那个中位数计算答案。
$ Code: $
#include
#define ll long long
using namespace std;
const int N=1e6+5;
ll n,a[N],sum,s[N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],sum+=a[i];
sum/=n;
for(int i=1;i<=n;i++)a[i]-=sum,s[i]=s[i-1]+a[i];
sort(s+1,s+n+1);
sum=0;
for(int i=1;i<=n;i++)sum+=abs(s[n/2+1]-s[i]);
cout<
$ POJ~1723~Soldiers $ (中位数)
单独的一篇题解
$ POJ~1220~Number~Base~Conversion $ (进制转换)
$ Solution: $
- 这个就是高精度的进制转换,我们模拟一下就好
$ Code: $
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
$ POJ~1050~To~the~Max $ (最大子矩阵和)
单独的一篇题解
$ HDOJ~4864~Task $ (贪心)
$ Solution: $
- 很套路的一道贪心
- 首先我们的价值算法和复杂度挖了一个坑:最小的时间价值比最大的难度价值要高
- 所以我们贪心可以直接按时间来,所以我们先按难度排序
- 然后用优先队列维护每一个机器能完成的最长时间的任务
- 然后暴力算答案即可(我们可以发现难度一维并不会产生后效性)
$ Code: $
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include