hdu多校第三场 1007 (hdu6609) Find the answer 线段树

题意:

给定一组数,共n个,第i次把第i个数扔进来,要求你删掉前i-1个数中的一些(不许删掉刚加进来这个数),使得前i个数相加的和小于m。问你对于每个i,最少需要删掉几个数字。

题解:

肯定是优先删大数,一开始想的方法类似于尺取,就是维护一个大顶堆作为现有的数,小顶堆作为要删的数,每次大顶堆的元素总和大于m了,就把堆顶扔进小顶堆,扔完了,再把小顶堆的堆顶扔回大顶堆,在会导致大顶堆值总和大于m时停止。

但是很容易会被1 1 1 1 1 1 1 1 1 1 100 这样的数据卡掉。

然后想到了,维护一个线段树,假想线段树根节点从左到右保存原数列中从大到小的数,但是一开始置为虚节点,值为0,而每当计算到数列的某一位时,再把这一位在线段树上该在的位置赋上实值,这个过程需要离线排序,nlogn。

二分,每次枚举节点k,在线段树上求区间和,使得1到k上,所有非虚节点和大于等于sum-m,找到最小k,然后输出1-k的非虚节点的数量,二分复杂度logn,线段树上求区间和复杂度logn。时间复杂度$O(nlog^2n)$

这个想法已经很接近正确答案了,还是T了,实际上,线段树本身就是一个二分的结构,又为什么要在线段树上二分枚举节点呢?归根结底还是我对于常用数据结构的不熟悉,只会套用板子,正解是直接在线段树上二分求前缀和,总时间复杂度$O(nlogn)$

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cassert>
#define MAXN 200005
#define LL long long
using namespace std;
struct Node{
    int l,r;
    LL val;
    int exis;
}node[MAXN*4];
void build(int l,int r,int x){
    node[x].l=l;
    node[x].r=r;
    if(l==r){
        node[x].exis=0;
        node[x].val=0;
        return ;
    }else{
        int mid=(l+r)/2;
        build(l,mid,x*2);
        build(mid+1,r,x*2+1);
    }
    node[x].val=node[2*x].val+node[2*x+1].val;
    node[x].exis=node[2*x].exis+node[2*x+1].exis;
    return ;
}
LL query(int l,int r,int x){
    if(l<=node[x].l && node[x].r<=r)return node[x].exis;
    if(node[x].r<l || r<node[x].l)return 0;
    LL ans=0;
    if(l<=node[x*2].r)ans+=query(l,r,2*x);
    if(node[x*2+1].l<=r)ans+=query(l,r,2*x+1);
    return ans;
}
void add(int id,int num,int x){
    if(node[x].l==node[x].r){
        node[x].val=num;
        node[x].exis=1;
        return ;
    }
    if(id<=node[x*2].r){
        add(id,num,x*2);
    }else{
        add(id,num,x*2+1);
    }
    node[x].val=node[x*2].val+node[x*2+1].val;
    node[x].exis=node[x*2].exis+node[x*2+1].exis;
    return ;
}
int bsearch(LL last,int x){
    if(node[x].l==node[x].r)return node[x].exis;
    if(node[x].val==last)return node[x].exis;
    if(node[x].val>last){
        if(last<=node[x*2].val)return bsearch(last,x*2);
        else return node[x*2].exis+bsearch(last-node[x*2].val,x*2+1);
    }
}
struct Tag{
    int index;
    int val;
    int rank;
}tag[MAXN];
inline bool cmpval(const Tag &a,const Tag &b){
    return a.val>b.val;
}
inline bool cmpindex(const Tag &a,const Tag &b){
    return a.index<b.index;
}
int main(){
    int q;
    scanf("%d",&q);
    while(q--){
        int n;
        LL m;
        scanf("%d %lld",&n,&m);
//        memset(node,0,sizeof node);
        build(1,n,1);
        for(int i=1;i<=n;i++){
            scanf("%d",&tag[i].val);
            tag[i].index=i;
        }
        sort(tag+1,tag+1+n,cmpval);
        for(int i=1;i<=n;i++){
            tag[i].rank=i;
        }
        sort(tag+1,tag+1+n,cmpindex);
        LL sum=0;
        for(int i=1;i<=n;i++){
//            printf("%d %d %d\n",tag[i].index,tag[i].val,tag[i].rank);
            sum+=tag[i].val;
            if(sum<=m)printf("0 ");
            else printf("%d ",bsearch(sum-m,1));
            add(tag[i].rank,tag[i].val,1);
//            printf("\n");
//            for(int j=1;j<=4*n;j++){
//                printf("%d %d %lld %d\n",node[j].l,node[j].r,node[j].val,node[j].exis);
//            }
//            printf("\n");
        }
        printf("\n");
//        for(int j=1;j<=4*n;j++){
//            printf("%d %d %lld %d\n",node[j].l,node[j].r,node[j].val,node[j].exis);
//        }
//        printf("\n");
    }
    return 0;
} 

后记:

在群里看到了一个神仙做法。

这道题难点在于刚加进去的点,不能立即删,如果可以立即删,那么,后一个数字加进来,删哪些,一定可以通过前一个数字加进来后删哪些快速求出,贪心删去最大的就行了。

神仙的思路是,假想这个游戏允许删掉刚刚加进来的那个数字,那么,记tot[i]为第i个数字加进来后,应该删去哪些,tot[i]肯定是tot[i+1]的子集。

