【传送门】F :Groundhog Looking Dowdy
3 / 10 3/10 3/10
稍微想一想就能想到的尺取题
一共有 n n n 天,每天有 k i k_i ki 条衣服,每条衣服有美丽值 a i , j a_{i,j} ai,j
你需要选择其中的 m m m 天,每天选择当天的一条衣服,使得选择出的 m m m 条衣服的魅力值的最大差值最小
1 ≤ a i , j ≤ 1 0 9 1\le a_{i,j} \le 10^9 1≤ai,j≤109
1 ≤ n ≤ 1 0 6 1\le n \le 10^6 1≤n≤106
1 ≤ m ≤ n 1\le m \le n 1≤m≤n
∑ n k i i = 1 ≤ 2 ⋅ 1 0 6 \underset{i=1\,\,\,\,\,}{\overset{n}{\sum}\,k_i}\le2 \,·\,10^6 i=1∑nki≤2⋅106
n m n\ m n m
k 1 a 1 , 1 , a 1 , 2 , ⋯ , a 1 , k 1 k_1 \quad a_{1,1}\,,a_{1,2},\,\cdots\,,a_{1,k_1} k1a1,1,a1,2,⋯,a1,k1
⋮ \vdots ⋮
k n a n , 1 , a n , 2 , ⋯ , a n , k n k_n \quad a_{n,1}\,,a_{n,2},\,\cdots\,,a_{n,k_n} knan,1,an,2,⋯,an,kn
4 3
1 3
2 8 6
1 2
3 1 7 5
2
第一天选择 3 的衣服,
第三天选择 2 的,
第四天选择 1 的。
最大差值为 2 ,是最小的差值。
n n n 很大, 不能从每天选哪件衣服去考虑。
我们考虑魅力值的差值,从这个方面去分析。
(1)
简单地想一下,若我们去二分差值是否可行?
那我们还需枚举起点。
每天贪心选择符合条件的衣服。
若有一个起点满足当前差值 x x x 可行,则二分范围下取,否则上取。
复杂度: 起点 * (每天天数 * 每天的衣服数) * 二分的次数
时间复杂度 O ( n × ∑ n k i i = 1 × log 2 1 0 9 ) O(n \times \underset{i=1\,\,\,\,\,}{\overset{n}{\sum}\,k_i}\times \log_210^9) O(n×i=1∑nki×log2109)
容易看出,会TLE。
(2)
想到,每次起点分别枚举过来:
对于同一个起点,我们会计算多次从该起点开始的答案计数。
从经验得知,尺取法可以解决该类问题。
(网络上和书本上最经典的一张图片)
那么我们定义 A [ i ] = j A[i]=j A[i]=j
下标i 设置成离散化后的美丽值
值A[I] 设置成这条美丽值衣服的对应天数
我们先按美丽值递增排序。
然后,我们只要尺取,使得尺取范围内的不同天数数量为 m m m ,
最后答案就是 满足要求的 ( 区间头美丽值 - 区间尾美丽值 ) 的最小值。
记 λ = ∑ n k i i = 1 \lambda = \underset{i=1\,\,\,\,\,}{\overset{n}{\sum}\,k_i} λ=i=1∑nki
时间复杂度 O ( λ log λ + λ ) O(\lambda\log \lambda + \lambda) O(λlogλ+λ)
(排序+尺取)
/*
_ __ __ _ _
| | \ \ / / | | (_)
| |__ _ _ \ V /__ _ _ __ | | ___ _
| '_ \| | | | \ // _` | '_ \| | / _ \ |
| |_) | |_| | | | (_| | | | | |___| __/ |
|_.__/ \__, | \_/\__,_|_| |_\_____/\___|_|
__/ |
|___/
*/
const int MAX = 2e6+50;
int enen;
struct Node{
int tt; /// 存美丽值
int ii; /// 存天数
}M[MAX]; /// 排序后即可离散化
bool cmp(Node ta,Node tb){ /// 按美丽值升序
return ta.tt < tb.tt;
}
int shu[MAX]; /// 计算某个天数出现的次数
int main()
{
int day,m;
scanf("%d%d",&day,&m);
for(int i=1;i<=day;++i){
int n;scanf("%d",&n);
while(n--){
int t;scanf("%d",&t);
enen++;
M[enen].tt = t;
M[enen].ii = i;
}
}
sort(M+1,M+enen+1,cmp);
int L = 0;
int R = 0;
int ans = INF;
int sum = 0;
while(1){ /// 标准尺取
R++;
while(R <= enen && sum < m){
if(shu[M[R].ii]==0){
sum++;
}
shu[M[R].ii]++;
if(sum==m)break;
R++;
}
if(R > enen)break;
L++;
while(L <= R && sum==m){
ans = min(ans ,M[R].tt - M[L].tt);
if(shu[M[L].ii]==1){
sum--;
}
shu[M[L].ii]--;
if(sum!=m)break;
L++;
}
}
printf("%d",ans);
return 0;
}
这次敲得比较差,因为尺取手比较生,还有离散化也比较生疏,希望多多练习起来。