【洛谷3527】[POI2011] MET-Meteors(树状数组+整体二分)

本文介绍了一种利用整体二分结合树状数组解决复杂问题的算法技巧,通过实例讲解了如何将陨石雨事件和国家需求转换为二分搜索问题,有效地减少了时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

点此看题面

大致题意: 一颗星球被分为\(M\)份,分别属于\(N\)个国家,有\(K\)场陨石雨,第\(i\)个国家希望收集\(P_i\)颗陨石,问其至少要在第几次陨石雨后才能达到目标。

关于整体二分

什么是整体二分

其实我也不太清楚,反正就是一个很神仙的东西。

而这题的做法听说就是传说中的整体二分。

关于树状数组

这题我一开始写的是线段树,结果代码又长又\(TLE\)

改成树状数组后就过了。

对于我之前说过的绝对不写树状数组,我只能说:真香。

大致思路

首先,我们将\(K\)场陨石雨\(N\)个询问全部用一个数组存储下来。

然后我们对时间进行二分,每次将\(l\sim mid\)场陨石雨能在第\(mid\)个操作前达成的询问放入左半区间,将其余的陨石雨和询问放入右半区间,然后继续操作即可。

具体实现如下:

  • 先枚举陨石雨,将编号\(\le mid\)的陨石雨全部用树状数组进行区间修改。
  • 然后枚举询问,枚举当前国家的每一份(可以使用邻接表)统计陨石个数和,然后与\(P_i\)比较即可。

有一些小细节,还是见代码吧。

代码

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define Gmax(x,y) (x<(y)&&(x=(y)))
#define Gmin(x,y) (x>(y)&&(x=(y))) 
#define abs(x) ((x)<0?-(x):(x))
#define swap(x,y) (x^=y^=x^=y)
#define uint unsigned int
#define LL long long
#define ull unsigned long long
#define INF 1000000000
#define N 300000
#define M 300000
#define K 300000
using namespace std;
int n,m,k,t[N+5],lnk[N+5],nxt[M+5];
struct key
{
    int op,pos,val,l,r;
    key(int o=0,int p=0,int v=0,int x=0,int y=0):op(o),pos(p),val(v),l(x),r(y){}
}s[N+K+5];
class Class_FIO
{
    private:
        #define Fsize 100000
        #define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,Fsize,stdin),A==B)?EOF:*A++)
        #define pc(ch) (void)(FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch))
        int f,FoutSize,Top;char ch,Fin[Fsize],*A,*B,Fout[Fsize],Stack[Fsize];
    public:
        Class_FIO() {A=B=Fin;}
        inline void read(int &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));x*=f;}
        inline void write(int x) {if(!x) return pc('0');x<0&&(pc('-'),x=-x);while(x) Stack[++Top]=x%10+48,x/=10;while(Top) pc(Stack[Top--]);}
        inline void writec(char x) {pc(x);}
        inline void write_NoAnswer() {pc('N'),pc('I'),pc('E'),pc('\n');}
        inline void clear() {fwrite(Fout,1,FoutSize,stdout),FoutSize=0;}
}F;
class Class_DivideSolver
{
    private:
        int ans[N+5];key ns1[N+K+5],ns2[N+K+5];
        class Class_TreeArray//树状数组
        {
            private:
                #define lowbit(x) ((x)&-(x))
                LL num[M+5];
            public:
                inline void Update(int x,LL val) {while(x<=m) num[x]+=val,x+=lowbit(x);}
                inline LL Query(int x,LL res=0) {while(x) res+=num[x],x-=lowbit(x);return res;}
        }T;
    public:
        inline void Solve(int l=1,int r=n+k,int L=1,int R=k+1)//整体二分
        {
            register int i,j,mid=L+R>>1,cnt1=0,cnt2=0;register LL tot;
            if(!(L^R)) {for(i=l;i<=r;++i) !s[i].op&&(ans[s[i].pos]=L);return;}//判边界
            for(i=l;i<=r;++i)//枚举操作
            {
                if(s[i].op)//对于陨石雨
                {
                    if(s[i].pos<=mid) (s[i].l<=s[i].r?(T.Update(s[i].l,s[i].val),T.Update(s[i].r+1,-s[i].val)):(T.Update(s[i].l,s[i].val),T.Update(1,s[i].val),T.Update(s[i].r+1,-s[i].val))),ns1[++cnt1]=s[i];//树状数组区间修改,并将其扔入左半部分
                    else ns2[++cnt2]=s[i];continue;//否则将其扔入右半部分
                }
                for(tot=0,j=lnk[s[i].pos];j;j=nxt[j]) if((tot+=T.Query(j))>=s[i].val) break;//统计陨石个数和,注意达成条件后直接break
                tot>=s[i].val?ns1[++cnt1]=s[i]:(s[i].val-=tot,ns2[++cnt2]=s[i]);//比较tot与s[i].val,来判断将其扔入左半区间还是右半区间
            }
            for(i=1;i<=cnt1;++i) s[l+i-1]=ns1[i];for(i=1;i<=cnt2;++i) s[l+cnt1+i-1]=ns2[i];//更新序列
            for(i=l;i<=r;++i) s[i].op&&s[i].pos<=mid&&(s[i].l<=s[i].r?(T.Update(s[i].l,-s[i].val),T.Update(s[i].r+1,s[i].val)):(T.Update(s[i].l,-s[i].val),T.Update(1,-s[i].val),T.Update(s[i].r+1,s[i].val)),0);//清空树状数组
            cnt1&&(Solve(l,l+cnt1-1,L,mid),0),cnt2&&(Solve(l+cnt1,r,mid+1,R),0);//继续处理子区间
        }
        inline void Print() {for(register int i=1;i<=n;++i) ans[i]<=k?F.write(ans[i]),F.writec('\n'):F.write_NoAnswer();}//输出答案
}D;
int main()
{
    register int i,x,y,z;
    for(F.read(n),F.read(m),i=1;i<=m;++i) F.read(x),nxt[i]=lnk[x],lnk[x]=i;
    for(i=1;i<=n;++i) F.read(t[i]);
    for(F.read(k),i=1;i<=k;++i) F.read(x),F.read(y),F.read(z),s[i]=key(1,i,z,x,y);//存储陨石雨
    for(i=1;i<=n;++i) s[i+k]=key(0,i,t[i]);//存储询问
    return D.Solve(),D.Print(),F.clear(),0;
}

