Jobs (Hard Version)
昨晚 学弟来问我这道题,就浅浅理A了一下
题意:
给定
n
=
1
e
6
n=1e6
n=1e6 个公司,
q
=
2
e
6
q=2e6
q=2e6个人,每个公司有
m
i
m_i
mi个岗位,且
∑
i
=
1
n
m
i
=
1
e
6
\sum_{i=1}^{n}{m_i}=1e6
∑i=1nmi=1e6,每个岗位有
A
,
B
,
C
A,B,C
A,B,C 三个属性值,每个人有
a
,
b
,
c
a,b,c
a,b,c 三个属性值。一个人能够胜任这个岗位,当且仅当
A
<
=
a
A<=a
A<=a &&
B
<
=
b
B<=b
B<=b &&
C
<
=
c
C<=c
C<=c (
A
,
a
<
=
400
,
B
,
b
<
=
400
,
C
,
c
<
=
400
A,a<=400,B,b<=400,C,c<=400
A,a<=400,B,b<=400,C,c<=400)。试求,对于每个人,他能在几个公司任职。(学弟给的题意,如果错了,请当这篇博客不存在
思路:
如果没有公司的限制,只是要求 每个人能够在几个岗位任职,就是简单的 三维偏序问题,直接
C
D
Q
CDQ
CDQ 即可,但是有公司的限制,就和种类数类似了,不能考虑
C
D
Q
CDQ
CDQ 了。
但是可以借鉴 C D Q CDQ CDQ 的一些思路,比如,我们可以将所有岗位的 A , B , C A,B,C A,B,C 和 人的 a , b , c a,b,c a,b,c 放在一起,按照 a , A a,A a,A 的大小排序,那么 对于每个人,排在他前面的岗位 就是都满足 A A A 的条件的(也即 像 C D Q CDQ CDQ 一样,通过排序降一维)。
这样处理之后,题目就转化成了,序列上 的 pair 对(B,C),对于每个人 j j j,需要求 有多少个公司,满足存在岗位 i, i < j i<j i<j && B i < = B j B_i<=B_j Bi<=Bj && C i < = C j C_i<=C_j Ci<=Cj 。因为求的是种类数,将同种类的岗位放在一起看,可以发现,对于两个不同的岗位 ( B , C ) (B,C) (B,C) 和 ( b , c ) (b,c) (b,c) 如果 B < = b B<=b B<=b 且 C < = c C<=c C<=c ,那么 ( b , c ) (b,c) (b,c) 是被 ( B , C ) (B,C) (B,C) 包含在内的,可以去掉。也即对于同种类的岗位,有效的岗位是一个 B B B升序, C C C降序的一个单调序列。发现了这个性质,那么离做出来也就不远了。
然后,我们看对于每个人
j
j
j,我们需要求有多少个公司,满足存在岗位 i,
i
<
j
i<j
i<j &&
B
i
<
=
B
j
B_i<=B_j
Bi<=Bj &&
C
i
<
=
C
j
C_i<=C_j
Ci<=Cj ,把
(
B
,
C
)
(B,C)
(B,C) 看成是一个平面,也就是求一个点的左下角有多少种类的点。我们可以维护一个 二维树状数组
t
r
tr
tr,
t
r
[
x
]
[
y
]
tr[x][y]
tr[x][y] 代表
(
x
,
y
)
(x,y)
(x,y) 的左下角的种类数;那么接下来就是怎么维护种类数了,对于每个公司维护一个单调序列,存
p
a
i
r
pair
pair 的值,那么每次新进来一个
p
a
i
r
pair
pair,可能会让对应单调序列 加入一个点 或者 删除一些点,当然不可能
f
o
r
for
for 单调序列,可以二分出这个
p
a
i
r
pair
pair 要插入的位置。然后插入一个点或者删除一个点就在 二维树状数组上进行加减,当然不是在当前点的所有右上角进行加减,而是需要求出当前这个点和 单调序列的 前驱、后继 夹出来的特有矩形,在这上面执行操作。这样就能满足 对于一个特定的公司,对于二维平面上每个点 最多只贡献了 1。
对于一个人只需要在全局的二维树状数组 查询一个给定点的值即可。
(懒得画图了,有心人可以自己结合上文 画图理解一下)
复杂度分析:
令
M
=
∑
i
=
1
n
m
i
M=\sum_{i=1}^{n}{m_i}
M=∑i=1nmi;
对于每一个岗位都需要在单调序列中二分一下,因为
B
<
=
400
B<=400
B<=400,所以单调序列中最多只有 400个点,所以这部分的复杂度是
M
⋅
l
o
g
2
400
M·log_2{400}
M⋅log2400;
对于每个人,都需要在二维树状数组上查询一次值,这部分复杂度是
q
⋅
l
o
g
2
400
⋅
l
o
g
2
400
q·log_2{400}·log_2{400}
q⋅log2400⋅log2400。
对于每个岗位在 单调序列上最多只会插入一次、删除一次,所以这部分复杂度是
M
⋅
l
o
g
2
400
⋅
l
o
g
2
400
M·log_2{400}·log_2{400}
M⋅log2400⋅log2400
可以发现复杂度是
O
(
能过
)
O(能过)
O(能过) 的,甚至绰绰有余吧。
代码:
因为笔者退役了,代码需要读者自行完成。
当然上面只是口头理A,还有许多细节需要读者自己思考,比如:
因为等式是
<
=
<=
<= 的,所以排序之后需要 按照
B
B
B 值相等的分组, 先把所有的岗位都插入了,再去求答案。
(如果学弟 写出来了,再把代码贴着吧)
(op: 学弟终于把题补了)
#include<bits/stdc++.h>
#include<random>
using namespace std;
#define ll long long
//#define int ll
constexpr int P=998244353;
int norm(int x){if(x<0)x+=P;if(x>=P)x-=P;return x;}
template<class T>
T power(T a,ll n){T ans=1;while(n){if(n&1)ans=ans*a;a=a*a;n>>=1;}return ans;}
struct Z
{
int x;
Z(int x=0):x(norm(x)){}
Z(ll x):x(norm(x%P)){}
int val() const{return x;}
Z operator-() const{return Z(norm(P-x));}
Z inv() const{assert(x!=0);return power(*this,P-2);}
Z&operator*=(const Z&rhs){x=(ll)x*rhs.x%P;return *this;}
Z&operator+=(const Z&rhs){x=norm(x+rhs.x);return *this;}
Z&operator-=(const Z&rhs){x=norm(x-rhs.x);return *this;}
Z&operator/=(const Z&rhs){return *this*=rhs.inv();}
friend Z operator*(const Z&lhs,const Z&rhs){Z res=lhs;res*=rhs;return res;}
friend Z operator+(const Z&lhs,const Z&rhs){Z res=lhs;res+=rhs;return res;}
friend Z operator-(const Z&lhs,const Z&rhs){Z res=lhs;res-=rhs;return res;}
friend Z operator/(const Z&lhs,const Z&rhs){Z res=lhs;res/=rhs;return res;}
friend istream&operator>>(istream&is,Z&a){ll v;is>>v;a=Z(v);return is;}
friend ostream&operator<<(ostream&os,const Z&a){return os<<a.val();}
};
void solve()
{
int n,q;
scanf("%d%d",&n,&q);
vector f(405,vector(405,vector<int>(405)));
for(int i=1;i<=n;i++)
{
int m;
scanf("%d",&m);
vector<array<int,3>>v(m);
for(int j=0;j<m;j++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
v[j]={a,b,c};
}
sort(v.begin(),v.end());
map<int,int>s;
for(auto [a,b,c]:v)
{
auto it=s.upper_bound(b);
if(it!=s.begin()&&prev(it)->second<=c)continue;
it=s.lower_bound(b);
if(it!=s.begin()&&it!=s.end())
f[a][it->first][prev(it)->second]++;
while(it!=s.end()&&it->second>=c)
{
f[a][it->first][it->second]--;
if(next(it)!=s.end())
f[a][next(it)->first][it->second]++;
it=s.erase(it);
}
auto now=s.emplace(b,c).first;
f[a][b][c]++;
if(it!=s.end())f[a][it->first][c]--;
if(now!=s.begin())f[a][b][prev(now)->second]--;
}
}
for(int i=2;i<=400;i++)
for(int j=1;j<=400;j++)
for(int k=1;k<=400;k++)
f[i][j][k]+=f[i-1][j][k];
for(int i=1;i<=400;i++)
for(int j=2;j<=400;j++)
for(int k=1;k<=400;k++)
f[i][j][k]+=f[i][j-1][k];
for(int i=1;i<=400;i++)
for(int j=1;j<=400;j++)
for(int k=2;k<=400;k++)
f[i][j][k]+=f[i][j][k-1];
int seed;
scanf("%d",&seed);
mt19937 rng(seed);
uniform_int_distribution<> u(1,400);
int lastans=0;
Z ans=0;
for(int i=1;i<=q;i++)
{
int IQ=(u(rng)^lastans)%400+1; // The IQ of the i-th friend
int EQ=(u(rng)^lastans)%400+1; // The EQ of the i-th friend
int AQ=(u(rng)^lastans)%400+1; // The AQ of the i-th friend
lastans=f[IQ][EQ][AQ]; // The answer to the i-th friend
ans=ans*seed+Z(lastans);
}
printf("%d\n",ans);
}
signed main()
{
// ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
// int _;scanf("%d",&_);while(_--)
solve();
return 0;
}
碎碎:
昨天晚上理A之后给学弟讲完,也算是感概万千,可能如果不退役,自己也能成为一名出色的金牌选手。
这几天,也不知道自己在纠结什么,不知道是后悔还是不甘,也不知道自己喜欢acm是因为 做出题的愉悦,还是因为自己打的还算不错的底子,抑或是这两年在集训队的点滴。
须知少时凌云志 曾许人间第一流