AtCoder Snuke21 J. Drink Bar - 分段得分题解

这里将每一个三元组 \((a_i,b_i,c_i)\) 称为一组数。


Subtask 1

暴力枚举所有的非空子集即可。

枚举方式可以采用类似状压 DP 的二进制枚举或者直接 DFS。

时间复杂度 \(O(N \times 2^N)\)


Subtask 2

性质:此时的特征值最多由两个有效组组成,原因可见 Subtask 3。

因为 \(a_i=b_i\),所以三元组 \((a_i,b_i,c_i)\) 实际上可以压缩为二元组 \((a_i,c_i)\),对其进行二维偏序。

我这里采用的是排序后树状数组的写法,具体地说:

将所有二元组按照 \(a\) 排序后扫描所有二元组,可以保证对于当前二元组 \(i\) ,之前组 \(j\)\(a_j\) 一定小于 \(a_i\)

此时如果之前某二元组 \(j\)\(c_j\) 比当前的 \(c_i\) 大,那么这两个二元组就可以合成一个特征值 \((a_i,c_j)\),组 \(i\) 贡献了 \(a_i\),组 \(j\) 贡献了 \(c_j\)

如此,我们构造权值树状数组,每次找当前 \(c_i\)后缀和就是之前 \(c_j>c_i\)\(j\) 的数量。

注意,我代码里面是先加入 \(c_i\) 再查找,并且查找的是 \(c_j \ge c_i\)\(j\) 的数量,这是为了确保 \(j\) 能够取到 \(i\),表示自己与自己组合,即自己单独构成一个特征值。

时间复杂度 \(O(N \log N)\)

点击查看代码
namespace BinaryIndexTree{

struct BIT_R{
	int c[N];
	#define lowbit(x) ((x)&-(x))
	void clear()
	{
		for(int i=1;i<=n;i++)
			c[i]=0;
		return;
	}
	void add(int x,int y)
	{
		for(;x;x-=lowbit(x))
			c[x]+=y;
		return;
	}
	int query(int x)
	{
		long long res=0;
		for(;x<=n;x+=lowbit(x))
			res+=c[x];
		return res;
	}
	#undef lowbit
}; //反向树状数组(后缀和)

} //namespace BinaryIndexTree

namespace Subtask_2{

pair<int,int> p[N];
void Prework()
{
	for(int i=1;i<=n;i++)
		p[i]={a[i],c[i]}; //a[i]=b[i]
	sort(p+1,p+n+1);
	return;
}

BinaryIndexTree::BIT_R bit;
void Solve()
{
	Prework();
	long long ans=0;
	for(int i=1;i<=n;i++)
	{
		bit.add(p[i].second,1);
		ans+=bit.query(p[i].second);
	}
	printf("%lld\n",ans);
	bit.clear();
	return;
}

} //namespace Subtask_2

Subtask 3

首先,一个特征值包括三个最大值,其组成有以下三种情况:

  1. 一组数贡献了所有的最大值,即它的 \(a_i,b_i,c_i\) 都是最大的,此时其它的组可有可无,可以忽略。
  2. 某一组数贡献了两个最大值,另一组数贡献了剩下的一个最大值,此时其他组可有可无,可以忽略。
  3. 三个最大值分别由三组构成,剩下的组可有可无,可以忽略。

综上所述,构成一个特征值有效的三元组最多只有三个,直接 \(O(N^3)\) 枚举这三/二/一个三元组即可。

点击查看代码
namespace Subtask_3{

const int N_3=505;
bitset<N_1_3> flag[N_1_3][N_1_3];

void ClearData()
{
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			flag[i][j]&=0;
	return;
}

void Solve()
{
	int ans=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			for(int k=1;k<=j;k++)
			{
				int x=max({a[i],a[j],a[k]});
				int y=max({b[i],b[j],b[k]});
				int z=max({c[i],c[j],c[k]});
				if(!flag[x][y][z])
				{
					ans++;
					flag[x][y][z]=true;
				}
			}
	printf("%d\n",ans);
	ClearData();
	return;
}

}

