刷题周记(十五)——#倍增:ST表、Balanced Lineup G、国旗计划;#单调队列优化:修剪草坪、宝物筛选、跳房子#背包:金明的预算方案#DP:传纸条

文章目录

  • ——2021年01月31日(周日)——————————————————
  • #倍增
  • 一、ST表
    • 二、Balanced Lineup G
    • 三、国旗计划
  • ——2021年02月01日(周一)——————————————————
    • 四、开车旅行
  • #单调队列优化
    • 五、宝物筛选(洛谷P1776)
    • 六、修剪草坪(P2627 [USACO11OPEN]Mowing the Lawn G)
  • ——2021年02月02日(周二)——————————————————
    • 八、跳房子
  • ——2021年02月03日(周三)——————————————————
  • #四边形优化
  • ——2021年02月04日(周四)——————————————————
  • #分组背包
    • 九、金明的预算方案
  • #DP
    • 十、传纸条
    • 十一、乘积最大
  • ——2021年02月05日(周五)——————————————————
  • ——2021年02月06日(周六)——————————————————
  • ——(完)——————————————————
  • To be continue……

——2021年01月31日(周日)——————————————————

#倍增

刷题周记(十五)——#倍增:ST表、Balanced Lineup G、国旗计划;#单调队列优化:修剪草坪、宝物筛选、跳房子#背包:金明的预算方案#DP:传纸条_第1张图片

