P8317 [FOI2021] 幸运区间
Description
一个抽奖活动正在进行。每个参加活动的人拿到了 \(n\) 个序列,每个序列包含了 \(d\) 个正整数,以及一个数字 \(k\),代表这些正整数中,存在 \(k\) 个幸运数字。
每个拿到序列的人,会从自己手中的序列中选出连续的若干个序列形成一个区间,称之为待选区间。如果待选区间中的每一个序列都包含至少一个幸运数字,则称该区间为幸运区间。当然幸运区间可能不止一个。游戏规定,其中包含的序列最多的即总长度最长的那个幸运区间称为超级幸运区间。
例如:\(d=2,k=3\) 时,序列如下:
- 序列 \(0\):
115 120
。 - 序列 \(1\):
50 80
。 - 序列 \(2\):
199 30
。 - 序列 \(3\):
40 40
。 - 序列 \(4\):
30 30
。 - 序列 \(5\):
25 40
。
从序列 \(0\) 到序列 \(2\) 的区间是幸运区间,因为从 \(0\) 到 \(2\) 中的每个序列都包含了 \(120,50\) 或 \(30\),共 \(3\) 个幸运数字。从序列 \(1\) 到序列 \(5\) 的区间也是幸运区间,因为 \(1\) 到 \(5\) 的所有序列都包含 \(80,30\) 或 \(40\),并且包含了 \(5\) 个序列,是总长度最大的超级幸运区间。
每个有序列的人都想知道自己的超级幸运区间是怎样的。编程任务就是对于每个拿到序列的人,输出总长度最大的超级幸运区间的第一个元素的下标和最后一个元素的下标。如果有多个长度一样的,输出第一个元素下标最小的。请注意下标从 \(0\) 开始。
对于 \(100\%\) 的数据,\(2\le k\le 3\)。
\(T=10,1\le d\le 4,1\le\) 每个序列中的数字 \(\le10^5\)。
对于最多 \(6\) 个 \(\text{case}\),\(1\le n\le 10^5\),对于其他所有的 \(\text{case}\),\(1\le n\le 10^3\)。
Solution
读题,注意到 \(d\) 和 \(k\) 都很小,这很有可能是突破口。
由于 \(d,k\) 很小,考虑直接搜索求解。
一个朴素的搜索是枚举区间,然后在区间内暴力判断是否合法。时间复杂度 \(\mathcal O(Tn^2d^{k+1}k)\)(\(\mathcal O(n^2)\) 枚举,\(\mathcal O(d^{k+1}k)\) 判断)。
简单地计算了一下发现是过不去的,考虑对其优化。
后面的搜索判断已经很难继续优化了,于是优化枚举区间。联想到 cdq 分治,于是将循环枚举区间改为分治区间。
具体来说,每次处理区间 \([l,r]\) 中的答案,其由三部分得到:\([l,mid-1],[mid+1,r]\) 和一定选择 \(mid\) 的答案。
前面两个可以继续分治下去,第三个直接暴力求解。
注意这里的暴力是向两边扩展,每次先找到无需添加幸运数字的最大区间,然后添加一个幸运数字,继续向下递归。
Code
#include<cstdio>
#include<cstring>
#define N 100005
using namespace std;
int T,n,d,m,mx,mxl,mxr,num,a[N][5];
bool t[N];
void dg(int ll,int rr,int l,int r)
{
while (l>ll)
{
bool bj=false;
for (int i=1;i<=d;++i)
bj|=t[a[l-1][i]];
if (bj) --l;
else break;
}
while (r<rr)
{
bool bj=false;
for (int i=1;i<=d;++i)
bj|=t[a[r+1][i]];
if (bj) ++r;
else break;
}
if ((mxr-mxl+1<r-l+1)||(mxr-mxl+1==r-l+1&&mxl>l)) mxl=l,mxr=r;
if (num==m) return;
if (l>ll)
{
for (int i=1;i<=d;++i)
{
++num;
t[a[l-1][i]]=true;
dg(ll,rr,l-1,r);
t[a[l-1][i]]=false;
--num;
}
}
if (r<rr)
{
for (int i=1;i<=d;++i)
{
++num;
t[a[r+1][i]]=true;
dg(ll,rr,l,r+1);
t[a[r+1][i]]=false;
--num;
}
}
}
void solve(int l,int r)
{
if (l==r) return;
int mid=(l+r)>>1;
if (l<mid) solve(l,mid-1);
if (r>mid) solve(mid+1,r);
num=1;
for (int i=1;i<=d;++i)
{
t[a[mid][i]]=true;
dg(l,r,mid,mid);
t[a[mid][i]]=false;
}
}
int main()
{
scanf("%d",&T);
for (int nowturn=1;nowturn<=T;++nowturn)
{
mxl=mxr=1;memset(t,false,sizeof(t));
scanf("%d%d%d",&n,&d,&m);
for (int i=1;i<=n;++i)
for (int j=1;j<=d;++j)
scanf("%d",&a[i][j]);
solve(1,n);
printf("Case #%d: %d %d\n",nowturn,mxl-1,mxr-1);
}
return 0;
}