杂题总结 Vol.3

杂题总结 Vol.3

试题总结 1

Status: CLOSED

\(\def\EZ{\textcolor{#51af44}{\text{EZ}}}\EZ\) 表示简单,10分钟内就能想到。
\(\def\HD{\textcolor{#3173b3}{\text{HD}}}\HD\) 表示中等,能独立想出
\(\def\IN{\textcolor{#be2d23}{\text{IN}}}\IN\) 表示困难,独立思考能想到 \(50\%\) 以上
\(\def\AT{\textcolor{#383838}{\text{AT}}}\AT\) 表示非常困难,独立思考只能想出 \(50\%\) 以下

ROI 2017 [2024-09-17]

P10653 [ROI 2017 Day 2] 存储器

\(\EZ\)

可考虑 \(T\) 中每一段的分界点不得在 \(S\) 中的一个连续段上。则对于每一个 \(T\) 中连续段的区域,都独立。考虑用 std::stack 维护颜色段,每次暴力合并,复杂度不超过总段数。

P10654 [ROI 2017 Day 2] 水星上的服务器

\(\EZ\)

考虑如果对于 \(i\),要向 \(i-1\)\(i+1\) 转移,那么由于转移边有开放区间,所以要知道 \(i\) 应该接受信息的时间。以此从前往后并从后往前递推出需要的区间,并在满足一定条件的情况下可以使用缓存。

P10655 [ROI 2017 Day 2] 反物质

\(\HD^{+}\)

见 "杂题总结 Vol.2"

JOI 2024 Final [2024-09-18]

P10208 [JOI 2024 Final] 礼物交换

\(\IN\)

本题再次提示我们,忘记了一个定理不要慌。。。也许不要定理结论也能出来。


由于本题连边关系非常特殊,排序之后就会变成前缀后缀。如果把 AB 排序放到一个序列里面,那么此时就可以发现,我们要的匹配就是要求 B 与右边的 A 匹配,而且不能匹自己。

我们发现每个 B 都是往后匹配的,且每个 B 往后都至少有其自己的 A,但是不能只有其自己的 A,都必须至少要有一个别人的 A,对于所有的,包括最后一组 BA 都是这样,换句话说,如果从 B 到 A 是一个区间,每个区间都必须至少有一个相交(包含或被包含也算相交)的才可以。

再次理解一下,一个区间 BA 右边如果有 K 个 BA,那么 B 右边至少也是有 K+1 个 A 的,但是自己那个 A 不能用,如果要用别人的一个 A,就必须把自己的 A 拿出去交换,换句话说,当前这组 BA 至少有一个 B 能够吃到这个 A,否则别人的就不够了。

每次都排序是不行的。但是这里有一个 Trick,如果多次询问在一个区间内是否存在满足条件的元素、元素对,可以对于每一个位置、元素求出上、下一个满足条件的元素在哪里,从而转化为只关于判断前驱后继位置的询问,可以考虑离线询问后扫描线做。

典例:P1972 [SDOI2009] HH的项链

P10207 [JOI 2024 Final] 马拉松比赛 2

\(\IN^{-}\)

要对自己有自信,不要摆,仔细考虑每一个想法。

记得不要死磕,适时重新抽象!


本来想出了 81pts 的 DP,但是我好像不太相信自己能写出 DP,然后没继续想。

有一种天真的想法是从 \(S\) 出发之后先走到一个端点把球全拿了,然后折回终点。

但是这个是不对的,因为有可能有一边球特别多,全拿了会导致代价飙高。

以上要对必须满足两个性质:1. 折返的时候经过的球必须拿 2. 靠远的必须比靠近的先拿

2 是肯定正确的。但是 1 不一定是对的,实际上不一定要一次性拿走。

但是这个告诉我们,保留的球一定是一段。故可设 \(f_{l,r,0/1}\) 表示若从左边第一个开始,当前还剩下 \([l,r]\) 中没有拿,且当前在左边/右边端点。计算答案的时候把它与 \(S,G\) 拼上即可。当然,也不一定从左边第一个开始,再设一个 \(g\) 表示从右边第一个开始的那种,转移一样。

COCI 2023/2024 [2024-09-24]

P9173 [COCI2022-2023#4] Zrinka

\(\EZ\)

本题告诉我们,不要乱猜贪心结论。你说的对,但是贪心 35/70pts


考虑一个 dp,令 \(f_{i,j}=(x_1,x_2)\) 表示当序列 \(1\) 匹配到 \(i\),序列 \(2\) 匹配到 \(j\) 的时候,他们最优的最后一个值为 \(x_1,x_2\)。很多时候我们通过某些性质把一些值转移到状态上,所以一般 dp 数组都只是存储一个值,但是实际上这道题不需要这样做。

进一步地,也可以完全不存储值,我们只需要记录最后填的值的奇偶性即可。

P9174 [COCI2022-2023#4] Bojanje

\(\EZ\)

非常明显的特征是,迭代次数非常大,这提示我们肯定要用矩阵快速幂。

但是如果要存储这个颜色数组,未免太多了一点,所以我们应该观察一下。

每次考虑减少状态的时候,应该想想到什么样的程度就可以转移了。显然本题中只需要知道各种颜色有多少就可以了,进一步地,颜色的标号其实是不重要的,再进一步,发现已经不存在的颜色是不能参与转移的,也没有用。这样状态数就不是 \(10^{10}\),也不是贝尔数 \(B_{10}=115975\),也不是有序分拆数,而是 \(42\)

P9175 [COCI2022-2023#4] Mreža

\(\EZ^{-}\)

又板又恶心的题目。二分+主席树+熟练泼粪+线段树。代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,INF=1e9+7;
typedef long long ll;
int n,q;
struct edge{int v,vi,ci,si;};
vector<edge> e[N];
namespace PSegT{
    struct SegT{
        int ls,rs;
        ll sum;
    }t[N*100];
    int tot,root[N];
    int Alloc(int p){
        int id=++tot;
        t[id]=t[p];
        return id;
    }
    void Update(int p){t[p].sum=t[t[p].ls].sum+t[t[p].rs].sum;}
    void Insert(int &p,int base,int l,int r,int x,int v){
        p=Alloc(base);
        if(l==r) return t[p].sum+=v,void();
        int mid=(l+r)/2;
        if(x<=mid) Insert(t[p].ls,t[base].ls,l,mid,x,v);
        else       Insert(t[p].rs,t[base].rs,mid+1,r,x,v);
        Update(p);
    }
    ll Query(int L,int R,int l,int r,int ql,int qr){
        if(ql<=l and r<=qr) return t[R].sum-t[L].sum;
        int mid=(l+r)/2;ll res=0;
        if(ql<=mid) res+=Query(t[L].ls,t[R].ls,l,mid,ql,qr);
        if(qr>mid)  res+=Query(t[L].rs,t[R].rs,mid+1,r,ql,qr);
        return res;
    }
}
namespace TCS{
    struct TCSInf{
        int dfn,top,dep,fa,hson,siz;
    }s[N];
    int idx[N],tim;int inEdge[N];
    void dfs1(int x,int fa){
        s[x].fa=fa,s[x].dep=s[fa].dep+1,s[x].siz=1;
        for(edge i:e[x]){
            int v=i.v;
            if(v==fa) continue;
            dfs1(v,x),s[x].siz+=s[v].siz;
            if(s[s[x].hson].siz<=s[v].siz) s[x].hson=v;
        }
    }
    void dfs2(int x,int tp){
        s[x].top=tp,s[x].dfn=++tim,idx[tim]=x;
        if(s[x].hson) dfs2(s[x].hson,tp);
        for(edge i:e[x]){
            int v=i.v;
            if(s[x].hson==v or s[x].fa==v) continue;
            dfs2(v,v);
        }
    }
    void dfs3(int x){
        for(edge i:e[x]){
            int v=i.v;
            if(s[x].fa==v) continue;
            PSegT::Insert(PSegT::root[v],PSegT::root[x],1,1e9,i.vi,i.ci);
            dfs3(v),inEdge[v]=i.si;
        }
    }
    int LCA(int x,int y){
        while(s[x].top!=s[y].top){
            if(s[s[x].top].dep<s[s[y].top].dep)
                swap(x,y);
            x=s[s[x].top].fa;
        }
        if(s[x].dep>s[y].dep) swap(x,y);
        return x;
    }
    void InitTCS(){dfs1(1,0),dfs2(1,1),dfs3(1);}
}
namespace MIN{
    struct SegT{
        int l,r,mv;
    }t[N*4];
    #define ls(p) (p<<1)
    #define rs(p) (p<<1|1)
    #define l(p) (t[p].l)
    #define r(p) (t[p].r)
    #define mv(p) (t[p].mv)
    void Update(int p){mv(p)=min(mv(ls(p)),mv(rs(p)));}
    void Build(int p,int l,int r){
        mv(p)=INF,l(p)=l,r(p)=r;
        if(l==r) return mv(p)=TCS::inEdge[TCS::idx[l]],void();
        int mid=(l+r)/2;
        Build(ls(p),l,mid),Build(rs(p),mid+1,r);
        Update(p);
    }
    int Query(int p,int l,int r){
        if(l<=l(p) and r(p)<=r) return mv(p);
        int mid=(l(p)+r(p))/2,res=INF;
        if(l<=mid) res=min(res,Query(ls(p),l,r));
        if(r>mid)  res=min(res,Query(rs(p),l,r));
        return res;
    }
};
void TotalConstruct(){
    TCS::inEdge[1]=INF,TCS::InitTCS(),MIN::Build(1,1,n);
}
int _QueryMin(int x,int lca){
    using namespace TCS;int res=INF;
    while(s[x].top!=s[lca].top){
        res=min(res,MIN::Query(1,s[s[x].top].dfn,s[x].dfn));
        x=s[s[x].top].fa;
    }
    if(x==lca) return res;
    return min(res,MIN::Query(1,s[lca].dfn+1,s[x].dfn));
}
int QueryMin(int x,int y){
    int lca=TCS::LCA(x,y);
    return min(_QueryMin(x,lca),_QueryMin(y,lca));
}
bool Check(int x,int y,ll ci,int mid){
    int mn=QueryMin(x,y);
    if(mn<mid) return false;
    using namespace PSegT;
    int lca=TCS::LCA(x,y);
    ll cost=Query(root[lca],root[x],1,1e9,1,mid-1)+
            Query(root[lca],root[y],1,1e9,1,mid-1);
    return cost<=ci;
}
int Solve(int x,int y,ll ci){
    ll l=1,r=1e9,mid,res=0;
    while(l<=r){
        mid=(l+r)/2;
        if(Check(x,y,ci,mid)) l=mid+1,res=mid;
        else                  r=mid-1;
    }
    return res;
}
int main(){
    ios::sync_with_stdio(false),cin.tie(nullptr);
    cin>>n;
    for(int i=1;i<n;i++){
        int x,y,v,c,s;cin>>x>>y>>v>>c>>s;
        e[x].push_back((edge){y,v,c,s});
        e[y].push_back((edge){x,v,c,s});
    }
    TotalConstruct();cin>>q;
    while(q--){
        int a,b;ll ei;
        cin>>a>>b>>ei;
        cout<<Solve(a,b,ei)<<"\n";
    }
    return 0;
}
  • P9176 [COCI2022-2023#4] Vrsta \(\EZ^{--}\) 板题不配有标题

P9178 [COCI2022-2023#5] Diskurs

\(\HD\)

考虑如果把二进制取反,那么就相当于找最小汉明距离。求某种距离,我们可以考虑建图,由于单位边权的图上最短路好处理,我们考虑对所有汉明距离为 1 的数对建边。

如果从每个点开始往外遍历,是难以借助其他点信息的,很难不重复更新。我们发现,如果反过来从所有关键点(查询点)同时扩展一步,那么就得到了每个点距离最近的关键点,而且,一定不会扩展一个点两次。使用 BFS,初始入队所有点即可。

P9179 [COCI2022-2023#5] Logaritam

\(\EZ\)

注意到,对于对数序列 \(a\),若 \(x=p_1^{c_1}p_2^{c_2}\dots p_n^{c_n}\),则 \(a_x=\sum c_ia_{p_i}\),又可知道修改一个质数 \(a_p\) ,要修改的数的个数是 \(\lfloor \lvert a\rvert / p\rfloor\),所以贪心地,修改最大那个质数就可以了。

同时,这也可以说明题目中的那个结论:可以证明对于任意的相同长度的初始对数序列,改动同一位置后,在不改动这个位置的情况下将序列变为对数序列的最小改动数都是相等的。

P9180 [COCI22022-2023#5] Slasticarnica

\(\EZ\)

不要乱猜贪心!!


考虑最后剩下的只会是原序列的一段,考虑区间 dp。每次可以 \(\mathcal O(1)\) 转移,只需要转移去掉第一个数、最后一个数、在区间开头做操作、在区间结尾做操作即可,没有必要枚举去掉多个数的情况,去掉 \(k\) 个数的情况就等于在连续的 \([l\sim l+k-1,r]\) 都去掉开头。

P9910 [COCI 2023/2024#2] Dizalo

\(\HD^{+}\)

注意需要维护的东西的性质!


假如我不得不下电梯,回去的时候肯定是按照升序排列的。

升序排列之后,前面这一段就不再产生额外代价,唯一可能产生额外代价的就是后面那些没有下过电梯的。那么会产生额外代价,就等于他后面没有比他小的,等于他是后缀最小值。

从动态维护的角度是可以做的,但是我不会。

由于不好动态维护后缀最小值的变化情况,则不考虑维护,考虑对于每一个值,观察他什么时候变成后缀最小值,那就是从他后面比他小的都删完了到他自己被删掉为止,是一个连续的时间区间,考虑离线求出该时间区间。用线段树区间取最大即可。

剩下部分是一些比较麻烦的操作,要维护答案,就要求出一个数前面比他大的,后面的比他小的后缀最小值个数。这个看起来很难做。

但是注意这两个操作的元素都有一些性质:

  • 第一个操作,操作的对象都是后缀最小值,这就意味着其后面都是比他大的,这一部分可以计算,那么可以考虑用总的减去这一段。

  • 第二个操作,求的是在一个数后面比他小的后缀最小值的个数,这些数不仅位置是递增的,值也是递增的,那么,符合条件的后缀最小值其实在位置上,是连续的,可以考虑二分找出这个上界(位置下界就是操作数的位置)

代码比较复杂。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5;
int n,q;
struct BIT{
    int a[N],siz;
    void reset(){memset(a,0,sizeof a);}
    BIT(){reset();}
    void add(int x,int v){
        for(;x<=n;x+=(x&-x)) a[x]+=v;
    }
    int ask(int x){
        int res=0;
        for(;x;x-=(x&-x)) res+=a[x];
        return res;
    }
}existing_id,existing_val,marked_id;
set<pair<int,int>> marked;
struct SegTr{
    struct SegT{
        int l,r;
        int mx;
    }t[N*4];
    #define l(p) t[p].l
    #define r(p) t[p].r
    #define mx(p) t[p].mx
    #define ls(p) (p<<1)
    #define rs(p) (p<<1|1)
    void update(int p){mx(p)=max(mx(ls(p)),mx(rs(p)));}
    void build(int p,int l,int r){
        l(p)=l,r(p)=r;
        if(l==r) return;
        int mid=(l+r)/2;
        build(ls(p),l,mid),build(rs(p),mid+1,r);
    }
    void insert(int p,int x,int v){
        if(l(p)==r(p)) return mx(p)=v,void();
        int mid=(l(p)+r(p))/2;
        if(x<=mid) insert(ls(p),x,v);
        else       insert(rs(p),x,v);
        update(p);
    }
    int query(int p,int l,int r){
        if(l<=l(p) and r(p)<=r) return mx(p);
        int mid=(l(p)+r(p))/2,res=0;
        if(l<=mid) res=max(res,query(ls(p),l,r));
        if(r>mid)  res=max(res,query(rs(p),l,r));
        return res;
    }
}max_del;
vector<int> become_marked[N];bool is_del[N],is_marked[N];
int id[N],val[N],del[N],del_tim[N],sum;
signed main(){
    cin>>n>>q,max_del.build(1,1,n);
    for(int i=1;i<=n;i++) cin>>val[i],id[val[i]]=i,del_tim[i]=q+1;
    for(int i=1;i<=q;i++) cin>>del[i],del_tim[del[i]]=i;
    for(int i=1;i<=n;i++) existing_id.add(i,1),existing_val.add(i,1);
    for(int i=n;i>=1;i--){
        int tim=max_del.query(1,1,val[i]);
        become_marked[tim].emplace_back(i);
        max_del.insert(1,val[i],del_tim[i]);
    }
    marked.insert({0,0}),marked.insert({n+1,n+1});
    for(int i:become_marked[0]){
        sum+=(n-val[i])-(n-i);
        marked_id.add(i,1),is_marked[i]=true;
        marked.insert({val[i],i});
    }
    cout<<sum+n<<" ";
    for(int i=1;i<=q;i++){
        is_del[del[i]]=true;
        if(is_marked[del[i]]){
            is_marked[del[i]]=false;
            marked_id.add(del[i],-1);
            marked.erase({val[del[i]],del[i]});
            sum-=(n-existing_val.ask(val[del[i]]))-
                 (n-existing_id.ask(del[i]));
        }else{
            int upper=prev(marked.lower_bound({val[del[i]],0}))->second;
            sum-=marked_id.ask(upper)-marked_id.ask(del[i]);
        }
        existing_val.add(val[del[i]],-1);;
        existing_id.add(del[i],-1);
        for(int j:become_marked[i]){
            if(is_del[j]) continue;
            is_marked[j]=true;marked_id.add(j,1);
            marked.insert({val[j],j});
            sum+=(n-existing_val.ask(val[j]))-(n-existing_id.ask(j));
        }
        cout<<sum+n-i<<" ";
    }
    return 0;
}

Last updated: 2024-09-25

posted @ 2024-09-19 17:10  haozexu  阅读(14)  评论(0编辑  收藏  举报