2018.10.24 NOIP2018模拟赛 解题报告

版权声明:此文为作者原创,若觉得写得好请点个赞再离开。当然,也欢迎在讨论区指出本文的不足,作者会及时加以改正。转载请注明地址: https://blog.csdn.net/chenxiaoran666/article/details/83373778

得分: 100 + 0 + 100 = 200 100+0+100=200 T 2 T2 悲惨爆 0 0

P . S . P.S. 由于原题是图片,所以我没有上传题目描述,只有数据。


T 1 T1 :query(点此看题面

熟悉主席树的人都知道,这是一道主席树查询区间排名的模板题。

L i n k Link

主席树 详见博客 可持久化专题(一)——浅谈主席树:可持久化线段树

但是,由于太久没打主席树,我对它有一些生疏了,结果依然用了一个多小时… …

主席树查询区间排名的大致思路,就是将元素给离散化,然后用 n n 棵线段树(合并成一棵主席树)分别存储 1 i 1\sim i 中每个元素出现次数,然后就可以很方便地利用前缀和思想查找了。

代码如下:

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define uint unsigned int
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define abs(x) ((x)<0?-(x):(x))
#define INF 1e9
#define Inc(x,y) ((x+=(y))>=MOD&&(x-=MOD))
#define ten(x) (((x)<<3)+((x)<<1))
#define N 100000
#define LogN 40
using namespace std;
int n,cnt,a[N+5],p[(N<<1)+5];
struct Query
{
    int l,r,val;
    Query(int x=0,int y=0,int z=0):l(x),r(y),val(z){}
}q[N+5];
class FIO
{
    private:
        #define Fsize 100000
        #define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
        #define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch))
        int f,FoutSize,OutputTop;char ch,Fin[Fsize],*FinNow,*FinEnd,Fout[Fsize],OutputStack[Fsize];
    public:
        FIO() {FinNow=FinEnd=Fin;}
        inline void read(int &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=ten(x)+(ch&15),isdigit(ch=tc()));x*=f;}
        inline void read_char(char &x) {while(isspace(x=tc()));}
        inline void read_string(string &x) {x="";while(isspace(ch=tc()));while(x+=ch,!isspace(ch=tc())) if(!~ch) return;}
        inline void write(int x) {if(!x) return (void)pc('0');if(x<0) pc('-'),x=-x;while(x) OutputStack[++OutputTop]=x%10+48,x/=10;while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;}
        inline void write_char(char x) {pc(x);}
        inline void write_string(string x) {register int i,len=x.length();for(i=0;i<len;++i) pc(x[i]);}
        inline void end() {fwrite(Fout,1,FoutSize,stdout);}
}F;
class Class_ChairmanTree//主席树
{
    private:
        #define PushUp(rt) (node[rt].Size=node[node[rt].Son[0]].Size+node[node[rt].Son[1]].Size+1)//上传操作
        int n,tot,v,data[N+5],Root[N+5];
        struct Tree
        {
            int Son[2],Size,Cnt;
            inline void Clear() {Son[0]=Son[1]=Size=Cnt=0;}
        }node[N*LogN+5];
        inline void Build(int l,int r,int &rt)//建一棵初始的空树
        {
            rt=++tot;//新建一个节点,用了动态开点
            register int mid=l+r>>1;
            if(l^r) Build(l,mid,node[rt].Son[0]),Build(mid+1,r,node[rt].Son[1]);//建树
        }
        inline void Upt(int l,int r,int lst,int &rt,int pos)//将pos的出现次数加1
        {
            node[rt=++tot]=node[lst],++node[rt].Size;//新建一个节点,将子树大小加1
            if(!(l^r)) return (void)(++node[rt].Cnt);//如果是叶子节点,将出现次数加1
            register int mid=l+r>>1;
            pos<=mid?Upt(l,mid,node[lst].Son[0],node[rt].Son[0],pos):Upt(mid+1,r,node[lst].Son[1],node[rt].Son[1],pos);//继续操作子树
        }
        inline int get_rank(int l,int r,int rt,int val)//求出val在[l,r]区间内的排名
        {
            if(l>r) return 0;//如果l>r,返回0
            register int mid=l+r>>1;
            if(r<=val) return node[rt].Size;//如果右边界小于等于val,返回这棵子树的大小
            if(mid<val) return node[node[rt].Son[0]].Size+get_rank(mid+1,r,node[rt].Son[1],val);//如果左边界小于val,返回左子树的大小加上在右子树中询问的结果
            else return get_rank(l,mid,node[rt].Son[0],val);//否则,返回在左子树中询问的结果
        }
    public:
        inline void Clear() {for(register int i=0;i<=tot;++i) node[i].Cnt;tot=v=0;}//清空
        inline void Init(int len) {Build(1,n=len,Root[0]);}//建树
        inline void Add(int pos) {++v,Upt(1,n,Root[v-1],Root[v],pos);}//将某个元素出现次数加1
        inline int GetRank(int l,int r,int val) {return get_rank(1,n,Root[r],val)-get_rank(1,n,Root[l-1],val);}//求区间排名
}ChairmanTree;
inline int get_pos(int val)//求元素离散化后的值
{
    register int l=1,r=cnt,mid=l+r>>1;
    for(;l<=r;mid=l+r>>1) p[mid]<val?l=mid+1:r=mid-1;
    return l;
}
int main()
{
    register int i,T,Q,x,y,z;F.read(T);
    while(T--)
    {
    	for(ChairmanTree.Clear(),cnt=0,F.read(n),F.read(Q),i=1;i<=n;++i) F.read(a[i]),p[++cnt]=a[i];
    	for(i=1;i<=Q;++i) F.read(q[i].l),F.read(q[i].r),F.read(q[i].val),p[++cnt]=q[i].val;
    	for(sort(p+1,p+cnt+1),cnt=unique(p+1,p+cnt+1)-p-1,ChairmanTree.Init(cnt),i=1;i<=n;++i) ChairmanTree.Add(get_pos(a[i]));//建n棵线段树(合并成一棵主席树)
        for(i=1;i<=Q;++i) F.write(ChairmanTree.GetRank(q[i].l,q[i].r,get_pos(q[i].val))),F.write_char('\n');//输出答案
    }
    return F.end(),0;
}

