POJ 2886 線段樹對約瑟夫問題的模擬

這個問題其實就是對約瑟夫問題的模擬,單純的暴力模擬肯定是會超時的,用線段樹來優化,只要算出當前要走的人在當前的數組中是第幾個人就可以了

剛開始的時候沒怎麼弄明白,後來看了人家的分析之後才茅塞頓開

如果當前走的是第k個人,他報的數是m,那麼下一個要走的人就是 k-1+m(當然還得取模)。爲什麼是這樣呢,k走了之後,其實k之後的人相當於都向前挪了一個位置,所以需要減一個1. 在取完模以後,算得的結構就是下次要走的人是當前隊伍中的第幾個人,然後通過線段樹去查找一下就好,線段樹的節點記錄其對應區間內的人數和,所以,查找時只需:

如果查找的第x個人不超過左子樹的節點值,即左子樹中有那麼多的人,此時往左子樹查找;否則,往右子樹查找,但是要注意,必須將查找的人數減去左子樹的人數

找到人之後應該對該節點(人最後肯定是一個節點)及其所有父節點的值更新,節點的值減1,然後push_up一下就好了,返回的值就是該人在最初隊列中的座標

下面還有一個問題,怎麼使得他的約數個數最多呢??網上有很多解法都是反素數,在這裏我用了篩法,把1-500000的每個數的約數個數給算出來了,具體怎麼篩的通過代碼很容易看出來,篩完之後,在ans數組裏記錄的是ans[i]表示有i個人時最多能得到的糖的數量,index[i]記錄當有i個人時,第index[i]個跳出的孩子能得到最多的糖

以下就是代碼:

#include <cstdio>
#include <cstring>
#define FF(i,a,b) for(int i=a; i<b; i++)
const int MAX = 500010;
int sum[MAX<<2];
template <class T>
T max(T a,T b)
{
    return (a>b)?a:b;
}
void push_up(int rt)
{
    sum[rt] = sum[rt<<1]+sum[rt<<1|1];
}
void build(int l,int r,int rt)
{
    if(l==r)
    {
        sum[rt] = 1;
        return;
    }
    int mid = r+l>>1;
    build(l,mid,rt<<1);
    build(mid+1,r,rt<<1|1);
    push_up(rt);
}
int update(int p,int l,int r,int rt)
{
    if(l==r)
    {
        sum[rt] = 0;
        return l;
    }
    int mid = r+l>>1;
    int res;
    if(p<=sum[rt<<1]) res = update(p,l,mid,rt<<1);
    else res = update(p-sum[rt<<1],mid+1,r,rt<<1|1);
    push_up(rt);
    return res;
}
struct point
{
    char s[15];
    int x;
}ch[MAX];
int f[MAX];
int ans[MAX];
int index[MAX];
int main()
{
    int n,k;
    memset(f,0,sizeof(f));
    //下面的雙重循環就是篩法求約數個數,f裏存的是除1和本身外的約數個數
    for(int i=2; i*2<MAX; i++)
    for(int j=i*2; j<MAX; j+=i)
    f[j]++;
    ans[1] = 1;
    index[1] = 1;
    for(int i=2; i<MAX; i++)
    if(ans[i-1]>=f[i]+2)
    {
        ans[i] = ans[i-1];
        index[i] = index[i-1];
    }
    else
    {
        ans[i] = f[i]+2;
        index[i] = i;
    }
    while(scanf("%d%d",&n,&k)==2)
    {
        build(1,n,1);
        for(int i=1; i<=n; i++)
        scanf("%s%d",ch[i].s,&ch[i].x);
        int x = index[n];
        int tt;
        for(int i=0; i<x; i++)
        {
            tt = update(k,1,n,1);
            if(i==x-1) break;
            if(ch[tt].x>0) k = (k+ch[tt].x-2)%sum[1]+1; //計算下一次的跳出的人在現在這個隊列中排第幾
            //注意是從1開始記得,所以最後有個+1,括號裏是-2,自己算一下就能體會
            else k = ((k+ch[tt].x-1)%sum[1]+sum[1])%sum[1]+1; //與上面原理一樣
        }
        printf("%s %d\n",ch[tt].s,ans[n]);
    }
    return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章