Subtask 4

根据 Subtask 3 中的结论,可以将选择情况分成三类:选择一个组、选择两个组、选择三个组。

选一个或两个组

先来说选择一个组和选择两个组的小常数做法。

还是先将所有组按照 \(a\) 值排序,设当前组为 \(i\),之前组为 \(j\)

因为已经排过序,所以 \(a_j<a_i\),接下来考虑什么情况下两者可以合成一组特征值。

如果 \(b_j<b_i\)\(c_j<c_i\),那么实际上两者组合后 \(i\) 贡献了全部的数,\(j\) 可以忽略,此时相当于是只选择了一个组。

反之,只要 \(b_j>b_i\) \(c_j>c_i\) 那么两者组合后,\(j\) 就会有贡献,此时这是一个新的特征值。

注意,我代码中枚举时 \(j \le i\),且 if 中判读那条件为大于等于是为了让 \(j\) 能够取到 \(i\),此时相当于计算了只选 \(i\) 的情况(但是只计算了一次)。

点击查看代码
for(int i=1;i<=n;i++) //选择一个或两个组
		for(int j=1;j<=i;j++)
			if(p[j].y>=p[i].y||p[j].z>=p[i].z) ans++;

选三个组

接下来考虑选了三个组的情况。

选择三个组,必然是一个组贡献了一个最大值,这里设 \(i\) 贡献了 \(a\) 的最大值,\(j\) 贡献了 \(b\) 的最大值,\(k\) 贡献了 \(c\) 的最大值。

上面已经将所有组按照 \(a\) 排过序了,可以只要找 \(j<i,k<i\),那么一定有 \(a_i>a_j,a_k\),保证了 \(i\) 贡献 \(a\) 的最大值。

如何让 \(j\) 贡献 \(b\) 的最大值?因为所有的 \(j<i\) 都是合法的,所以将这 \(i-1\) 个组单独拿出来,按照 \(b\) 排序后再依次扫描,就可以保证对于当前组 \(j\),如果 \(k<j\),那么 \(b_k<b_j\)。当然,还要另行判断 \(b_j>b_i\),才能保证 \(j\) 贡献了 \(b\) 的最大值,可以计入答案;否则,\(b_i>b_j\)\(b_j>b_k\)\(i\) 同时贡献了 \(a\)\(b\) 的最大值,就退化为了只选两组的情况。

而我们要做的,就是在此时合法的 \(k\)(即小于 \(j\)\(k\))中,找到使 \(c_k\) 最大的 \(k\) 的数量,即 \(c_k > \max\{c_i,c_j\}\)\(k\) 的数量。

该问题与 Subtask 2 中的问题相似,同样可以采用后缀和权值树状数组解决,具体地说:

  • 在遍历 \(j\) 的时候,先判断 \(b_j\) 是否可以贡献最大值(\(b_j>b_k\) 已经由排序保证,只需判断 \(b_j\) 是否大于 \(b_i\)),如果可以,将 \(\max\{c_i,c_j\}\) 的后缀和累加到答案里。
  • \(c_j\) 加入到树状数组当中。

时间复杂度 \(O(N^2 \log N)\)

具体实现可看代码注释。

点击查看代码

namespace BinaryIndexTree{

struct BIT_R{
	int c[N];
	#define lowbit(x) ((x)&-(x))
	void clear()
	{
		for(int i=1;i<=n;i++)
			c[i]=0;
		return;
	}
	void add(int x,int y)
	{
		for(;x;x-=lowbit(x))
			c[x]+=y;
		return;
	}
	int query(int x)
	{
		long long res=0;
		for(;x<=n;x+=lowbit(x))
			res+=c[x];
		return res;
	}
	#undef lowbit
}; //后缀和树状数组 

} //namespace BinaryIndexTree