T 2 T2 :assassin(点此看题面

这题其实还是挺简单的,但比赛最后 10 10 分钟手贱把打了一半的代码给改炸了,最后爆 0 0

首先,我们将题目中给出的敌人分成两类: B i = 0 B_i=0 (共 c n t 1 cnt1 个)和 B &gt; = 0 B_&gt;=0 (共 c n t 2 cnt2 个)。

那么最终答案也无非两种情况:

  • 全部选 B i = 0 B_i=0 的。
  • 首先选择 B i &gt; 0 B_i&gt;0 A i A_i 最小的敌人杀死。不难想到,既然杀死了一个 B i &gt; 0 B_i&gt;0 的敌人,则最优方案肯定是将所有 B i &gt; 0 B_i&gt;0 的敌人全部杀死。如果用 s u m sum 表示 i = 1 n B i \sum_{i=1}^n B_i ,则在杀死所有 B i &gt; 0 B_i&gt;0 的敌人后,我们还可以额外杀死 s u m c n t 2 + 1 sum-cnt2+1 B i = 0 B_i=0 的敌人,则根据贪心的思想,肯定是尽量选择 A i A_i 大的敌人杀死。然后对于剩下的敌人,再求出最多能杀死多少个即可。

代码如下:

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define uint unsigned int
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define abs(x) ((x)<0?-(x):(x))
#define INF 1e9
#define Inc(x,y) ((x+=(y))>=MOD&&(x-=MOD))
#define ten(x) (((x)<<3)+((x)<<1))
#define N 100000
using namespace std;
int n,m,ans1,ans2,cnt1,cnt2,s1[N+5],s2[N+5];
class FIO
{
    private:
        #define Fsize 100000
        #define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
        #define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch))
        int f,FoutSize,OutputTop;char ch,Fin[Fsize],*FinNow,*FinEnd,Fout[Fsize],OutputStack[Fsize];
    public:
        FIO() {FinNow=FinEnd=Fin;}
        inline void read(int &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=ten(x)+(ch&15),isdigit(ch=tc()));x*=f;}
        inline void read_char(char &x) {while(isspace(x=tc()));}
        inline void read_string(string &x) {x="";while(isspace(ch=tc()));while(x+=ch,!isspace(ch=tc())) if(!~ch) return;}
        inline void write(int x) {if(!x) return (void)pc('0');if(x<0) pc('-'),x=-x;while(x) OutputStack[++OutputTop]=x%10+48,x/=10;while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;}
        inline void write_char(char x) {pc(x);}
        inline void write_string(string x) {register int i,len=x.length();for(i=0;i<len;++i) pc(x[i]);}
        inline void end() {fwrite(Fout,1,FoutSize,stdout);}
}F;
inline void Solve(int x,int s,int t)//对最多有x个敌人可杀,剩余的耐久度为s,且已经杀死敌人数为t时的情况进行处理
{
    register int i,res=t; 
    for(i=1;i<=x&&s+s1[i]<=m;++i) s+=s1[i];//尽量多杀
    if((res+=min(i-1,x))>ans1) ans1=res,ans2=s;//更新答案
    else if(!(ans1^res)&&s<ans2) ans2=s;
}
int main()
{
    int i,j,T,x,y,sum;F.read(T);
    while(T--)
    {
    	for(F.read(n),F.read(m),sum=ans1=ans2=cnt1=cnt2=0,i=1;i<=n;++i) F.read(x),F.read(y),sum+=y,y?s2[++cnt2]=x:s1[++cnt1]=x;//将敌人分两种类别存储
    	sort(s1+1,s1+cnt1+1),sort(s2+1,s2+cnt2+1),Solve(cnt1,0,0);//排序,处理全选B[i]=0的敌人的情况
    	if(cnt2)//如果有B[i]>0的敌人
        {
            for(i=2;i<=cnt2;++i) s1[cnt1+i-1]=s2[i];//将除第一个以外的B[i]>0的敌人与B[i]=0的敌人存储在一起
            if(sort(s1+1,s1+cnt1+cnt2),s2[1]<=m) Solve(cnt1-(sum-cnt2+1),s2[1],sum+1);//排序+求解,个人认为还是比较好理解的
        }
    	F.write(ans1),F.write_char(' '),F.write(ans2),F.write_char('\n');//输出答案
    }
    return F.end(),0;
}