f[a][k]代表从a开始算起,长度为2k的区间的最大(最小)值是多少
由于长度被划分为二进制的样子,所以每次计算从a开始的长度为2k+1的区间中最大(最小)值的时候,只需要将f[a][ 2k+1 ] 以及f[ a + 2k+1 ][ 2k+1 ]进行比较就好了。
处理好初始化后,找到2n < L < 2n+1,那么就用两个长度为2n的段进行覆盖,然后从这两段中比较最大(最小)值就可以得到答案了。
最后的公式是:ans = max( f[1][ 2n ], f[ L - 2n ][ 2n ]
还有一件事:
取log的时候可以这样写:int x = log2(k),会自动向下取整;
或者int x = (int)log((double(R - L + 1)) / log(2.0);
或者想快一点的自己去手写;

一、ST表

题目点这里

被题目骗了。方法对的话快读根本用不上。

#include
using namespace std;
const int N = 1e5 + 10;
typedef long long ll;

ll f[N][30];
ll a[N];
ll n, m;
ll p_2[30];

int Log[N];
void init()
{
     
	p_2[0] = 1;
	for(int i = 1; i <= 20; i ++) p_2[i] = p_2[i - 1] * 2;
	
	Log[0] = -1;
	for(int i = 1 ; i <= n ; i++) Log[i] = Log[i >> 1] + 1;
}

int main()
{
     
	scanf("%lld%lld", &n ,&m);
	init();
	for(int i = 1; i <= n; i ++) scanf("%lld", &f[i][0]);
	
	for(int l = 1; l <= Log[n]; l ++)
		for(int i = 1; i + p_2[l] - 1 <= n; i ++)
			f[i][l] = max(f[i][l - 1], f[i + p_2[l - 1] ][l - 1]);
			
	int l, r;
	for(int i = 1; i <= m; i ++)
	{
     
		scanf("%d%d", &l, &r);
		int l_2 = Log[r - l + 1];
		printf("%lld\n", max(f[l][l_2], f[r - p_2[l_2] + 1][l_2]));
	}
	return 0;
}

二、Balanced Lineup G

不知道为什么,假如我把初始化函数Init()放在输入之前就会无法初始化……迷惑,这种奇奇怪怪的错误能够卡我半小时。
题目

#include
using namespace std;
const int N = 1e5 + 10;

int p[20], Log[N];
int n, q;
int f[N][20], d[N][20];
void init()
{
     
	p[0] = 1;
	for(int i = 1; i <= 16; i ++) p[i] = p[i - 1] << 1;
	Log[0] = -1;
	for(int i = 1 ; i <= n ; i++) Log[i] = Log[i >> 1] + 1;
}

int main()
{
     
	scanf("%d%d", &n, &q);
	init();
	for(int i = 1; i <= n; i ++)
	{
     
		scanf("%d", &f[i][0]);
		d[i][0] = f[i][0];
	}	
	
	for(int len = 1; len <= Log[n]; len ++)
		for(int i = 1; i + p[len] - 1 <= n; i ++)
		{
     
			f[i][len] = max(f[i][len - 1], f[i + p[len - 1]][len - 1]);
			d[i][len] = min(d[i][len - 1], d[i + p[len - 1]][len - 1]);
		}	
	
	for(int i = 1; i <= q; i ++)
	{
     
		int l, r;
		scanf("%d%d", &l, &r);
		int l_2 = Log[r - l + 1];
		printf("%d\n", max(f[l][l_2], f[r - p[l_2] + 1][l_2]) - min(d[l][l_2], d[r - p[l_2] + 1][l_2]));
	}
//	for(int i = 1; i <= n; i ++) printf("%d\n", Log[i]);
	return 0;
}

三、国旗计划

三个技巧:

  1. 断环成链:
    具体而言就是:
if(w[i].R < w[i].L)
	w[i].R += m;
	m是环的长度;
  1. 贪心:
    选择一个区间i后,下一个区间只能从左端小于等于i的右端点的区间中选。
    但是每次都往后遍历n次的话,时间复杂度就是O(n2),超时。
  2. 倍增
    为了高效进行查询,参考ST算法,预设好一些“跳板”,快速找到后面的区间。
    定义go[s][i],表示从第s个区间出发,走2i个最优区间后到达的区间。
    说人话就是:从s到s + 2i之间,最大的满足条件的左端点的值。
    这个操作的时间复杂度是O(nlogn)。
    肝了一个晚上,将自己遇到的几个难点说一下:
    第一个是关于狗函数,我们设从当前区间到下一合法区间为一次,那么我们要跳好多好多次……
    为了简便运算,我们利用倍增的思想进行快速跳跃,狗就是拿来这么用的。

以上所有的操作是O(nlogn) + O(nlogn)次,完全不会超时。

#include
using namespace std;
const int maxn = 4e5 + 10;
int n, m;
struct wa
{
     
    int id, L, R;
}W[maxn * 2];

bool operator < (wa &a, wa &b)
{
     
    return a.L < b.L;
}

int n2;
//狗函数表示从第s个区间出发,走2^i个区间到达的区间
int go[maxn][20];
//神奇的预处理……尤其是那个什么狗(go)数组,麻烦死了
void init()
{
     
    //先用nxt找到下一个位置要到哪里?
    int nxt = 1;
    for(int i = 1; i <= n2; i ++)
    {
     
        //但下一个位置还在范围之内的时候,且下一个位置的L不超过i的R;
        //至于nxt为什么不用刷新,那是因为这个数列具有单调性,无论是L还是R
        while(nxt <= n2 && W[nxt].L <= W[i].R) nxt ++;
        go[i][0] = nxt - 1;
    }
    //长度
    for(int i = 1; (1 << i) <= n; i ++)
        //起点
        for(int s = 1; s <= n2; s ++)
            go[s][i] = go[go[s][i - 1]][i - 1];
}
int res[maxn];
//从第x个战士开始的话,目标战士就一定会被包含在里面了。
void getans (int x)
{
     
    //len是战士的L加一圈,用来防止R自己跑了一圈,cur是当前战士的位置,ans就是人数咯
    int len = W[x].L + m, cur = x, ans = 1;
    //i从大往小枚举,代表
    for(int i = log2(maxn); i >= 0; i --)
    {
     
        //然后就是大步大步地跳,跳到的位置没有超过len就是合法的跳
        int pos = go[cur][i];
        //首先往右夸那么多步的区间要存在
        //其次是这个区间的R小于限制
        //最后一点,先不要越过起点的左端点,最后补上;
        //因为我们不确定是不会还有兄贵守着更小的区间,有的话是不能结束的,所以一定要走到i=0。
        if(pos && W[pos].R < len)
        {
     
            //这中间跳过了2^i个人,要加上。
            ans += 1 << i;
            //然后将标记打到新的人身上。
            cur = pos;
        }
    }
    //最后答案加1
    res[W[x].id] = ans + 1;
}

int main()
{
     
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i ++)
    {
     
        W[i].id = i;
        scanf("%d %d", &W[i].L, &W[i].R);
        //要是R比L小,那就将R加一圈,变成R+m;
        if(W[i].R < W[i].L) W[i].R += m;
    }
    //按照L进行排序,当然,按照R进行排序也可以
    sort(W + 1, W + n + 1);
    n2 = n;
    //拆成链,所有的都往后延长一遍
    for(int i = 1; i <= n; i ++)
    {
     
        n2 ++;
        W[n2] = W[i];
        W[n2].L = W[i].L + m;
        W[n2].R = W[i].R + m;
    }
    init();
    //逐个计算每个战士。
    for(int i = 1; i <= n; i ++) getans(i);
    for(int i = 1; i <= n; i ++) printf("%d ", res[i]);
    return 0;
}

——2021年02月01日(周一)——————————————————

四、开车旅行

ACwing题目
洛谷题目

f[i][j][0]表示从第j点开始
.……阿巴阿巴阿巴阿巴阿巴阿巴(失去理智)……放弃了……

#单调队列优化

五、宝物筛选(洛谷P1776)

题目链接

好家伙,找到了一个之前学习多重背包优化时的错误……
之前记的笔记还是很有用的……

#include
using namespace std;
const int N = 1e5 + 10;
int f[N];
int n, m; 
int v, w, s;
int lim;
int head, tail;
struct Q{
     
	//位置, 对应的底数(base number = basenb) 
	int pos, bn;
}q[N];//q记录的是不同mod数的组里面的底数的最大值(以及它的位置)

int main(){
     
 	cin >> n >> m;
	for(int i = 1; i <= n; i ++){
     
		scanf("%d%d%d", &w, &v, &s);
		//按照不超过体积的每个数作为底数
		//既然枚举的是组数,那么不同组之间是不会被相互影响到的。
		for(int modd = 0; modd < v; modd ++){
     
			head = 0, tail = -1;
			//数量 
			for(int k = 0; k * v + modd <= m; k ++){
     
				//当前位置,以及对应的底数(now base number 缩写成 nb ) 
				int nowpos = k * v + modd, nbn = f[nowpos] - k * w;
				//头不在范围内了就弹出队头
				//不在范围内就是说:总的s的数量的体积已经无法触及到底数的对应位置了,
				//也就是bpos = 1,但是k = 4, s = 2,此时就是k的长度无法涉及的范围了。
				if(q[head].pos < k - s && head <= tail) head ++;
				while(q[tail].bn <= nbn && head <= tail) tail --;
				//队尾 ,这里的pos之前写错了……但是在某wing上还是过了……water。
				q[++ tail].pos = k, q[tail].bn = nbn;
				f[nowpos] = max(f[nowpos], q[head].bn + k * w);
			}
		}
	}
	cout << f[m];
	return 0;
}

六、修剪草坪(P2627 [USACO11OPEN]Mowing the Lawn G)

洛谷上的题目
Acwing上的题目
刷题周记(十五)——#倍增:ST表、Balanced Lineup G、国旗计划;#单调队列优化:修剪草坪、宝物筛选、跳房子#背包:金明的预算方案#DP:传纸条_第2张图片
根据y总的一波分析,我们得出……公式就是一切……
所以,我要学会推公式……
推公式……
公式……

#include
using namespace std;
typedef long long ll; 

const int N = 1e5 + 10;
int n, m;
ll s[N]; 
ll f[N];
int q[N];
ll g(int i)
{
     
    return f[i - 1] - s[i];
}

int main()
{
     
    scanf("%d%d", &n, &m);
    //以前缀和的形式进行保存
    for(int i = 1; i <= n; i ++)
    {
     
        scanf("%lld", &s[i]);
        s[i] += s[i - 1];
    }
    //然后是单调队列优化着做
    //单调队列存储的是根据公式分析出来的那个关于变量j的式子,用子函数g()解决计算问题
    int hh = 0, tt = 0;
    for(int i = 1; i <= n; i ++)
    {
     
        //头超出范围要弹出
        if(q[hh] < i - m) hh ++;
        //这里就是一个关于选与不选的故事了,要从一个蝙蝠讲起……
        f[i] = max(f[i - 1], g(q[hh]) + s[i]);
        while(hh <= tt && g(q[tt]) <= g(i)) tt --;
        //可以看出,q存储的是用公式推出来的那一坨……
        q[++ tt] = i;
    }
    printf("%lld\n", f[n]);
    return 0;
}

——2021年02月02日(周二)——————————————————

八、跳房子

洛谷题目
ACwing题目

放弃单调队列优化了,又长又臭有难搞,还不如直接剪枝来得快。

#include 
#include 
#include 

using namespace std;
typedef long long LL;
const int N = 500010;
int n, d, k;
LL dist[N], w[N], f[N];

//检查花费g个金币进行改造后,最高得分是否会超过k
bool check(int gold)
{
     
    //机器人能够弹跳的范围[L,R]
    int L = max(1, d - gold);
    int R = d + gold;

    //注意:这里要初始化为负无穷
    memset(f, 0xc0, sizeof f);
    f[0] = 0;

    for(int i = 1; i <= n; i ++)
    {
     
        //从i的前一个格子开始,枚举到起点
        for(int j = i - 1; j >= 0; j --)
        {
     
            //剪枝:如果机器人从j号格子加上R还是无法到达i号格子,那么从j号格子之前的格子也无法到达i号格子
            if(dist[j] + R < dist[i]) break;

            //机器人从j号格子加上L还是超过了i号格子,那么继续尝试j号之前的格
            if(dist[j] + L > dist[i]) continue;

            //机器人从j号格子可以转移到i号格子
            f[i] = max(f[i], f[j] + w[i]);

            if(f[i] >= k) return true;
        }
    }

    return false;
}

int main()
{
     
    scanf("%d%d%d", &n, &d, &k);
    for(int i = 1; i <= n; i ++)
        scanf("%lld%lld", &dist[i], &w[i]);
    //R的大小可以通过x/n来确定,平均距离为2000
    int L = 0, R = 20000, ans = -1;
    while(L < R)
    {
     
        //所有满足条件的情况都在mid的右边区间,
        //搜索右边区间最小值
        //适用二分搜索模板1
        int mid = L + R >> 1;
		
        if(check(mid))
        {
     
            ans = mid;
            R = mid;
        }
        else L = mid + 1;
    }
    cout << ans << endl;

    return 0;
}

——2021年02月03日(周三)——————————————————

#四边形优化

石子合并
邮局

代码就不写了,我也不会写……
主要讲讲思路:
……推导过程我也不会,也看不懂……
总之就是一个结论:
Sj,i-1 <= Sj,i <= Sj+1-i, S代表这个区间最优断点的位置
什么意思呢?
i为已用的邮局数,j为已到达的村庄数
假如在j不变的情况下加邮局数i,那么邮局显然会往右走或不变,断点也会断在右边,因为邮局数多了的话,左边已经是最优解了,那么就挤到右边去找一个最优的区间放在它中间。
还有一点显然的,邮局在一个区间内放中间的优先级会高很多吧?
假如在i不变的情况下加村庄数j,显然邮局显然要往右走或不变,断点也要往右移
就是说,我们在枚举断点k的时候,可以以感性的方法推出:
后面的决策的最优断点必定在某两个已知状态的最优断点之间。
于是这样我们就可以缩小k的枚举范围了。

——2021年02月04日(周四)——————————————————

#分组背包

九、金明的预算方案

洛谷题目
ACwing题目

#include
using namespace std;
const int N = 32010;
int W[70][5], V[70][5], f[N], n, m;
int w, v, s;
int main()
{
     
    cin >> m >> n;
    for(int i = 1; i <= n; i ++)
    {
     
        scanf("%d%d%d", &v, &w, &s);
        w *= v;
        if(s == 0)
        {
     
            //喜提新组别
            V[i][0] = 1;
            V[i][1] = v, W[i][1] = w;
        }
        else
        {
     
            //这个只是拿来计算这一组有多少个物品
            V[s][0] ++;
            //不是第一个物品了,要么第二个
            if(V[s][0] == 2)
            {
     
                V[s][2] = v + V[s][1];
                W[s][2] = w + W[s][1];
            }
            //要么第三个
            else
            {
     
                V[s][3] = v + V[s][1];
                W[s][3] = w + W[s][1];
                //手动再加一个
                V[s][4] = v + V[s][2];
                W[s][4] = w + W[s][2];
                //不要忘了数量要对应上
                V[s][0] = 4;
            }
        }
    }
    //然后就是很简单的分组背包了、
    //枚举组别(为了不跨组别干扰)、体积(为了不重复选择组内的任意一个物品)、物品(打包好了的);
    for(int k = 1; k <= n; k ++)
        if(V[k][0])
            for(int j = m; j >= 0; j --)
                for(int i = 1; i <= V[k][0]; i ++)
                    if(j >= V[k][i])
                        f[j] = max(f[j], f[j - V[k][i]] + W[k][i]);
    cout << f[m];
    return 0;
}
/*
记不太清了……翻了下之前的做法,大概就是:
由于每组最多有3个物品,那么就预处理出:1,12,13,123这四种组合,因为1是必要的,
然后就是很简单的分组背包了……
a[n][0]储存当前组别的物品数,如果只有一件就一件,两件就两件,三件就四件。

由于a[70][5]并不大,所以可以放心地开
*/

#DP

十、传纸条

洛谷题目
ACwing题目

第一次做,之前只是有所耳闻……
大概地做了个四维的做法
注意的地方是:两个格子是同时走的,每次只转移一个显然不现实……

#include
using namespace std;
const int N = 60;
int q[N][N], f[N][N][N][N], n, m; 
int main()
{
     
    cin >> n >> m;
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++)
            scanf("%d", &q[i][j]);
            
    
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++)
            for(int k = 1; k <= n; k ++)
                for(int l = j + 1; l <= m; l ++)
                {
     
                    f[i][j][k][l] = max(max(f[i][j-1][k-1][l], f[i-1][j][k][l-1]), max(f[i][j-1][k][l-1], f[i-1][j][k-1][l])) + q[i][j] + q[k][l];
                }
    
    cout << f[n][m - 1][n - 1][m];
    return 0;
}

