L君找工作
题目描述:(暂不提供)
这道题拿到时已经没有什么时间了。想到一个二分套主席树的做法,但是因为考虑到自己主席树不熟练,就只写了一个暴力。结果呢,因为读错题目,输出的时候换了个行,然后就挂了。
这道题题解上给的是 的做法。但是因为我们的评测机很垃圾,所以 爆炸了。于是乎同学们开始吐槽 。
这道题有两个做法,在线做法是主席树,离线做法是整体二分。
对于主席树的做法,我们可以按每天工作时间的长短从小到大排序,并从大到小顺序在这天的 上插入它的工作时间。然后对于每组询问,我们可以先二分出工作序列上第一个大于等于准备时间的位置,然后对于这个历史版本进行查询。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 200010;
const int M = 200010;
inline void read(int &x)
{
char ch = getchar(); x = 0;
for(;ch < '0' || ch > '9';) ch = getchar();
for(;ch >= '0' && ch <= '9';) x = x * 10 + (ch ^ '0'), ch = getchar();
}
struct node
{
int id, val;
bool operator<(const node &o) const { return val < o.val; }
} a[N];
struct SGT{ int ls,rs,cnt; ll sum; } t[M * 50];
int rt[N],n,m,tot = 0;
void insert(int x,int k,int &p,int tl,int tr)
{
t[++tot] = t[p], ++t[p = tot].cnt,t[p].sum += k;
if(tl == tr) return;
int mid = tl + tr >> 1;
x <= mid ? insert(x,k,t[p].ls,tl,mid) : insert(x,k,t[p].rs,mid + 1,tr);
}
ll query(int y,int x,int p,int tl,int tr)
{
if(y > t[p].sum - (ll)t[p].cnt * x) return 0;
if(!p || tl == tr) return tl;
int mid = tl + tr >> 1;
ll sum = t[t[p].ls].sum - (ll)t[t[p].ls].cnt * x;
return y <= sum ? query(y,x,t[p].ls,tl,mid) : query(y - sum,x,t[p].rs,mid + 1,tr);
}
int main()
{
freopen("work.in","r",stdin);
freopen("work.out","w",stdout);
read(n), read(m);
for(int i = 1;i <= m; ++ i) read(a[i].val), a[i].id = i;
sort(a + 1,a + 1 + m); //先排序
for(int i = m;i; -- i)
insert(a[i].id,a[i].val,rt[i] = rt[i + 1],1,m);
//对于这一步,我们想让在查询时有序。
for(int i = 1,x,y;i <= n; ++ i)
{
read(x), read(y); int id = lower_bound(a + 1,a + m + 1,(node){0,x}) - a;
//二分,不用说了。
printf("%lld ",query(y,x,rt[id],1,m));
}
fclose(stdin); fclose(stdout);
return 0;
}
看来主席树我学的真的不好。看来要找 补补了。
接下来是离线做法,也就是整体二分。
然后整体二分是用来处理多次二分用的。一般来说一些
的做法就可以用整体二分离线变成
的。
对于这道题整体二分,先对天按照时间排序。
整体二分为保证时间复杂度,我们需要另
到
这段区间内的时间有序。
然后对于询问我们也可以用类似二分的分治来实现。
具体看看代码吧。本人太弱口胡不出来。
自己没写,就把 学长的代码贴上来吧。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2E5 + 7;
int n,m,ans[N];
struct day{ int id,time; } a[N],b[N];
struct query{ int id,x,y,ans; } q[N],t[N];
inline void swap(int &x,int &y) { x ^= y ^= x ^= y; }
inline int operator < (query x,query y) { return x.x < y.x; }
inline int operator < (day x,day y) { return x.time < y.time; }
inline void swap(query &x,query &y)
{ swap(x.id,y.id), swap(x.x,y.x), swap(x.y,y.y), swap(x.ans,y.ans); }
void solve(int l,int r,int L,int R)
{
if(l > r || L > R) return;
if(l == r)
{
for(int i = L;i <= R; ++ i)
if(a[l].time - q[i].x >= q[i].y) q[i].ans = l;
return;
}
int mid = l + r >> 1,i,j = l,k = mid + 1,p;
for(i = l;i <= r; ++ i)
if(a[i].id <= mid) b[j++] = a[i]; else b[k++] = a[i];
for(i = l;i <= r; ++ i) a[i] = b[i];
//由于保证了两段分别有序,所以就可以这么搞。
j = L, k = R; long long sum = 0ll;
for(i = mid,p = R;i >= l; -- i)
{
for(;p >= L && q[p].x > a[i].time; -- p)
if(sum - 1ll * (mid - i) * q[p].x >= q[p].y) t[j] = q[p], t[j].ans = mid, ++ j;
else t[k] = q[p], t[k].y -= sum - (mid - i) * q[p].x, -- k;
sum += a[i].time;
}
for(;p >= L; -- p)
if(sum - 1ll * (mid - l + 1) * q[p].x >= q[p].y) t[j] = q[p], t[j].ans = mid, ++ j;
else t[k] = q[p], t[k].y -= sum - (mid - l + 1) * q[p].x, -- k;
//这上面两段的check我们用双指针的方法就可以优化掉一个log
//同时具体赋值也类似于上面的a
for(i = L;i <= R; ++ i) q[i] = t[i];
for(i = L,p = j - 1;i < p; ++ i, -- p) swap(q[i],q[p]);
solve(l,mid,L,j - 1), solve(mid + 1,r,k + 1,R);
}
int main()
{
freopen("work.in","r",stdin),
freopen("work.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i = 1;i <= m; ++ i)
scanf("%d",&a[i].time), a[i].id = i;
for(int i = 1;i <= n; ++ i)
scanf("%d%d",&q[i].x,&q[i].y), q[i].id = i, q[i].ans = 0;
sort(a + 1,a + m + 1),
sort(q + 1,q + n + 1), solve(1,m,1,n);
for(int i = 1;i <= n; ++ i) ans[q[i].id] = q[i].ans;
for(int i = 1;i <= n; ++ i)
printf("%d%c",ans[i],i == n ? '\n' : ' ');
fclose(stdin), fclose(stdout);
return 0;
}
整体二分的题目还有很多。这个算法也不熟练。要多加练习啊。