POJ 2182 Lost Cows(三种解法:暴力 和 线段树 和 树状数组)

题目传送门

题意:

有n头牛,每头牛有一个序号(范围是1-n)。现在知道每头牛前面比自己序号小的牛的数量。需要求出每头牛的序号

分析:

可以想到的是,从后往前遍历。对于第i头牛,如果它前面有k头牛比它的序号小,那么它的序号就是当前仅剩编号中的第(k + 1)大的数。

暴力求法(类似约瑟夫环)

那么本题的难点就变成了如果求出剩下的编号中第m大的数,因为从后往前遍历的过程中,每确定一头牛的编号,那么剩下的编号就要去掉一个值。

这和约瑟夫环非常的类似(本题没有环),因此可以用vector模拟一个约瑟夫环来暴力求解

因为n = 8000,所以O(n ^ 2)的复杂度也勉强可以过

暴力代码:

//pre存储的是每头牛前面比自己序号小的牛的头数
int pre[N];
//r为剩下的编号的集合,ans为求出的每头牛的编号
VI r, ans;

int main(){	
	int n;
	scanf("%d", &n);
	for(int i = 2; i <= n; i++) scanf("%d", &pre[i]);
    
    //初始的编号集合为 {1,2,3,4.....n}    
	for(int i = 1; i <= n; i++) r.push_back(i);
	
    //倒序遍历
	for(int i = n; i >= 1; i--){
		//取出第pre[i]大的值(因为r是升序排列的,直接按下标把值取出来即可)
        //因为r的下标是从0开始的,因此下标为k的数其实是第 k+1 大的
        ans.push_back(r[pre[i]]);
		//编号集合去掉pre[i]
        r.erase(r.begin() + pre[i]);
	}
    
    //因为前面是倒序遍历每一头牛,因此输出前需要先反转一下
	reverse(ans.begin(), ans.end());
	
	for(int i = 0; i < ans.size(); i++)
		printf("%d\n", ans[i]);
	
	return 0;
}

线段树解法(维护区间内还有多少数没有被用过)

线段树维护每个区间内有多少个没被用过的数。每次取出第m大的数后,区间内数的个数 -1。

而找到第m大的数,类似于在一个有序序列中找第m大的数,其实本质思想是二分了。

因此,复杂度为O(n*logn),肯定是可以过的

线段树代码:

//线段树的结点,保存着区间左右端点和区间中值的个数
struct node{
	int v, l, r;
}tr[N * 4];

//建树
void build(int u, int l, int r){
	
    //初始时[l, r]区间中的数的个数为 r - l + 1
    tr[u] = {r - l + 1, l, r};
	if(l == r) return;
	int mid = l + r >> 1;
	build(u << 1, l, mid);
	build(u << 1 | 1, mid + 1, r);
}

//找第x大的数
int query(int u, int x){
	//既然搜到了这个区间,那么x必定在这个区间里面,区间值的个数自然要-1
    tr[u].v--;
	//如果搜到了叶子结点,那么必定是这个值了
    if(tr[u].l == tr[u].r) return tr[u].r;
	//左子区间的值的个数不足x个,那么就在右子区间找第 x - k大的数(k为左区间数的个数)
    if(tr[u << 1].v < x) return query(u << 1 | 1, x - tr[u << 1].v);
	//左区间的数的个数 >= x,那么就在左区间搜索即可
    return query(u << 1, x);
}

int pre[N];
VI ans;

int main(){	
	int n;
	scanf("%d", &n);
	for(int i = 2; i <= n; i++) scanf("%d", &pre[i]);
	
    //建树
	build(1, 1, n);
	//倒序遍历,求出每头牛的序号
    for(int i = n; i >= 1; i--) ans.push_back(query(1, pre[i] + 1)); 
	
    //翻转输出
    reverse(ans.begin(), ans.end());
	for(int i = 0; i < ans.size(); i++) printf("%d\n", ans[i]);
	
	return 0;
}

树状数组解法

单点修改,区间查询也可以用树状数组的,代码还短一些!

#include<bits/stdc++.h>
using namespace std;

#define fi first 
#define se second 
#define pb push_back
#define mst(a, b) memset((a), (b), sizeof (a))
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define prt(x) printf("%d\n", x)
#define prtl(x) printf("%lld\n", x)
#define debug(x) cout << #x << ": " << x << endl;
#define pY putlls("YES")
#define pN puts("NO")
typedef vector<int> VI;
typedef long long LL;
typedef pair<int, int> PII;
typedef priority_queue<int, vector<int>, greater<int> > PQ;
typedef priority_queue<int, vector<int>, less<int> > QP;

int n, m;
const int N = 200010, M = 510, INF = 0x3f3f3f3f;
int book[N], a[N];
int val[N], res[N];

inline int lowbit(int x) {return x & -x;}

void update(int x, int c){
    while(x <= n){
        val[x] += c;
        x += lowbit(x);
    }
}

int getsum(int x){
    int sum = 0;
    while(x){
        sum += val[x];
        x -= lowbit(x);
    }
    return sum;
}


//二分查找第k大的数,肯定是递增的哇
int findK(int k){
    int l = 1, r = n;
    while(l < r){
        int mid = l + r >> 1; 
        int t = getsum(mid);
        if(t < k) l = mid + 1;
        else r = mid;
    }
    return l;
}


int main(){
    int __ = 1;
    // sc(__);
    while(__--){
       sc(n);
       for(int i = 1; i <= n; i++){
           if(i > 1) sc(a[i]); 
           update(i, 1);
       }

        for(int i = n; i >= 1; i--){
            //找第a[i]+1大的数
            int k = findK(a[i] + 1);
            update(k, -1);
            //用过了,就减去1
            res[i] = k;
        }

        for(int i = 1; i <= n; i++) prt(res[i]);
    }
    return 0;
}

都看到这了,也不差👍的那点时间了吧~😘

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值