“蔚来杯“2022牛客暑期多校训练营4 E: Jobs (Hard Version)

这篇博客介绍了如何处理一个复杂的问题,即在大量公司和工作需求的场景下,判断求职者能获得多少份工作offer。通过预处理三维属性的贡献,并利用二维问题的解决思路扩展到三维,实现三维前缀和来高效计算每个求职者的匹配公司数量。算法涉及到在线查询、前缀和、集合操作以及快速幂等技术。
摘要由CSDN通过智能技术生成

E题: Jobs (Hard Version)

原题链接:https://ac.nowcoder.com/acm/contest/33189/E

题目大意

n ( 1 ≤ n ≤ 1 0 6 ) n(1\le n\le 10^6) n(1n106) 家公司,第 i i i 家有 m i ( 1 ≤ m i ≤ 1 0 6 , ∑ m i ≤ 1 0 6 ) m_i(1\le m_i\le 10^6,\sum m_i\le 10^6) mi(1mi106,mi106) 个工作,每个工作对 I Q / E Q / A Q ( 1 ≤ a j , b j , c j ≤ 400 ) IQ/EQ/AQ(1\le a_j,b_j,c_j\le 400) IQ/EQ/AQ(1aj,bj,cj400) 三项属性各有下限要求。对于某个求职者,如果其满足一家公司的任意一项工作的三维属性要求,则该公司会给他发offer。

q ( 1 ≤ q ≤ 2 × 1 0 6 ) q(1\le q\le 2\times 10^6) q(1q2×106) 个求职者,对于每个求职者,求有多少家公司会给他发offer。

询问强制在线。

题解

观察到三维的范围都较小,不妨预处理出 d p x , y , z dp_{x,y,z} dpx,y,z 表示三维分别为 x , y , z x,y,z x,y,z 时可收到的offer数。

考虑一个较简单的版本,若只有二维怎么办。
当两个工作 i , j i,j i,j 满足 a i ≤ a j , b i ≤ b j a_i\le a_j,b_i\le b_j aiaj,bibj 时,工作 j j j i i i 包含,不用考虑。
将互不包含的工作所产生贡献的范围画出图像大致如下:
显然,所有的染色部分都应该做 1 1 1 的贡献(因为是同一公司)。
我们不妨进行差分,将每个橙色点 + 1 +1 +1 ,红色点 − 1 -1 1 ,然后横纵各做一次前缀和,即可得到我们想要的结果。

当问题升到三维时,我们可以按照第三维度从小到大枚举每个工作,然后转化为每层一个二维问题。当更上层的工作被包含时,我们不考虑他;如果不被包含,则将被他包含的那些原来的工作的贡献去除。最后做三维前缀和即可(先做第三维度)。
对于判断包含关系,可以将每个工作的前两个维度维护在一个 s e t set set 中,因为互不包含的工作在 a a a 单调增加时, b b b 单调递减,可以按照顺序进行判断。

参考代码

#include<bits/stdc++.h>
#include <random>
using namespace std;

template<class T>inline void read(T&x){
	char c,last=' ';
	while(!isdigit(c=getchar()))last=c;
	x=c^48;
	while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+(c^48);
	if(last=='-')x=-x;
}

#define P pair<int,int>
#define X first
#define Y second
const int MAXN=4e2+5,mod=998244353;
int n,q;
int a[MAXN][MAXN][MAXN];
vector<pair<int,P>>v;
set<P>st;

int qpow(int x,int p){// 快速幂(其实seed的幂也可以打表O(q)预处理)
	int ret=1;
	for(;p;x=1ll*x*x%mod,p>>=1)if(p&1)ret=1ll*ret*x%mod;
	return ret;
}

P Next(P p){//寻找后继用于判断是否包含
	return *st.upper_bound(p);
}

int main()
{
	read(n),read(q);
	for(int i=1,m;i<=n;++i){
		read(m);
		vector<pair<int,P>>().swap(v);
		for(int k,x,y;m--;){
			read(k),read(x),read(y);
			v.push_back(make_pair(k,P(x,y)));
		}
		sort(v.begin(),v.end());//先排序
		st.clear();
		st.insert(P(0,401));
		st.insert(P(401,0));//插入两个哨兵节点防止RE
		for(int i=0,k;i<v.size();++i){
			k=v[i].X;
			P p=v[i].Y;
			set<P>::iterator it=st.upper_bound(p);
			--it;//此时的it可能是p的前驱也可能与p相同
			if(it->Y<=p.Y)continue;//p被包含或存在相同
			++a[k][Next(*it).X][it->Y];//将原来的-1贡献消除
			st.insert(p);
			--a[k][p.X][it->Y];//p与前驱的交点-1(因为it不与p相同,则it一定是p的前驱)
			while(Next(p).Y>=p.Y){//包含后继
				P nxt=Next(p);
				--a[k][nxt.X][nxt.Y],++a[k][Next(nxt).X][nxt.Y];//消除后继的贡献
				st.erase(nxt);
			}
			++a[k][p.X][p.Y];//p的贡献
			--a[k][Next(p).X][p.Y];//p与后继的交点
		}
	}
	for(int k=1;k<=400;++k){
		for(int x=1;x<=400;++x){
			for(int y=1;y<=400;++y){
				a[k][x][y]+=a[k-1][x][y];//先处理额外的那个维度
			}
		}
	}
	for(int k=1;k<=400;++k){
		for(int x=1;x<=400;++x){
			for(int y=1;y<=400;++y){
				a[k][x][y]+=a[k][x-1][y];
			}
		}
	}
	for(int k=1;k<=400;++k){
		for(int x=1;x<=400;++x){
			for(int y=1;y<=400;++y){
				a[k][x][y]+=a[k][x][y-1];
			}
		}
	}
	int seed;read(seed);
	std::mt19937 rng(seed);
	std::uniform_int_distribution<>u(1,400);
	int lastans=0,ans=0;
	for(int i=1;i<=q;i++){
		int IQ=(u(rng)^lastans)%400+1;
		int EQ=(u(rng)^lastans)%400+1;
		int AQ=(u(rng)^lastans)%400+1;
		lastans=a[IQ][EQ][AQ];//查表即可
		ans=(ans+1ll*lastans*qpow(seed,q-i)%mod)%mod;
	}
	cout<<ans<<'\n';
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值