P9870题解

P9870 [NOIP2023] 双序列拓展

题目传送门

题解

T3 双序列拓展(expand)

考察:dp、人类智慧(

部分分启示正解。

  • \(35pts\)\(O(qnm)\)

将原题目转化为这样:两个指针分别指着两个序列,每次将任意至少一个指针向后移一个位置,并使每时每刻都满足两个指针所指的位置有单调的偏序关系。

所谓单调的偏序关系可以用第一个位置来判断,在下文我们只考虑 \(a<b\) 的情况。

我们考虑 dp:记 \(f_{i,j}\) 表示 \(a_{1-i}\) 能否和 \(b_{1-j}\) 匹配,那么有转移 \(f_{i,j}=[a_i<b_j]\operatorname{and}(f_{i-1,j}\operatorname{or}f_{i,j-1}\operatorname{or}f_{i-1,j-1})\)。此时即可 \(O(qnm)\) 完成本题。

  • \(70pts\) 的特殊性质

我们考虑构造 01 矩阵 \(c\),其中 \(c_{i,j}=[a_i<b_j]\),那么等价于我们要判断是否存在只走 \(1\),只能向下、向右、向右下的路径从 \((1,1)\)\((n,m)\)

其实我们不会向右下走,因为可以证明不存在

1 0
0 1

这样的矩阵,但对正解没啥作用,所以我就没管了。

由于性质保证,我们发现最后一列与最后一行都是 \(1\)。接下来我们考虑 \(a\)\(a_n\) 外的最小值 \(a_{\min}\)\(b\)\(b_m\) 外的最小值 \(b_{\min}\),如果 \(a_{\min}<b_{\min}\),那么 \(a_{\min}\) 所在的列也全是 \(1\),于是我们可以递归下去;否则,一定有 \(b_{\min}\) 所在的行全是 \(0\)

对于 \(a_{\max}\)\(b_{\max}\) 我们同样考虑,如果 \(b_{\max}>a_{\max}\) 那么就继续递归,否则一定有 \(a_{\max}\) 所在的列全是 \(0\),然后你发现,\(b_{\min}\) 所在的行与 \(a_{\max}\) 所在的列把 \((1,1)\) 围起来了!于是就直接返回 false。

实现的话预处理前缀 \(\min\)\(\max\) 即可。

  • \(100pts\)

其实 \(70pts\) 就是 \(100pts\) 的一半,物理意义的一半,我们寻找全局 \(a_{\min}\) 和全局 \(b_{\max}\),只要有一列或一行全是 \(0\),那么就相当于分割了 \((1,1)\)\((n,m)\),否则做两次特殊性质即可。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int rd() {
	int s=0,m=0;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-')m=1;ch=getchar();}
	while( isdigit(ch)) s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
	return m?-s:s;
}
int cc,n,m,q,c[500005],d[500005],a[500005],b[500005],pax[500005],pan[500005],sax[500005],san[500005],pbx[500005],pbn[500005],sbx[500005],sbn[500005];
bool calc1(int x,int y) {
	if(x==1||y==1) return 1;
	int ax=pax[x-1],an=pan[x-1],bx=pbx[y-1],bn=pbn[y-1];
	if(a[an]<b[bn]) return calc1(an,y);
	if(b[bx]>a[ax]) return calc1(x,bx);
	return 0;
}
bool calc2(int x,int y) {
	if(x==n||y==m) return 1;
	int ax=sax[x+1],an=san[x+1],bx=sbx[y+1],bn=sbn[y+1];
	if(a[an]<b[bn]) return calc2(an,y);
	if(b[bx]>a[ax]) return calc2(x,bx);
	return 0;
}
bool check() {
	if(a[1]>b[1]) {
		for(int i=1;i<=n;i++) a[i]*=-1;
		for(int i=1;i<=m;i++) b[i]*=-1;
	}
	pax[1]=1,pan[1]=1;
	for(int i=2;i<=n;i++)
		pax[i]=(a[pax[i-1]]>a[i]?pax[i-1]:i),
		pan[i]=(a[pan[i-1]]<a[i]?pan[i-1]:i);
	pbx[1]=1,pbn[1]=1;
	for(int i=2;i<=m;i++)
		pbx[i]=(b[pbx[i-1]]>b[i]?pbx[i-1]:i),
		pbn[i]=(b[pbn[i-1]]<b[i]?pbn[i-1]:i);
	sax[n]=n,san[n]=n;
	for(int i=n-1;i>=1;i--)
		sax[i]=(a[sax[i+1]]>a[i]?sax[i+1]:i),
		san[i]=(a[san[i+1]]<a[i]?san[i+1]:i);
	sbx[m]=m,sbn[m]=m;
	for(int i=m-1;i>=1;i--)
		sbx[i]=(b[sbx[i+1]]>b[i]?sbx[i+1]:i),
		sbn[i]=(b[sbn[i+1]]<b[i]?sbn[i+1]:i);
	int ax=pax[n],an=pan[n],bx=pbx[m],bn=pbn[m];
	if(a[an]>=b[bn]) return 0;
	if(b[bx]<=a[ax]) return 0;
	return calc1(an,bx)&calc2(an,bx);
}
signed main() {
	cin>>cc>>n>>m>>q;
	for(int i=1;i<=n;i++)
		a[i]=c[i]=rd();
	for(int i=1;i<=m;i++)
		b[i]=d[i]=rd();
	cout<<check();
	while(q--) {
		for(int i=1;i<=n;i++) a[i]=c[i];
		for(int i=1;i<=m;i++) b[i]=d[i];
		int k1=rd(),k2=rd();
		for(int i=1;i<=k1;i++) {
			int p=rd(),v=rd();
			a[p]=v;
		}
		for(int i=1;i<=k2;i++) {
			int p=rd(),v=rd();
			b[p]=v;
		}
		cout<<check();
	}
	return 0;
}

时间复杂度 \(O(q(n+m))\)

posted @ 2024-01-19 15:33  operator-  阅读(81)  评论(0)    收藏  举报