首页 > 其他分享 >洛谷题单指南-分治与倍增-P7167 [eJOI2020 Day1] Fountain

洛谷题单指南-分治与倍增-P7167 [eJOI2020 Day1] Fountain

时间:2024-09-14 11:47:22浏览次数:10  
标签:eJOI2020 水量 int 洛谷题 top Day1 圆盘 -- stk

原题链接:https://www.luogu.com.cn/problem/P7167

题意解读:从喷泉任意一个圆盘倒水,水流经的圆盘直径必须递增,水最后流到哪个圆盘。

解题思路:

1、枚举法

有30%的数据范围在N<=1000,Q<=1000,因此枚举也可以得到30分。

可以通过单调栈预计算每个圆盘后面第一个直径更大的圆盘位置Next[N],这样直接通过模拟,

将水依次流经各个圆盘,计算最后停留在哪。

30分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 100005;
int n, q;
int d[N], c[N]; //d[i]:第i个圆盘直径 c[i]:第i个圆盘容量
int stk[N], top; //单调栈
int Next[N]; //Next[i]表示第i个圆盘之后第一个直径大于d[i]的位置

int main()
{
    cin >> n >> q;
    for(int i = 1; i <= n; i++) cin >> d[i] >> c[i];

    //利用单调栈计算每个圆盘后面第一个直径更大的圆盘位置
    for(int i = n; i >= 1; i--)
    {
        while(top && d[i] >= d[stk[top]]) top--;
        Next[i] = stk[top];
        stk[++top] = i;
    }

    int r, v;
    while(q--)
    {
        cin >> r >> v;
        while(r) //模拟法,水顺着往下流
        {
            v -= c[r];
            if(v <= 0) break;
            r = Next[r];
        }
        cout << r << endl;
    }
    return 0;
}

2、二分

又有30%的数据,圆盘直径是递增的,因此可以通过前缀和以及区间和公式计算任意两个圆盘之间能存下多少水,然后二分水最后流到的位置,

计算如果起点到终点的存水量超过导入的水量,则不断缩小答案范围,否则扩大答案范围,这样又可以得到30分。

60分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 100005;
int n, q;
int d[N], c[N]; //d[i]:第i个圆盘直径 c[i]:第i个圆盘容量
int stk[N], top; //单调栈
int Next[N]; //Next[i]表示第i个圆盘之后第一个直径大于d[i]的位置
int s[N]; //c[N]的前缀和

int main()
{
    cin >> n >> q;
    for(int i = 1; i <= n; i++) cin >> d[i] >> c[i];
    for(int i = 1; i <= n; i++) s[i] = s[i - 1] + c[i];

    //利用单调栈计算每个圆盘后面第一个直径更大的圆盘位置
    for(int i = n; i >= 1; i--)
    {
        while(top && d[i] >= d[stk[top]]) top--;
        Next[i] = stk[top];
        stk[++top] = i;
    }

    int r, v;
    while(q--)
    {
        cin >> r >> v;
        if(n <= 1000)
        {
            while(r) //模拟法,水顺着往下流
            {
                v -= c[r];
                if(v <= 0) break;
                r = Next[r];
            }
            cout << r << endl;
        }
        else
        {
            int left = r, right = n + 1;
            while(left < right)
            {
                int mid = (left + right) / 2;
                if(s[mid] - s[r - 1] >= v) right = mid;
                else left = mid + 1;
            }
            if(left == n + 1) cout << 0 << endl;
            else cout << left << endl;
        }
    }
    return 0;
}

3、倍增法

当圆盘直径不是递增时,就不能用前缀和以及区间和来计算两个圆盘之间的存水量,二分也就失效了。

关键还是要计算两个值:

a、水从起点经过一定数量圆盘能到哪个点

b、从起点经过圆盘能存多少水

有了这两个信息,就可以计算答案。

但是本题数据量较大,计算所有区间必然超时,可以借助倍增和ST表的思想,只计算2^k次方范围的数据。

状态表示:

设f[i][j]表示从i点开始,跳过2^j个圆盘后能到的点,注意下一个点直径必须是递增的,所以初始化f[i][0]可以借助单调栈

设g[i][j]表示从i点之后开始,一共2^j个圆盘能存的水量,g[i][0]就是从i之后一个圆盘的存水量

状态转移:

f[i][j] = f[f[i][j-1]][j-1],从i跳过2^j个圆盘能到的点等于从i跳过2^(j-1)个圆盘能到的点再跳过2^(j-1)个圆盘能到的点

g[i][j] = g[i][j-1] + g[f[i][j-1]][j-1],从i之后开始共2^j个圆盘能存的水量,等于从i之后开始2^(j-1)个圆盘存的水量,加上从f[i][j-1]之后开始2^(j-1)个圆盘能存的水量

结果计算:

如果v小于等于起点圆盘的存水量,v<=c[r],则答案就是r

否则,可以从最大区间开始尝试,如果v>该区间的存水量,则跳过整个区间,存水量更新,起点更新,直到无法再往后跳

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 100005;
int n, q;
int d[N], c[N]; //d[i]:第i个圆盘直径 c[i]:第i个圆盘容量
int stk[N], top; //单调栈
//设f[i][j]表示从i点开始,跳过2^j个圆盘后能到的点,注意下一个点直径必须是递增的
//设g[i][j]表示从i点开始,跳过的2^j个圆盘能存的水量
int f[N][20], g[N][20];

