NOI online2022题解

丹钓战

solution

考虑模拟题中所描述的过程:首先第一个元素 \(a\) 入栈,它是“成功的”;之后其他不成功的元素入栈;最后某个元素 \(b\) 入栈后弹出所有元素;之后循环往复。注意到 \(b\) 成功后之后的变化和加入 \(b\) 前的元素完全没有关系了,这启示我们对每个元素找到它后面第一个会将它弹出的元素,那么每次询问只需从 \(l\) 处往后跳直到 \(r\) 即可。使用倍增即可做到单次 \(\mathcal O(\log n)\)

至于如何找到后面第一个会将其弹出的元素?只需要按照题意从左到右模拟一遍弹栈过程即可。

总复杂度为 \(\mathcal O(n\log n)\)

code

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n,q,tot,a[N],b[N],sta[N],top,id[N],go[20][N];
int main()
{
	scan(n),scan(q);
	for(int i=1;i<=n;++i)scan(a[i]);
	for(int i=1;i<=n;++i)scan(b[i]);
	for(int i=1;i<=n;++i)
	{
		while(top)
		{
			if(a[i]!=a[sta[top]]&&b[i]<b[sta[top]])break;
			go[0][sta[top]]=i;--top;
		}
		sta[++top]=i;
	}
	while(top)go[0][sta[top]]=n+1,--top;
	go[0][n+1]=n+1;
	for(int i=1;(1<<i)<=n;++i)
		for(int j=n+1;j;--j)
			go[i][j]=go[i-1][go[i-1][j]];
	int lim=__lg(n);
	while(q--)
	{
		int l,r;scan(l),scan(r);
		int res=1;
		for(int i=lim;~i;--i)
			if(go[i][l]<=r)
				res+=1<<i,l=go[i][l];
		putint(res,'\n');
	}iobuff::flush();
	return 0;
}

讨论

solution

首先有一个暴力的想法:枚举每道题,然后将所有包含该题的集合按照集合大小从小到大排序,之后依次check相邻的两个是否满足包含关系。如果不满足则直接找到解,否则无解。

该算法的缺点在于虽然总比较次数是 \(\mathcal O(m)\) 的了,但每次比较时的复杂度仍然可能达到 \(\mathcal O(n)\) 。然而注意到如果无解,那么集合之间按照包含关系会形成一棵树。如果我们找到了这棵树,那么就只需要 \(\mathcal O(\sum k)=\mathcal O(m)\) 的复杂度对所有包含关系check从而得到答案。对于暴力中排序后相邻的集合(设为 \(a,b\) ),我们知道 \(b\) 一定是 \(a\) 的祖先。而 \(a\) 的所有祖先中集合大小最小的就是它的父亲。据此建出树来,最后check即可。

注意初始时需要去重。

code

#include<bits/stdc++.h>
namespace iobuff{
	const int LEN=1000000;
	char in[LEN+5], out[LEN+5];
	char *pin=in, *pout=out, *ed=in, *eout=out+LEN;
	inline char gc(void)
	{
		return pin==ed&&(ed=(pin=in)+fread(in, 1, LEN, stdin), ed==in)?EOF:*pin++;
	}
	inline void pc(char c)
	{
		pout==eout&&(fwrite(out, 1, LEN, stdout), pout=out);
		(*pout++)=c;
	}
	inline void flush()
	{ fwrite(out, 1, pout-out, stdout), pout=out; }
	template<typename T> inline void scan(T &x)
	{
		static int f;
		static char c;
		c=gc(), f=1, x=0;
		while(c<'0'||c>'9') f=(c=='-'?-1:1), c=gc();
		while(c>='0'&&c<='9') x=10*x+c-'0', c=gc();
		x*=f;
	}
}
using iobuff::scan;
using namespace std;
const int N=1e6+5;
const int B=3,mod=998244353;
map<int,bool>mp[N];
vector<int>p[N],q[N];
int n,pw[N],id[N],pa[N];bool pd[N];
inline bool ok(int x,int y)
{
	for(int u:p[x])
	{
		auto pi=lower_bound(p[y].begin(),p[y].end(),u);
		if(pi!=p[y].end()&&(*pi)==u)continue;
		return false;
	}
	return true;
}
inline void clear()
{
	for(int i=1;i<=n;++i)q[i].clear(),mp[i].clear();
}
int main()
{
	int T;scan(T);
	int lim=1e6;
	pw[0]=1;for(int i=1;i<=lim;++i)pw[i]=1ll*pw[i-1]*B%mod;
	while(T-->0)
	{
		scan(n);int tot=0;
		for(int i=1;i<=n;++i)
		{
			id[++tot]=i;p[tot].clear();
			int k,d,kk;scan(k);kk=k;
			int h=0;
			while(k--)
			{
				scan(d),p[tot].push_back(d),h+=pw[d];
				h>=mod?h-=mod:0;
			}
			if(mp[kk].count(h)||kk==0)--tot;
			else
			{
				mp[kk][h]=1;
				for(int u:p[tot])q[u].push_back(tot);
			}
		}
		for(int i=1;i<=tot;++i)pa[i]=tot+1;bool fl=0;
		for(int i=1;i<=tot;++i)sort(p[i].begin(),p[i].end());
		for(int i=1;i<=n;++i)
		{
			if(q[i].empty())continue;
			sort(q[i].begin(),q[i].end(),[&](const int&x,const int&y){return p[x].size()<p[y].size();});
			for(int t=0;t<q[i].size()-1;++t)
			{
				int u=q[i][t],v=q[i][t+1];
				if(pa[u]==tot+1||p[pa[u]].size()>p[v].size())pa[u]=v;
				else if(pa[u]!=v&&p[pa[u]].size()==p[v].size())
				{
					puts("YES");
					if(!ok(u,pa[u]))printf("%d %d\n",id[u],id[pa[u]]);
					else if(!ok(u,v))printf("%d %d\n",id[u],id[v]);
					else if(!ok(pa[u],v))printf("%d %d\n",id[pa[u]],id[v]);
					fl=1;break;
				}
			}
			if(fl)break;
		}
		if(fl){clear();continue;}
		for(int i=1;i<=tot;++i)if(pa[i]!=tot+1)
		{
			if(ok(i,pa[i]))continue;
			puts("YES");printf("%d %d\n",id[pa[i]],id[i]);
			fl=1;break;
		}
		if(!fl)puts("NO");
		clear();
	}
	return 0;
}