转载于:https://www.cnblogs.com/chenxiaoran666/p/Luogu3527.html

### 如何使用 poi-tl 输出数组数据 在 `poi-tl` 中,可以通过 Spring Expression Language (SpEL) 来处理复杂的数据结构,比如数组或列表。以下是具体的实现方式以及代码示例。 #### 使用 SpEL 处理数组数据 当需要输出数组中的元素时,可以利用 SpEL 的语法来遍历并渲染数组中的每一项。例如,在模板中定义占位符 `${dogs}` 或者通过循环标签 `<#list>` 配合表达式完成操作[^2]。 以下是一个完整的例子: ```java import cn.afterturn.easypoi.word.WordExportUtil; import org.apache.poi.xwpf.usermodel.XWPFDocument; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class PoiTlArrayExample { public static void main(String[] args) throws Exception { // 创建一个包含数组数据的地图对象 Map<String, Object> dataMap = new HashMap<>(); List<Map<String, String>> dogsList = new ArrayList<>(); for (int i = 0; i < 3; i++) { Map<String, String> dog = new HashMap<>(); dog.put("name", "Dog-" + i); dog.put("age", String.valueOf(i * 2)); dogsList.add(dog); } dataMap.put("dogs", dogsList); // 加载模板文件并填充数据 XWPFDocument document = WordExportUtil.exportWord("template.docx", dataMap); // 将生成的文档保存到指定路径 try (FileOutputStream fos = new FileOutputStream("output.docx")) { document.write(fos); } System.out.println("文档已成功生成!"); } } ``` 在这个示例中: - 数据模型被封装成一个 `List<Map<String, String>>` 结构,其中每个子地图表示一条记录。 - 模板文件 (`template.docx`) 应该预先设计好,并且包含类似于 `${dogs.name}` 这样的占位符用于动态替换[^1]。 #### 关键点解析 1. **数据准备** 数组或者列表形式的数据通常会被映射为 Java 的集合类型(如 `ArrayList`)。每条记录可以用简单的 POJO 对象或者是哈希表存储属性值对。 2. **模板配置** 在 `.docx` 文件中设置对应的占位符名称以便匹配传入的数据源字段名。如果要显示整个数组的内容,则可以在模板里嵌套表格或其他布局控件辅助呈现效果[^2]。 3. **渲染逻辑** 调用 `XWPFTemplate.compile()` 方法加载预设好的 DOCX 文档作为基础框架;随后调用其 `render(Map)` 函数注入实际参数集即可自动完成内容组装过程[^1]。 --- #### 注意事项 - 如果目标是逐行打印出数组里的每一个成员而非一次性全部展示出来的话,那么可能还需要额外引入 FreeMarker 等更高级别的模版引擎支持更为灵活复杂的场景需求。 - 当前版本仅适用于简单线性排列情况下的批量替换单元格文本动作,对于更加精细定制化的样式调整或许还得依赖其他插件扩展功能才行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值