NOIP 2023 题解

A

难度:\(\colorbox{#f39c11}{\color{White}普及-}\)

B

难度:\(\colorbox{#52c41a}{\color{White}普及+/提高}\)

C

原题

难度:\(\colorbox{#9d3dcf}{\color{White}省选/\text{NOI-}}\)

\(\large\textbf{Statement.}\)

题意,即给定一个 \(n\times m\) 的网格图,\((i,j)\) 可以通过当且仅当 \(a_i>b_j\)(若 \(a_1<b_1\) 则需先将 \(a,b\) 交换),求出 \((1,1)\)\((n,m)\) 是否处于一个八联通。


\(\large\textbf{Solution.}\)

考虑什么时候不连通,发现只有下面两种情况:

\(1.\) 存在一条横着或竖着的直线,满足直线上的格点都无法通过,且将整个网格分为两部分。

证明:

参考代码:

#include<bits/stdc++.h>
#define ll long long
#define mxn 500003
#define md 1000000007
#define ld long double
#define pb push_back
#define mkp make_pair
#define umap unordered_map
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define rept(i,a,b) for(int i=a;i<b;++i)
#define drep(i,a,b) for(int i=a;i>=b;--i)
#define pq priority_queue
using namespace std;
inline int read(){
    int x=0;char c=getchar();
    while(!isdigit(c))c=getchar();
    while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x;
}
int n,m,q,ct,a[mxn],b[mxn],a1[mxn],b1[mxn],f1[mxn],f2[mxn];
vector<int>c1,c2;
bool solve(){
    if(a[1]==b[1])return 0;
    bool fl=a[1]<b[1];
    if(fl)swap(a,b),swap(n,m);
    int mn=1e9,mx=0;
    rep(i,1,n)mn=min(mn,a[i]);
    rep(i,1,m)mx=max(mx,b[i]);
    ct=0;
    rep(i,1,m)if(b[i]>=mn)ct++;
    if(ct==m){
    	if(fl)swap(a,b),swap(n,m);
    	return 0;
	}
    ct=0;
    rep(i,1,n)if(a[i]<=mx)ct++;
    if(ct==n){
    	if(fl)swap(a,b),swap(n,m);
    	return 0;
	}
	{
		mn=1e9;
		int rt=1;
		rep(i,1,n){
			mn=min(mn,a[i]);
			while(rt<=m&&b[rt]>=mn)rt++;
			f1[i]=rt-1;
		}
		mx=0;
		rt=1;
		rep(i,1,m){
			mx=max(mx,b[i]);
			while(rt<=n&&a[rt]<=mx)rt++;
			f2[i]=rt-1;
		}
		rep(i,1,n)if(f1[i]&&f2[f1[i]]>=i){
    		if(fl)swap(a,b),swap(n,m);
    		return 0;
		}
	}
	{
		mn=1e9;
		int rt=m;
		drep(i,n,1){
			mn=min(mn,a[i]);
			while(rt>=1&&b[rt]>=mn)rt--;
			f1[i]=rt+1;
		}
		mx=0;
		rt=n;
		drep(i,m,1){
			mx=max(mx,b[i]);
			while(rt>=1&&a[rt]<=mx)rt--;
			f2[i]=rt+1;
		}
		rep(i,1,n)if(f1[i]<=m&&f2[f1[i]]<=i){
    		if(fl)swap(a,b),swap(n,m);
    		return 0;
		}
	}
    if(fl)swap(a,b),swap(n,m);
    return 1;
}
signed main(){
    read(),n=read(),m=read(),q=read();
    rep(i,1,n)a[i]=a1[i]=read();
    rep(i,1,m)b[i]=b1[i]=read();
    putchar(solve()?'1':'0');
    int k0,k1,x,y;
    while(q--){
        k0=read(),k1=read();
        c1.clear(),c2.clear();
        while(k0--)x=read(),y=read(),a[x]=y,c1.pb(x);
        while(k1--)x=read(),y=read(),b[x]=y,c2.pb(x);
        putchar(solve()?'1':'0');
        for(int i:c1)a[i]=a1[i];
        for(int i:c2)b[i]=b1[i];
    }
    return 0;
}

D

难度:\(\colorbox{#3498db}{\color{White}提高+/省选}\)

\(\large\textbf{Statement.}\)

题意,即求出选择若干个互不相交且互不相邻的区间,每个区间的大小不超过 \(k\),每个区间覆盖到的挑战的权值和 \(-\sum (r-l+1)\cdot d\) 的最大值。


\(\large\textbf{Solution.}\)

直接 \(\tt dp\),令 \(f_n\) 表示前 \(n\) 天的答案,枚举一个 \(r=n\) 的区间的左端点对当前状态进行更新,最后再与 \(f_{n-1}\) 取个最大值。

这样就得到了一个 \(O(tn^2)\) 的做法,离散化以后可以做到 \(O(tm^2)\)

然后就是线段树优化 \(\tt dp\) 板子题,时间复杂度 \(O(tm\log m)\),可以轻松通过。

参考代码:

#include<bits/stdc++.h>
#define ll long long
#define mxn 100003
#define md 1000000007
#define ld long double
#define pb push_back
#define mkp make_pair
#define umap unordered_map
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define rept(i,a,b) for(int i=a;i<b;++i)
#define drep(i,a,b) for(int i=a;i>=b;--i)
#define pq priority_queue
using namespace std;
struct node{
    int x,y,z;
}a[mxn];
int C,T,n,m,k,d,rt,tot,f[mxn<<1];
ll t[mxn<<3],dp[mxn<<1],ad[mxn<<3];
inline void push_down(int p){
    if(ad[p]){
        ad[p<<1]+=ad[p],t[p<<1]+=ad[p];
        ad[p<<1|1]+=ad[p],t[p<<1|1]+=ad[p];
        ad[p]=0;
    }
}
void build(int p,int l,int r){
    ad[p]=0;
    if(l==r){
        t[p]=(ll)f[l+1]*d;
        return;
    }
    int mid=(l+r)>>1;
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
    t[p]=max(t[p<<1],t[p<<1|1]);
}
void add(int p,int l,int r,int x,int L,int R){
    if(l>r)return;
    if(l<=L&&R<=r){
        ad[p]+=x,t[p]+=x;
        return;
    }
    push_down(p);
    int mid=(L+R)>>1;
    if(l<=mid)add(p<<1,l,r,x,L,mid);
    if(r>mid)add(p<<1|1,l,r,x,mid+1,R);
    t[p]=max(t[p<<1],t[p<<1|1]);
}
ll ask(int p,int l,int r,int L,int R){
    if(l>r)return -1e18;
    if(l<=L&&R<=r)return t[p];
    push_down(p);
    int mid=(L+R)>>1;
    if(l<=mid&&r>mid)return max(ask(p<<1,l,r,L,mid),ask(p<<1|1,l,r,mid+1,R));
    if(l<=mid)return ask(p<<1,l,r,L,mid);
    return ask(p<<1|1,l,r,mid+1,R);
}
void change(int p,int x,ll y,int l,int r){
    if(l==r){t[p]=y;return;}
    push_down(p);
    int mid=(l+r)>>1;
    if(x<=mid)change(p<<1,x,y,l,mid);
    else change(p<<1|1,x,y,mid+1,r);
    t[p]=max(t[p<<1],t[p<<1|1]);
}
signed main(){
    scanf("%d%d",&C,&T);
    while(T--){
        scanf("%d%d%d%d",&n,&m,&k,&d);
        f[tot=1]=0;
        rep(i,1,m){
            scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
            swap(a[i].x,a[i].y);
            a[i].x=a[i].y-a[i].x+1;
            f[++tot]=a[i].x,f[++tot]=a[i].y;
        }
        sort(f+1,f+tot+1);
        tot=unique(f+1,f+tot+1)-f-1;
        build(1,1,tot);
        rt=1;
        sort(a+1,a+m+1,[](node x,node y){
            return x.y<y.y;
        });
        rep(i,2,tot){
            while(rt<=m&&a[rt].y<=f[i]){
                add(1,1,lower_bound(f+1,f+tot+1,a[rt].x)-f-1,a[rt].z,1,tot);
                rt++;
            }
            dp[i]=max(ask(1,lower_bound(f+1,f+tot+1,f[i]-k+1)-f-1,i-1,1,tot)-(ll)(f[i]+1)*d,dp[i-1]);
            if(f[i]+1==f[i+1])change(1,i,dp[i-1]+(ll)f[i+1]*d,1,tot);
            else change(1,i,dp[i]+(ll)f[i+1]*d,1,tot);
        }
        printf("%lld\n",dp[tot]);
    }
    return 0;
}
posted @ 2023-11-24 14:01  zifanwang  阅读(30)  评论(0编辑  收藏  举报  来源