如何正确地排序

solution

\(m=1,2\) 的情况都是平凡的,不再赘述。

对于 \(m=3\) ,考虑 \(\min\) 怎么算, \(\max\) 同理。多变量问题从来都是困难的,于是我们转换思路,考虑对每个元素计算它会贡献多少次。以第一层的元素来说,加入我们在考虑 \(a_{1,i}\) ,其贡献次数是满足如下条件的 \(j\) 的个数:

\[a_{1,i}+a_{1,j}\le a_{2,i}+a_{2,j}\\ a_{1,i}+a_{1,j}\le a_{3,i}+a_{3,j} \]

将变量和变量分开:

\[a_{1,i}-a_{2,i}\le a_{2,j}-a_{1,j}\\ a_{1,i}-a_{3,i}\le a_{3,j}-a_{1,j} \]

这便是一个经典的二维数点问题:平面上有 \(n\) 个点,第 \(i\) 个点坐标为 \((a_{1,i}-a_{2,i},a_{1,i}-a_{3,i})\) ,若干次询问以左下角为 \((-\inf,-\inf)\) ,右上角为 \((a_{2,j}-a_{1,j},a_{3,j}-a_{1,j})\) 的矩形区域内有多少个点。直接排序后+树状数组即可解决。第二层和第三层的贡献类似处理。

对于 \(m=4\) ,固然可以类似上面的方法转化为三维偏序,不过还有更有趣的做法,即使用min-max反演将max反演掉,可得:

\[f(i,j)=\min_{t=1}^k(a_{k,i}+a_{k,j})+\sum_{S\subseteq \{1,2,3,4\}}(-1)^{|S|-1}\min_{t\in S}(a_{k,i}+a_{k,j})\\ =\sum_{S\subsetneqq \{1,2,3,4\}}(-1)^{|S|-1}\min_{t\in S}(a_{k,i}+a_{k,j}) \]

于是又转化为 \(m\le3\) 的情况,照着之前的方法做即可。

code

巨丑无比的代码。