十一、乘积最大

洛谷题目
ACwing题目

这道题……某谷上面的数据加强得……要高精度……甚至只能A两个点……
这里先用区间DP的写法写一个。

//第一眼:枚举断点,区间DP
	#include 
#include 
#include 
using namespace std;
const int N = 45 , M = 10;

int n , k;
int w[N];
//用num来预处理这个区间里面的乘积
int num[N][N];
int f[N][M];

int main(){
     
   char ch;
   cin >> n >> k;

   int i = 0;
   while(cin >> ch) w[++ i] = ch - '0'; 

    for(int i = 1; i <= n; i ++)
       for(int j = i; j <= n; j ++) 
           num[i][j] = num[i][j - 1] * 10 + w[j]; 

    for(int i = 1; i <= n; i ++) f[i][0] = num[1][i]; 

    for(int i = 1; i <= n; i ++)
       for(int j = 1; j <= k; j ++)
          for(int h = 1; h < i; h ++)
			f[i][j] = max(f[i][j] , f[h][j - 1] * num[h + 1][i]);

        cout << f[n][k] << endl;   
    return 0;
}

——2021年02月05日(周五)——————————————————

——2021年02月06日(周六)——————————————————

——(完)——————————————————

To be continue……

你可能感兴趣的:(刷题记录)