int main()
{
    cin >> n >> q;
    for(int i = 1; i <= n; i++) 
    {
        cin >> d[i] >> c[i];
    }

    //利用单调栈初始化f[i][0],g[i][0]
    for(int i = n; i >= 1; i--)
    {
        while(top && d[i] >= d[stk[top]]) top--;
        f[i][0] = stk[top]; //初始化,注意f[n][0] = 0
        g[i][0] = c[stk[top]];
        stk[++top] = i;
    }

    for(int j = 1; (1 << j) <= n; j++)
    {
        for(int i = 1; i + (1 << j) <= n; i++)
        {
            f[i][j] = f[f[i][j-1]][j-1];
            g[i][j] = g[i][j-1] + g[f[i][j-1]][j-1];
        }
    }

    int r, v;
    while(q--)
    {
        cin >> r >> v;
        if(c[r] >= v) cout << r << endl; //如果第r号圆盘能存下v水量
        else 
        {
            v -= c[r]; //先减掉起点圆盘的存水量
            for(int j = 17; j >= 0; j--) //从大到小倍增,每次v减去能跳到最远的位置能存的水量,r更新为跳过之后的位置
            {             
                if(f[r][j] && v > g[r][j]) //f[r][j]==0时说明已跳到底
                {
                    v -= g[r][j];
                    r = f[r][j];
                }
            }
            cout << f[r][0] << endl; //v刚好>g[r][j],说明水可以留到2^j之后的下一个圆盘
        }
    }
    return 0;
}

 

标签:eJOI2020,水量,int,洛谷题,top,Day1,圆盘,--,stk
From: https://www.cnblogs.com/jcwy/p/18413052

相关文章

  • leetcode刷题day17|二叉树Part05(654.最大二叉树、617.合并二叉树、700.二叉搜索树中的
    654.最大二叉树构造树一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。递归三步曲:1、传入参数,整数数组,数组的开始和结束,返回树的中间节点。2、终止条件:开始大于等于结束,即数组为空。3、递归逻辑:找到最大的元素,记录元素其下标,构建中间节点;递归构造......
  • DAY14 二叉树part04
    找树左下角的值513.找树左下角的值迭代法层序遍历classSolution{publicList<List<Integer>>reList=newArrayList<>();publicintfindBottomLeftValue(TreeNoderoot){check(root);intindex=reList.size()-1;......
  • day11-多线程
    一、线程安全问题线程安全问题出现的原因?存在多个线程在同时执行同时访问一个共享资源存在修改该共享资源线程安全:多个线程同时修改同一个资源取钱案例小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元如果小明和小红同时来取钱,并且2人各自......
  • 【读书笔记-《30天自制操作系统》-18】Day19
    本篇内容涉及到文件与文件系统,以及应用程序的运行。首先实现type命令,读取文件并显示;接下来导入对FAT文件系统的支持,实现读取大小512字节以上,存放在不连续扇区中的文件。在此基础上,最终实现读取并运行应用程序。1.type命令实现type命令是Windows命令行中用于读取并显示文......
  • 【每日刷题】Day119
    【每日刷题】Day119......
  • 洛谷题单指南-分治与倍增-P1226 【模板】快速幂
    原题链接:https://www.luogu.com.cn/problem/P1226题意解读:快速幂模版题。解题思路:1、分治法要计算a^b,可以对b分情况讨论:如果b是偶数,即b=2t,a^b=a^t*a^t如果b是奇数,即b=2t+1,a^b=a*a^t*a^t很容易用递归实现100分代码:#include<bits/stdc++.h>usingnamespa......
  • C++复习day11
    类型转化C语言中的类型转换在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。隐式类型转化:编译器在编译阶段自动进行,能转就转,......
  • 洛谷题单指南-分治与倍增-P1966 [NOIP2013 提高组] 火柴排队
    原题链接:https://www.luogu.com.cn/problem/P1966题意解读:计算两个序列∑(ai​−bi​)^2的最小值,对10^8-3取模。解题思路:1、贪心思路要使得两个序列对应位置元素之差的平方和最小,必须满足两个序列相对排序是一致的,什么意思?设a序列有两个元素:a1,a2,b序列有两个元素b1,b2当a1<a2,b......
  • leetcode刷题day13|二叉树Part01(递归遍历、迭代遍历、统一迭代、层序遍历)
    递归遍历思路:使用递归的方式比较简单。1、递归函数的传参:因为最后输出一个数组,所以需要传入根节点和一个容器,本来想写数组,但发现长度不能确定,所以选择list。2、终止条件:当访问的节点为空时,return3、递归函数的逻辑:先访问一个节点,递归访问其他节点144.二叉树的前序遍历......
  • leetcode刷题day14|二叉树Part02(以递归方法为主:226.翻转二叉树、101. 对称二叉树、104
    226.翻转二叉树思路:利用先序遍历递归翻转1、终止条件:节点为空,return2、参数:root3、递归函数逻辑:处理中间节点,交换;递归左孩子,递归右孩子代码如下:classSolution{publicTreeNodeinvertTree(TreeNoderoot){if(root==null)returnroot;swapC......