• 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;
    }
    
  • 相关阅读:
    TortoiseSVN是windows平台下Subversion的免费开源客户端。
    Lotus Sametime 服务器的安装和配置
    群件
    Lotus Sametime
    软件项目经理
    PMP考试
    IT项目经理
    什么是模式呢?
    上下文(context):相关的内容
    设计模式-四要素
  • 原文地址:https://www.cnblogs.com/zmyzmy/p/16065268.html
Copyright © 2020-2023  润新知