题解:CF691F Couple Cover
提供一种复杂度多一个 \(\log\) 的做法。
解析
我最初的想法是将询问离线下来,将球按权值排序,对于每个球 \(i\) 维护一个指针指向满足 \(a_i \cdot a_j \ge p\) 的第一个 \(j\),然后在处理询问的时候去移这个指针。
但是我们甚至无法接受在处理每个询问时遍历所有指针的复杂度。想到这一点,我就直接否掉了这个做法,但是其实可以继续做。
由于处理一个更大的 \(p\) 时并不一定是所有指针都需要移,所以考虑维护一个小根堆来存每个球当前指针指向的位置,按照对应值的乘积排序。但是这样总的指针移动次数依然可以达到 \(O(n ^ 2)\)。
注意到对于权值相同的小球,指针指向的位置永远相同。所以直接去重,这样对于权值为 \(x\) 的小球,指针至多移动 \(\lceil\frac{p_{max}}{x}\rceil\) 次,根据调和级数的结论总移动次数就是 \(O(p \log n)\)。去重之后计算贡献需要预处理一个每种小球出现次数的后缀和。
总时间复杂度 \(O(p\log^2 n)\)。
代码
/*
*/
#include<bits/stdc++.h>
#define eps 0.000001
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
typedef pair<ll,int> pii;
const int N = 3e6 + 5,M = 3.2e4 + 5;
ll a[N],cnt[N],suf[N],ans[N];
vector<ll> num;
struct cmp{
bool operator()(pii p1,pii p2){
return num[p1.first] * num[p1.second] > num[p2.first] * num[p2.second];
}
};
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
num.push_back(a[i]);
cnt[a[i]]++;
}
for(int i=N - 2;i>=1;i--){
suf[i] = suf[i + 1] + cnt[i];
}
int m;
cin>>m;
vector<pii> q;
for(int i=1;i<=m;i++){
int p;
cin>>p;
q.push_back({p,i});
}
sort(q.begin(),q.end());
sort(num.begin(),num.end());
num.erase(unique(num.begin(),num.end()),num.end());
int l = num.size();
priority_queue<pii,vector<pii>,cmp> pq;
ll res = 1ll * n * (n - 1);//所有数对均合法的方案数
for(int i=0;i<l;i++){
pq.push({i,0});
}
for(int i=0;i<m;i++){
int x = q[i].first;
while(!pq.empty() && num[pq.top().first] * num[pq.top().second] < x){//注意判空
pii tp = pq.top();
pq.pop();
res -= cnt[num[tp.first]] * (suf[num[tp.second]] - (tp.second <= tp.first));//注意在拿第二个球的时候第一个球不会放回去
int npos = tp.second;
while(npos < l && num[tp.first] * num[npos] < x){
npos++;
}
if(npos < l){
pq.push({tp.first,npos});
res += cnt[num[tp.first]] * (suf[num[npos]] - (npos <= tp.first));
}
}
ans[q[i].second] = res;
}
for(int i=1;i<=m;i++){
cout<<ans[i]<<"\n";
}
return 0;
}

浙公网安备 33010602011771号