namespace Subtask_4{

struct MJJ{
	int x,y,z;
}p[N];

bool cmp(MJJ x,MJJ y)
{
	return x.x<y.x;
}
void Prework()
{
	for(int i=1;i<=n;i++)
		p[i]={a[i],b[i],c[i]};
	sort(p+1,p+n+1,cmp); //先将所有数按照a值排序 
	return;
}

BinaryIndexTree::BIT_R bit; //后缀和树状数组 
pair<int,int> tmp[N]; //用以排序 
void Solve()
{
	Prework();
	long long ans=0;
	
	for(int i=1;i<=n;i++) //选择一个或两个组 
		for(int j=1;j<=i;j++)
			if(p[j].y>=p[i].y||p[j].z>=p[i].z) ans++;
	
	for(int i=1;i<=n;i++) //选择三个组 
	{
		for(int j=1;j<i;j++) //此时已经保证a[i]>a[j]且a[i]>a[k],所以不用再考虑a 
			tmp[j]={p[j].y,p[j].z}; //将所有在i之前的b[j]和c[j]复制一份,用以排序
		//tmp的first和second分别表示b[j]和c[j],a[j]不用考虑 
		//tmp的长度为(i-1)
		sort(tmp+1,tmp+(i-1)+1); //排序以保证从前往后扫描j的时候,前面的b小于后面的b 
		bit.clear();
		for(int j=1;j<i;j++)
		{
			//因为从前向后扫描已经保证b[k]<b[j],所以这里只需要判断b[i]<b[j]即可证明b[j]合法 
			if(tmp[j].first>p[i].y) //保证b[j]贡献了最大值 
				ans+=bit.query(max(p[i].z,tmp[j].second)); //保证c[k]>c[i]且c[k]>c[j]的k的数量
			//上面其实改成query(max(...)+1)更准确,不过因为c值互不相同,所以这样写也对 
			bit.add(tmp[j].second,1); //将当前c[j]加入到后缀和树状数组当中 
		}
	}
	printf("%lld\n",ans);
	return;
}

} //namespace Subtask_4

Subtask 5

这部分其实是上面的代码卡常优化卡过的😅。

因为所有的 \(b\) 是一个排列,所以可以把上面的快排优化为桶排。

主要就是这个,可以优化近一半的时间,这样就可以卡过 Subtask 5 了。

和上面区别不大,就只贴核心代码(选择三个组的代码)了。

点击查看代码
	for(int i=1;i<=n;i++) //选择三个组 
	{
		for(int j=1;j<i;j++) //p[j].a<p[i].a
			toilet[p[j].y]=p[j].z;
		int tmp_idx=0;
		for(int j=1;j<=n;j++)
			if(toilet[j]) tmp[++tmp_idx]={j,toilet[j]};
		for(int j=1;j<=n;j++) toilet[j]=0;
		bit.clear();
		for(int j=1;j<=tmp_idx;j++)
		{
			if(tmp[j].first>p[i].y)
				ans+=bit.query(max(p[i].z,tmp[j].second));
			bit.add(tmp[j].second,1);
		}
	}

总:70 分代码。

其实只用 Subtask 2 和 Subtask 5 的就够了,不过还是把刚才的都放上来吧。

#include<cstdio>
#include<bitset>
#include<algorithm>
using namespace std;

const int N=2e5+5;
int n,a[N],b[N],c[N];

namespace BinaryIndexTree{

struct BIT_R{
	int c[N];
	#define lowbit(x) ((x)&-(x))
	void clear()
	{
		for(int i=1;i<=n;i++)
			c[i]=0;
		return;
	}
	void add(int x,int y)
	{
		for(;x;x-=lowbit(x))
			c[x]+=y;
		return;
	}
	int query(int x)
	{
		long long res=0;
		for(;x<=n;x+=lowbit(x))
			res+=c[x];
		return res;
	}
	#undef lowbit
};

} //namespace BinaryIndexTree

namespace Subtask_2{

pair<int,int> p[N];
void Prework()
{
	for(int i=1;i<=n;i++)
		p[i]={a[i],c[i]}; //a[i]=b[i]
	sort(p+1,p+n+1);
	return;
}

BinaryIndexTree::BIT_R bit;
void Solve()
{
	Prework();
	long long ans=0;
	for(int i=1;i<=n;i++)
	{
		bit.add(p[i].second,1);
		ans+=bit.query(p[i].second);
	}
	printf("%lld\n",ans);
	bit.clear();
	return;
}

} //namespace Subtask_2

