• NOIP2018提高组Day1 解题报告


    前言

    关于(NOIP2018),详见此博客:NOIP2018学军中学游记(11.09~11.11)

    这次(NOIP Day1)的题目听说很简单(毕竟是三道原题),然而我(T3)依然悲剧地写炸了。

    很奇怪啊,毕竟在几乎所有民间数据中我这题都(AC)了... ...

    (T1):铺设道路(点此看题面

    另一个题面

    我的思路是,每个元素肯定都是由其左右两边第一个比它小的数转移而来的。

    于是就开了两个单调栈,前后各扫一遍,求出了答案。

    然而貌似还有更简单的解法?但我不会。

    代码如下:

    #include<bits/stdc++.h>
    #define N 100000
    using namespace std;
    int n,a[N+5],s[N+5],Stack[N+5];
    int main()
    {
        register int i,ans=0,Top=0;
        for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
        for(i=1;i<=n;++i)//从前往后,求出第一个小于等于a[i]的数
        {
            while(Top&&Stack[Top]>=a[i]) --Top;//单调栈
            s[i]=a[i]-Stack[Top],Stack[++Top]=a[i];//将a[i]-Stack[Top]存储下来,然后将a[i]加入栈
        }
        for(Top=0,i=n;i;--i)//从后往前,求出第一个小于a[i]的数
        {
            while(Top&&Stack[Top]>a[i]) --Top;//单调栈,注意前面加了=,这题就不能加了,否则会重复计算
            ans+=min(s[i],a[i]-Stack[Top]),Stack[++Top]=a[i];//统计答案
        }
        return printf("%d",ans),0;
    }
    

    (T2):货币系统(点此看题面

    比较裸的完全背包但是我不会。

    我的思路就是先(sort)一遍,然后暴力更新每种价值是否能由之前的价值组合而成。

    这样显然会(TLE)

    于是我又考虑再用一个(lst)数组存储每种价值上一次是被价值为多少的元素更新的,如果上次就是由当前元素更新的,则可直接(break)

    这样一优化就(AC)了(实际上加上这个优化之后与完全背包应该是等价的)。

    代码如下:

    #include<bits/stdc++.h>
    #define N 100
    #define P 25000
    using namespace std;
    inline void Gmax(int &x,int y) {x<y&&(x=y);}
    int n,a[N+5],vis[P+5],lst[P+5];
    int main()
    {
        register int T,i,j,k,ans,Max;
        for(scanf("%d",&T);T;--T)
        {
            for(scanf("%d",&n),ans=n,Max=0,i=1;i<=n;++i) scanf("%d",&a[i]),Gmax(Max,a[i]);//求最大值
            for(vis[0]=T,sort(a+1,a+n+1),i=1;i<=n;++i)//现将a[i]排序一遍,并标记价值0已被访问
            {
                if(vis[a[i]]^T)//如果当前的价值不可以被之前的价值组合而成
                {
                    for(j=Max;~j;--j)//枚举值 
                    {
                        if(vis[j]^T) continue;//如果当前值不能被之前的价值组合而成,跳过
                        for(k=1;1LL*a[i]*k+j<=Max;++k)//更新
                        {
                            if(vis[a[i]*k+j]^T||lst[a[i]*k+j]^a[i]) vis[a[i]*k+j]=T,lst[a[i]*k+j]=a[i];//如果没访问过,或不是由当前价值的元素更新的,更新其vis数组和lst数组
                            else break;//否则,可以直接退出循环
                        }
                    }
                }
                else --ans;//如果可以组合而成,将ans减1
            }
            printf("%d
    ",ans);
        }
    }
    

    (T3):赛道修建(点此看题面

    这题有很多做法,我个人认为还是二分+(multiset)比较好写。

    首先,先二分答案(ans)。(关于二分的上界,可以设置为树的直径

    关于如何验证,我们可以考虑用一个变量(tot)存储满足条件的路径数,并对树上每一个节点开一个(multiset)

    对于当前节点(x),设其有(Size)个子节点,由于每条边只能选择一次,则最多只有(Size)条从子节点到其的路径会被选择。

    而这些路径可能有长度大于等于(ans)的,对于这样的边,直接将(tot)(1)即可,否则,可以将其扔入(multiset)

    比较显然,我们可以开一个变量(res)来存储没有被选择的边中最长边的长度。然后从小到大枚举剩下的路径,每次找到与其和大于等于(ans)的最短边,并将它们同时删除,然后将(tot)(1)。如果找不到,就更新(res)

    最后返回(res),就是最后选择的通向父节点的路径。

    代码如下:

    #include<bits/stdc++.h>
    #define Gmax(x,y) (x<(y)&&(x=(y)))
    #define Gmin(x,y) (x>(y)&&(x=(y)))
    #define N 50000
    #define add(x,y,v) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].val=v)
    using namespace std;
    int n,m,ee,lnk[N+5];
    struct edge
    {
        int to,nxt,val,used;
    }e[(N<<1)+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,Fsize,stdout),Fout[(FoutSize=0)++]=ch))
            int Top,FoutSize;char ch,*A,*B,Fin[Fsize],Fout[Fsize],Stack[Fsize];
        public:
            Class_FIO() {A=B=Fin;}
            inline void read(int &x) {x=0;while(!isdigit(ch=tc()));while(x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));}
            inline void write(int x) {if(!x) return pc('0');while(x) Stack[++Top]=x%10+48,x/=10;while(Top) pc(Stack[Top--]);}
            inline void clear() {fwrite(Fout,1,FoutSize,stdout),FoutSize=0;}
    }F;
    class Class_TreeDiameterSolver//求树的直径,实际上可以直接用BFS,但我用了树形DP
    {
        private:
            int ans,Max[N+5],Max_[N+5],MaxSon[N+5];
            inline void dfs1(int x,int lst)
            {
                register int i;
                for(Max[x]=Max_[x]=MaxSon[x]=0,i=lnk[x];i;i=e[i].nxt)
                {
                    if(!(e[i].to^lst)) continue;
                    dfs1(e[i].to,x);
                    if(Max[e[i].to]+e[i].val>Max[x]) Max_[x]=Max[x],Max[x]=Max[MaxSon[x]=e[i].to]+e[i].val;
                    else if(Max[e[i].to]+e[i].val>Max_[x]) Max_[x]=Max[e[i].to]+e[i].val;
                }
                Gmax(ans,Max[x]+Max_[x]);
            }
            inline void dfs2(int x,int lst,int val)
            {
                register int i;
                for(i=lnk[x];i;i=e[i].nxt)
                {
                    if(!(e[i].to^lst)) continue;
                    dfs2(e[i].to,x,max(val,e[i].to^MaxSon[x]?Max[x]:Max_[x])+e[i].val);
                }
                Gmax(ans,Max[x]+val);
            }
        public:
            inline int GetAns() {return dfs1(1,0),dfs2(1,0,0),ans;}
    }TD;
    class Class_Checker//验证答案
    {
        private:
            int tot;multiset<int> s[N+5];multiset<int>::iterator p;
            inline int dfs(int x,int lst,int val)//遍历树,x表示当前访问到的节点,lst表示父节点,val表示当前验证的答案
            {
                register int i,t,res=0;//res存储没有被选择边中最长边的长度
                for(s[x].clear(),i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&((t=dfs(e[i].to,x,val)+e[i].val)>=val?++tot:(s[x].insert(t),0));//枚举子节点,如果当前边长度大于等于val,则将tot加1,否则将其扔入multiset
                while(!s[x].empty())//如果multiset不为空
                {
                    if(t=*s[x].begin(),!(s[x].size()^1)) return max(res,t);//如果只剩一条边,返回res与当前边长度的较大值
                    (p=s[x].lower_bound(val-t))==s[x].begin()&&!(s[x].count(t)^1)&&(++p,0),(p==s[x].end()?Gmax(res,t):(s[x].erase(p),++tot)),s[x].erase(s[x].begin());//找到与其和大于等于ans的最短边,并将它们同时删除,然后将tot加1。如果找不到,就更新res
                }
                return res;//返回res
            }
        public:
            inline bool Check(int val) {return tot=0,dfs(1,0,val),tot>=m;}//判断tot是否大于等于m
    }C;
    int main()
    {
        register int i,x,y,v,l,r,mid;
        for(F.read(n),F.read(m),i=1;i<n;++i) F.read(x),F.read(y),F.read(v),add(x,y,v),add(y,x,v);
        for(mid=(l=0)+(r=TD.GetAns())+1>>1;l<r;mid=l+r+1>>1) C.Check(mid)?l=mid:r=mid-1;//二分答案
        return F.write(l),F.clear(),0;
    }
    
  • 相关阅读:
    【原】 POJ 1308 Is It A Tree? 并查集树结构 解题报告
    终于决定投身Linux怀抱
    Inside the C++ Object Model
    Fedora 下 OpenCV 的安装
    sed 与 awk
    工具链接收藏
    [转] 计算机视觉领域稍微容易中的期刊
    QtCreator开发多文档编辑器(Project 1)
    Fedora 17: Grub Rescue
    做文档类的工作总是让我感到一些烦躁
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/NOIP2018Day1.html
Copyright © 2020-2023  润新知