CSP赛前集训 【L君找工作】

L君找工作

题目描述:(暂不提供)

这道题拿到时已经没有什么时间了。想到一个二分套主席树的做法,但是因为考虑到自己主席树不熟练,就只写了一个暴力。结果呢,因为读错题目,输出的时候换了个行,然后就挂了。

这道题题解上给的是 O ( n l o g 2 m ) O(nlog^2m) 的做法。但是因为我们的评测机很垃圾,所以 s t d std 爆炸了。于是乎同学们开始吐槽 s t d std

这道题有两个做法,在线做法是主席树,离线做法是整体二分。

对于主席树的做法,我们可以按每天工作时间的长短从小到大排序,并从大到小顺序在这天的 i d id 上插入它的工作时间。然后对于每组询问,我们可以先二分出工作序列上第一个大于等于准备时间的位置,然后对于这个历史版本进行查询。

#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;
}

看来主席树我学的真的不好。看来要找 L Z C LZC 补补了。

接下来是离线做法,也就是整体二分。
然后整体二分是用来处理多次二分用的。一般来说一些 O ( n l o g 2 n ) O(nlog^2n) 的做法就可以用整体二分离线变成 O ( n l o g n ) O(nlogn) 的。

对于这道题整体二分,先对天按照时间排序。
整体二分为保证时间复杂度,我们需要另 i d [ l ] id[l] i d [ r ] id[r] 这段区间内的时间有序。
然后对于询问我们也可以用类似二分的分治来实现。

具体看看代码吧。本人太弱口胡不出来。

自己没写,就把 Z J J ZJJ 学长的代码贴上来吧。

#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;
}

整体二分的题目还有很多。这个算法也不熟练。要多加练习啊。

猜你喜欢

转载自blog.csdn.net/INnovate2030/article/details/102811475