namespace Subtask_3{

const int N_3=505;
bitset<N_3> flag[N_3][N_3];

void ClearData()
{
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			flag[i][j]&=0;
	return;
}

void Solve()
{
	int ans=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			for(int k=1;k<=j;k++)
			{
				int x=max({a[i],a[j],a[k]});
				int y=max({b[i],b[j],b[k]});
				int z=max({c[i],c[j],c[k]});
				if(!flag[x][y][z])
				{
					ans++;
					flag[x][y][z]=true;
				}
			}
	printf("%d\n",ans);
	ClearData();
	return;
}

} //namespace Subtask_3 

namespace Subtask_4{

struct MJJ{
	int x,y,z;
}p[N];

bool cmp(MJJ x,MJJ y)
{
	return x.x<y.x;
}
void Prework()
{
	for(int i=1;i<=n;i++)
		p[i]={a[i],b[i],c[i]};
	sort(p+1,p+n+1,cmp);
	return;
}

BinaryIndexTree::BIT_R bit;
pair<int,int> tmp[N];
void Solve()
{
	Prework();
	long long ans=0;
	
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			if(p[j].y>=p[i].y||p[j].z>=p[i].z) ans++;
	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<i;j++)
			tmp[j]={p[j].y,p[j].z};
		sort(tmp+1,tmp+(i-1)+1);
		bit.clear();
		for(int j=1;j<i;j++)
		{
			if(tmp[j].first>p[i].y)
				ans+=bit.query(max(p[i].z,tmp[j].second));
			bit.add(tmp[j].second,1);
		}
	}
	printf("%lld\n",ans);
	return;
}

} //namespace Subtask_4

namespace Subtask_5{

struct MJJ{
	int x,y,z;
}p[N];

bool cmp(MJJ x,MJJ y)
{
	return x.x<y.x;
}
void Prework()
{
	for(int i=1;i<=n;i++)
		p[i]={a[i],b[i],c[i]};
	sort(p+1,p+n+1,cmp);
	return;
}

BinaryIndexTree::BIT_R bit;
pair<int,int> tmp[N];
int toilet[N];
int tmp_idx;
void Solve()
{
	Prework();
	long long ans=0;
	
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			if(p[j].y>=p[i].y||p[j].z>=p[i].z) ans++;
	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<i;j++)
			toilet[p[j].y]=p[j].z;
		int tmp_idx=0;
		for(int j=1;j<=n;j++)
			if(toilet[j]) tmp[++tmp_idx]={j,toilet[j]};
		for(int j=1;j<=n;j++) toilet[j]=0;
		bit.clear();
		for(int j=1;j<=tmp_idx;j++)
		{
			if(tmp[j].first>p[i].y)
				ans+=bit.query(max(p[i].z,tmp[j].second));
			bit.add(tmp[j].second,1);
		}
	}
	printf("%lld\n",ans);
	return;
}

} //namespace Subtask_5

int main()
{
//	freopen("max.in","r",stdin);
//	freopen("max.out","w",stdout);
	
	int T; scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		bool is_sub2=true;
		for(int i=1;i<=n;i++)
		{
			scanf("%d%d%d",&a[i],&b[i],&c[i]);
			if(a[i]!=b[i]) is_sub2=false;
		}
		if(is_sub2) Subtask_2::Solve();
		else if(n<=100) Subtask_3::Solve();
		else if(n<=500) Subtask_4::Solve();
		else if(n<=2000) Subtask_5::Solve();
		else printf("Oh no!\n");
	}
	return 0;
}

补充内容-正解代码

点击查看代码
#include<cstdio>
#include<bitset>
#include<algorithm>
using namespace std;

const int N=2e5+5;
int n,a[N],b[N],c[N];