而实际上,第i个数字加进来后,只能删前i-1个数字,那么,临时删去一些数字,用num[i]记录临时删掉的数字,那么,此时的答案就是tot[i-1]+num[i].

贪心删去最大的就行,因此可以用multiset维护。

神仙的链接:https://blog.csdn.net/qq_41603898/article/details/97674737

原文地址:https://www.cnblogs.com/isakovsky/p/11273411.html

时间: 2024-10-09 15:12:12

hdu多校第三场 1007 (hdu6609) Find the answer 线段树的相关文章

2018 HDU多校第三场赛后补题

2018 HDU多校第三场赛后补题 从易到难来写吧,其中题意有些直接摘了Claris的,数据范围是就不标了. 如果需要可以去hdu题库里找.题号是6319 - 6331. L. Visual Cube 题意: 在画布上画一个三维立方体. 题解: 模拟即可. 代码: #include <bits/stdc++.h> using namespace std; int a, b, c, R, C; char g[505][505]; int main () { int T; cin >>

hdu多校第三场 1006 (hdu6608) Fansblog Miller-Rabin素性检测

题意: 给你一个1e9-1e14的质数P,让你找出这个质数的前一个质数Q,然后计算Q!mod P 题解: 1e9的数据范围pass掉一切素数筛法,考虑Miller-Rabin算法. 米勒拉宾算法是一种判断素数的随机化算法,由于其随机性,它不能保证总是正确的,但其对于一个素数,总会返回素数的结果,对于一个合数,才有极小概率返回素数的结果(假阳性). 米勒拉宾算法对于单个素数的判断时间复杂度为$O(log^3n)$.(因为1e14相乘会爆longlong,模乘要写成龟速乘,因此要多一个log) 18

hdu多校第四场 1007 (hdu6620) Just an Old Puzzle 逆序对

题意: 给你一个数字拼图,问你数字拼图能否能复原成原来的样子. 题解: 数字拼图的性质是,逆序数奇偶相同时,可以互相转化,逆序数奇偶不同,不能互相转化. 因此统计逆序对即可. #include<iostream> using namespace std; int main(){ int t; scanf("%d",&t); while(t--){ int a[20]; int kg; for(int i=1;i<=16;i++){ scanf("%d

hdu多校第五场1007 (hdu6630) permutation 2 dp

题意: 给你n个数,求如下限制条件下的排列数:1,第一位必须是x,2,最后一位必须是y,3,相邻两位之差小于等于2 题解: 如果x<y,那么考虑把整个数列翻转过来,减少讨论分支. 设dp[n]为限制1和n在两边,相邻的数之差小于等于2的排列方案. dp[0]=1 dp[1]=1 dp[2]=2 dp[3]=3 如果x==1 y==n 直接用公式dp[i]=dp[i-1]+dp[i-3]求解,将i代入为n-1-1. 如果x!=1 或者y!=n,假如对于12个的情况,x=4,y=9 那么,1,2,3

2019杭电多校第三次hdu6609 Find the answer(线段树)

Find the answer 题目传送门 解题思路 要想变0的个数最少,显然是优先把大的变成0.所以离散化,建立一颗权值线段树,维护区间和与区间元素数量,假设至少减去k才能满足条件,查询大于等于k的最少数量即可. 代码如下 #include <bits/stdc++.h> #define INF 0x3f3f3f3f using namespace std; typedef long long ll; inline int read(){ int res = 0, w = 0; char c

Wow! Such Sequence! HDU多校联合赛第三场1007

Wow! Such Sequence! Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Problem Description Recently, Doge got a funny birthday present from his new friend, Protein Tiger from St. Beeze College. No, not cactuses. It's

2014多校第三场1005 || HDU 4891 The Great Pan(模拟)

题目链接 题意 : 给你n行字符串,问你有多少种理解方式.有两大类的理解 (1){A|B|C|D|...}代表着理解方式可以是A,可以是B或C或者D. (2)$blah blah$,在$$这两个符号中间,如果是不连续的空格的那个位置就有2种理解方式,可以理解为没有空格也可以理解为有空格.如果有连续N个空格的位置,那里就有N+1种理解方式. 最后所有的理解方式相乘,数据保证$一定与$匹配,{一定与匹配},不会有任何嵌套,类似{$$}或者{{}}或者${}$这种情况都不会出现,也不会有{$}这种情况

2014 HDU多校弟五场J题 【矩阵乘积】

题意很简单,就是两个大矩阵相乘,然后求乘积. 用 Strassen算法 的话,当N的规模达到100左右就会StackOverFlow了 况且输入的数据范围可达到800,如果变量还不用全局变量的话连内存开辟都开不出来 1 #pragma comment(linker, "/STACK:16777216") 2 #include <iostream> 3 #include <stdio.h> 4 #define ll long long 5 using namesp

2014 HDU多校弟八场H题 【找规律把】

看了解题报告,发现看不懂 QAQ 比较简单的解释是这样的: 可以先暴力下达标,然后会发现当前数 和 上一个数 的差值是一个 固定值, 而且等于当前数与i(第i个数)的商, 于是没有规律的部分暴力解决,有规律的套公式 //#pragma comment(linker, "/STACK:16777216") //for c++ Compiler #include <stdio.h> #include <iostream> #include <cstring&g