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;
}