namespace BinaryIndexTree{

struct BIT{
	int c[N];
	#define lowbit(x) ((x)&-(x))
	void clear()
	{
		for(int i=1;i<=n;i++)
			c[i]=0;
		return;
	}
	void add(int x,int y)
	{
		for(;x<=n;x+=lowbit(x))
			c[x]+=y;
		return;
	}
	int query(int x)
	{
		int res=0;
		for(;x;x-=lowbit(x))
			res+=c[x];
		return res;
	}
	#undef lowbit
};

struct BIT_R{
	int c[N];
	#define lowbit(x) ((x)&-(x))
	void clear()
	{
		for(int i=1;i<=n;i++)
			c[i]=0;
		return;
	}
	void add(int x,int y)
	{
		for(;x;x-=lowbit(x))
			c[x]+=y;
		return;
	}
	int query(int x)
	{
		int res=0;
		for(;x<=n;x+=lowbit(x))
			res+=c[x];
		return res;
	}
	#undef lowbit
};

} //namespace BinaryIndexTree

namespace Subtask_2{

pair<int,int> p[N];
void Prework()
{
	for(int i=1;i<=n;i++)
		p[i]={a[i],c[i]}; //a[i]=b[i]
	sort(p+1,p+n+1);
	return;
}

BinaryIndexTree::BIT_R bit;
void Solve()
{
	Prework();
	long long ans=0;
	for(int i=1;i<=n;i++)
	{
		bit.add(p[i].second,1);
		ans+=bit.query(p[i].second);
	}
	printf("%lld\n",ans);
	bit.clear();
	return;
}

} //namespace Subtask_2

namespace Subtask_3{

const int N_3=505;
bitset<N_3> flag[N_3][N_3];

void ClearData()
{
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			flag[i][j]&=0;
	return;
}

void Solve()
{
	int ans=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			for(int k=1;k<=j;k++)
			{
				int x=max({a[i],a[j],a[k]});
				int y=max({b[i],b[j],b[k]});
				int z=max({c[i],c[j],c[k]});
				if(!flag[x][y][z])
				{
					ans++;
					flag[x][y][z]=true;
				}
			}
	printf("%d\n",ans);
	ClearData();
	return;
}

} //namespace Subtask_3 

namespace Subtask_4{

struct MJJ{
	int x,y,z;
}p[N];

bool cmp(MJJ x,MJJ y)
{
	return x.x<y.x;
}
void Prework()
{
	for(int i=1;i<=n;i++)
		p[i]={a[i],b[i],c[i]};
	sort(p+1,p+n+1,cmp);
	return;
}

BinaryIndexTree::BIT_R bit;
pair<int,int> tmp[N];
void Solve()
{
	Prework();
	long long ans=0;
	
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			if(p[j].y>=p[i].y||p[j].z>=p[i].z) ans++;
	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<i;j++)
			tmp[j]={p[j].y,p[j].z};
		sort(tmp+1,tmp+(i-1)+1);
		bit.clear();
		for(int j=1;j<i;j++)
		{
			if(tmp[j].first>p[i].y)
				ans+=bit.query(max(p[i].z,tmp[j].second));
			bit.add(tmp[j].second,1);
		}
	}
	printf("%lld\n",ans);
	return;
}

} //namespace Subtask_4

namespace Subtask_5{

struct MJJ{
	int x,y,z;
}p[N];

bool cmp(MJJ x,MJJ y)
{
	return x.x<y.x;
}
void Prework()
{
	for(int i=1;i<=n;i++)
		p[i]={a[i],b[i],c[i]};
	sort(p+1,p+n+1,cmp);
	return;
}

BinaryIndexTree::BIT_R bit;
pair<int,int> tmp[N];
int toilet[N];
int tmp_idx;
void Solve()
{
	Prework();
	long long ans=0;
	
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			if(p[j].y>=p[i].y||p[j].z>=p[i].z) ans++;
	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<i;j++)
			toilet[p[j].y]=p[j].z;
		int tmp_idx=0;
		for(int j=1;j<=n;j++)
			if(toilet[j]) tmp[++tmp_idx]={j,toilet[j]};
		for(int j=1;j<=n;j++) toilet[j]=0;
		bit.clear();
		for(int j=1;j<=tmp_idx;j++)
		{
			if(tmp[j].first>p[i].y)
				ans+=bit.query(max(p[i].z,tmp[j].second));
			bit.add(tmp[j].second,1);
		}
	}
	printf("%lld\n",ans);
	return;
}

} //namespace Subtask_5