#include<bits/stdc++.h>
using namespace std;
const int N=4e5+5;
using ll=long long;
int m,n,a[5][N];
namespace sub2
{
	ll ans;int p[N];
	inline ll main(bool tp=0)
	{
		ans=0;
		if(!tp)
		{
			for(int i=1;i<=m;++i)
				for(int j=1;j<=n;++j)
					ans+=a[i][j];
			ans*=2*n;
		}
		else
		{
			for(int i=1;i<=n;++i)p[i]=a[1][i]-a[2][i];
			sort(p+1,p+n+1);
			for(int i=1;i<=n;++i)
				ans+=1ll*a[1][i]*(upper_bound(p+1,p+n+1,a[2][i]-a[1][i])-1-p);
			reverse(p+1,p+n+1);
			for(int i=1;i<=n;++i)p[i]=-p[i];
			for(int i=1;i<=n;++i)
				ans+=1ll*a[2][i]*(lower_bound(p+1,p+n+1,a[1][i]-a[2][i])-1-p);
			ans*=2;
		}
		return ans;
	}
}
namespace sub3
{
	int totx,toty,p[N],q[N],tmpx[N],tmpy[N];ll ans;
	namespace Fenwick
	{
		#define lb(x) (x&(-x))
		int c[N];
		inline void upd(int x,int v){for(;x<=toty;x+=lb(x))c[x]+=v;}
		inline int query(int x){int ret=0;for(;x;x-=lb(x))ret+=c[x];return ret;}
	}
	using namespace Fenwick;
	inline void lsh()
	{
		totx=toty=0;
		for(int i=1;i<=n;++i)
			tmpx[++totx]=p[i],tmpx[++totx]=-p[i],tmpy[++toty]=q[i],tmpy[++toty]=-q[i];
		sort(tmpx+1,tmpx+totx+1);
		sort(tmpy+1,tmpy+toty+1);
		totx=unique(tmpx+1,tmpx+totx+1)-tmpx-1;
		toty=unique(tmpy+1,tmpy+toty+1)-tmpy-1;
	}
	inline int askx(int x){return lower_bound(tmpx+1,tmpx+totx+1,x)-tmpx;}
	inline int asky(int x){return lower_bound(tmpy+1,tmpy+toty+1,x)-tmpy;}
	vector<int>up[N],qp[N];
	inline ll work(int tp)
	{
		ll res=0;
		for(int i=1;i<=n;++i)
			up[askx(p[i])].push_back(i),qp[askx(-p[i])].push_back(i);
		for(int i=1;i<=totx;++i)
		{
			if(tp==1)
			{
				for(int u:up[i])upd(asky(q[u]),1);
				for(int u:qp[i])res+=1ll*a[tp][u]*query(asky(-q[u]));
			}
			else if(tp==2)
			{
				for(int u:qp[i])res+=1ll*a[tp][u]*query(asky(-q[u]));
				for(int u:up[i])upd(asky(q[u]),1);
			}
			else
			{
				for(int u:qp[i])res+=1ll*a[tp][u]*query(asky(-q[u])-1);
				for(int u:up[i])upd(asky(q[u]),1);
			}
		}
		fill(c+1,c+toty+1,0);
		for(int i=1;i<=totx;++i)up[i].clear(),qp[i].clear();
		return res*2;
	}
	inline ll main(bool tp=0)
	{
		ans=0;
		for(int i=1;i<=n;++i)
			p[i]=a[1][i]-a[2][i],q[i]=a[1][i]-a[3][i];
		lsh();ans+=work(1);
		for(int i=1;i<=n;++i)p[i]=-p[i],q[i]=-q[i];
		if(!tp)ans+=work(1);
		for(int i=1;i<=n;++i)
			p[i]=a[2][i]-a[1][i],q[i]=a[2][i]-a[3][i];
		lsh();ans+=work(2);
		for(int i=1;i<=n;++i)p[i]=-p[i],q[i]=-q[i];
		if(!tp)ans+=work(2);
		for(int i=1;i<=n;++i)
			p[i]=a[3][i]-a[1][i],q[i]=a[3][i]-a[2][i];
		lsh();ans+=work(3);
		for(int i=1;i<=n;++i)p[i]=-p[i],q[i]=-q[i];
		if(!tp)ans+=work(3);
		return ans;
	}
}
namespace sub4
{
	ll ans;int b[5][N];
	inline ll main()
	{
		ans=0;
		for(int i=1;i<=m;++i)
			for(int j=1;j<=n;++j)
				b[i][j]=a[i][j];
		m=1;
		for(int i=1;i<=4;++i)
		{
			for(int j=1;j<=n;++j)a[1][j]=b[i][j];
			ans+=sub2::main();
		}
		m=2;
		for(int i=1;i<=4;++i)
			for(int j=i+1;j<=4;++j)
			{
				for(int t=1;t<=n;++t)
					a[1][t]=b[i][t],a[2][t]=b[j][t];
				ans-=sub2::main(1);
			}
		m=3;
		for(int i=1;i<=4;++i)
			for(int j=i+1;j<=4;++j)
				for(int k=j+1;k<=4;++k)
				{
					for(int t=1;t<=n;++t)
						a[1][t]=b[i][t],a[2][t]=b[j][t],a[3][t]=b[k][t];
					ans+=sub3::main(1);
				}
		return ans;
	}
}
int main()
{
	scanf("%d%d",&m,&n);
	for(int i=1;i<=m;++i)
		for(int j=1;j<=n;++j)
			scanf("%d",a[i]+j);
	if(m==2)return printf("%lld\n",sub2::main()),0;
	if(m==3)return printf("%lld\n",sub3::main()),0;
	printf("%lld\n",sub4::main());
	return 0;
}
posted @ 2022-03-27 23:54  BILL666  阅读(294)  评论(0)    收藏  举报