T 3 T3 :group(点此看题面

原题: 【洛谷2519】[HAOI2011] problem a

刚看完题目可以说是一头雾水。

仔细想想,可以把每个人的状态转化为一个区间 [ a i + 1 , n b i ] [a_i+1,n-b_i] ),表示这个区间内所有元素都相等。

那么我们要求的就是求出最大的区间数,使这些区间两两之间要么重合,要么不相交

考虑将区间左边界排个序,就变成了一个类似于求最长上升子序列的过程,这可以做到 O ( N l o g N ) O(NlogN)

呃,好像成了一道普及组难度的水题…

注意,原题中输出与此题刚好相反,为 n f n n-f_n

代码如下:

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define uint unsigned int
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define abs(x) ((x)<0?-(x):(x))
#define INF 1e9
#define Inc(x,y) ((x+=(y))>=MOD&&(x-=MOD))
#define ten(x) (((x)<<3)+((x)<<1)) 
#define N 100000
using namespace std;
int n,cnt=0,cnt_=0,a[N+5],f[N+5];
struct Interval//存储区间信息
{
    int l,r,v;//l和r表示边界,v表示出现次数
    Interval(int x=0,int y=0):l(x),r(y){v=1;}
    inline friend bool operator < (Interval x,Interval y) {return x.r^y.r?x.r<y.r:x.l<y.l;}
    inline friend bool operator == (Interval x,Interval y) {return !(x.l^y.l||x.r^y.r);}
}s[N+5]; 
class FIO
{
    private:
        #define Fsize 100000
        #define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
        #define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch))
        int f,FoutSize,OutputTop;char ch,Fin[Fsize],*FinNow,*FinEnd,Fout[Fsize],OutputStack[Fsize];
    public:
        FIO() {FinNow=FinEnd=Fin;}
        inline void read(int &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=ten(x)+(ch&15),isdigit(ch=tc()));x*=f;}
        inline void read_char(char &x) {while(isspace(x=tc()));}
        inline void read_string(string &x) {x="";while(isspace(ch=tc()));while(x+=ch,!isspace(ch=tc())) if(!~ch) return;}
        inline void write(int x) {if(!x) return (void)pc('0');if(x<0) pc('-'),x=-x;while(x) OutputStack[++OutputTop]=x%10+48,x/=10;while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;}
        inline void write_char(char x) {pc(x);}
        inline void write_string(string x) {register int i,len=x.length();for(i=0;i<len;++i) pc(x[i]);}
        inline void end() {fwrite(Fout,1,FoutSize,stdout);}
}F;
inline int find(int x,int len)//二分出一个最大的res使a[res]<x,实现O(logN)转移
{
    register int l=0,r=len,mid=l+r+1>>1;
    for(;l<r;mid=l+r+1>>1) a[mid]<x?l=mid:r=mid-1;
    return l;
}
int main()
{
    register int i,j,T,x,y,res,Max;F.read(T);
    while(T--)
    {
    	for(F.read(n),i=1,cnt=cnt_=Max=0;i<=n;++i) F.read(x),F.read(y),x+y<n&&(s[++cnt_]=Interval(x+1,n-y),0),a[i]=INF;//存储区间,注意将a数组初始化为INF
    	for(sort(s+1,s+cnt_+1),i=1;i<=cnt_;++i) cnt&&s[cnt]==s[i]?s[cnt].v=min(s[cnt].v+1,s[cnt].r-s[cnt].l+1):s[++cnt]=s[i];//去重
    	for(i=1;i<=cnt;++i)//枚举区间 
        {
            for(res=find(s[i].l,Max),j=res+1,f[i]=res+s[i].v;j<=f[i];++j) a[j]=min(a[j],s[i].r);//转移+更新a数组
            Max=max(Max,f[i]);//更新最大值
        }
        F.write(Max),F.write_char('\n');//输出答案
    }
    return F.end(),0;
}

猜你喜欢

转载自blog.csdn.net/chenxiaoran666/article/details/83373778