namespace Subtask_6{

namespace PartialOrder_3{

struct LZX{
	int x,y,z;
	int id;
}p[N];

bool cmp_x(LZX x,LZX y)
{
	if(x.x!=y.x) return x.x<y.x;
	else if(x.y!=y.y) return x.y<y.y;
	else return x.z<y.z;
}
void Prework()
{
	for(int i=1;i<=n;i++)
		p[i]={a[i],b[i],c[i],i};
	sort(p+1,p+n+1,cmp_x);
	return;
}

BinaryIndexTree::BIT bit;
bool cmp_y(LZX x,LZX y)
{
	if(x.y!=y.y) return x.y<y.y;
	else return x.z<y.z;
}
int ans[N];
void CDQ(int l,int r)
{
	if(l==r) return;
	int mid=l+r>>1;
	CDQ(l,mid),CDQ(mid+1,r);
	sort(p+l,p+mid+1,cmp_y);
	sort(p+mid+1,p+r+1,cmp_y);
	int i=mid+1,j=l;
	for(;i<=r;i++)
	{
		for(;p[j].y<=p[i].y && j<=mid;j++)
			bit.add(p[j].z,1);
		ans[p[i].id]+=bit.query(p[i].z);
	}
	for(i=l;i<j;i++)
		bit.add(p[i].z,-1);
	return;
}

}

namespace PartialOrder_2{

pair<int,int> p[N];
BinaryIndexTree::BIT bit;
long long PO2(const int g[],const int h[])
{
	bit.clear();
	for(int i=1;i<=n;i++)
		p[i]={g[i],h[i]};
	sort(p+1,p+n+1);
	long long res=0;
	for(int i=1;i<=n;i++)
	{
		long long tmp=bit.query(p[i].second);
		res+=tmp*(tmp-1)/2;
		bit.add(p[i].second,1);
	}
	return res;
}

}

void Solve()
{
	PartialOrder_3::Prework();
	PartialOrder_3::CDQ(1,n);
	long long po3=0;
	for(int i=1;i<=n;i++)
		po3+=PartialOrder_3::ans[i];
	
	long long po2_ab=PartialOrder_2::PO2(a,b);
	long long po2_bc=PartialOrder_2::PO2(b,c);
	long long po2_ac=PartialOrder_2::PO2(a,c);
	
	long long po3_cpl=0;
	for(int i=1;i<=n;i++)
	{
		int tmp=PartialOrder_3::ans[i];
		po3_cpl+=1ll*tmp*(tmp-1)/2;
	}
	
	long long ans=0;
	ans += 1ll*n;
	ans += 1ll*n*(n-1)/2 - po3;
	ans += 1ll*n*(n-1)*(n-2)/6 - (po2_ab+po2_bc+po2_ac) + po3_cpl*2;
	printf("%lld\n",ans);
	
	for(int i=1;i<=n;i++)
		PartialOrder_3::ans[i]=0;
	return;
}

}

int main()
{
	scanf("%d",&n);
	bool is_sub2=true;
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d%d",&a[i],&b[i],&c[i]);
		if(a[i]!=b[i]) is_sub2=false;
	}
	if(is_sub2) Subtask_2::Solve();
	else if(n<=100) Subtask_3::Solve();
	else if(n<=500) Subtask_4::Solve();
	else if(n<=2000) Subtask_5::Solve();
	else Subtask_6::Solve();
	return 0;
}
posted @ 2024-10-24 09:52  Jerrycyx  阅读(12)  评论(0编辑  收藏  举报