暑假習題 2

投票遊戲
信奧班的同學總是這麼無聊,他們現在喜歡玩一種投票遊戲。遊戲規則如下,把參與遊戲的同學們編號由1到N,然後他們開始投票,每個人只能投一票,只能是贊成或者反對,投票結果下來後如果贊成人數嚴格大於反對的人數,那麼編號最小的那位同學便被踢出遊戲,剩下的同學繼續開始投票,直到投票僵持下來(比如剩兩個人的時候一個人反對一個人贊成就會一直僵持下去),這時候遊戲結束。XJ準備了一些小紅花,會把他們平均分給這些剩下的同學。每個同學都想得到最多的小紅花,而且不想自己被踢出。給出同學個數N,問最後剩下的同學個數,假定信奧班的同學都無比機智。
輸入
一行,一個整數N
輸出
一行,一個整數表示剩下的同學個數。
樣例
vote.in
3
vote.out
2
數據範圍
30% 1<=n<=50.
100% 1<=n<=5000.

分析:

一道很像海盜分金幣的趣味題,我們採取逆推方法。
當只剩2個人時肯定僵持,所以如果有3個人,則定會淘汰一個,如果4個人,因爲都不想被淘汰,1,2號肯定反對,3,4號定會贊成,則僵持,所以有5,6,7個人時定會淘汰到只剩4個人,依次類推,則如果有n個人,定會只剩2^log2(n)個人。

#include <cstdio>

using namespace std;

int n, ans;

int main() {
    freopen("vote.in", "r", stdin);
    freopen("vote.out", "w", stdout);

    scanf("%d", &n); ans = 1;
    while(ans * 2 <= n) ans *= 2;
    printf("%d\n", ans);

    return 0;
}

吃早飯
同學們每天早上都會晨練,但是每次運動後都會很餓,於是他們回去吃早飯,信奧班的同學們當然很有素質,他們都遵守規矩排隊。每個同學都面向窗口,一共有N個同學。現在XJ想知道每個同學能夠看到的前面的同學的個數和是多少。
定義一個同學能夠看到的人爲在他前面且身高嚴格低於他的人,並且他的視線會被在他前面第一個身高大於等於他的人擋住。也就是說無論如何也無法看到再前面的人了。哪怕是郭敬明。
輸入
N+1行
第一行一個正整數N表示同學的個數。
以下N行分別表示從後到前(窗口)同學的身高a.
輸出
一行一個整數,表示所有同學能看到的人的個數和。
樣例
eat.in
6
10
3
7
4
12
2
eat.out
5
數據範圍
30% 1<=N<=100,
100% 1<=N<=80000,1<=a<=1000000000.

分析:
搜索+貪心優化,sf[i]表示第i個人最遠看到的那個人,從後往前搜,如果i能看到第j個人,那麼一定能看到j能看到的所有人,則直接跳到j最遠看到的那個人繼續判斷,這樣將大大優化時間。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;

int n;
unsigned long long h[80010],f[80010],r[80010];

int main()
{
    freopen("eat.in","r",stdin);
    freopen("eat.out","w",stdout);
    scanf("%d",&n);
    for (int i=1; i<=n; i++)
        scanf("%d",&h[i]);

    for (int i=n; i>0; i--)
    {
        int j=i+1; 
        while (j<n+1 && h[i]>h[j]) j=r[j];
        r[i]=j;
        f[i]=j-i-1;
    }

    unsigned long long ans=0;
    for (int i=1; i<=n; i++)
        ans+=f[i];
    printf("%I64u\n",ans);
return 0;
}

棋盤遊戲
描述
現有一新型的棋盤遊戲。棋盤大小爲N*N,棋盤中有M個棋子,棋子每步可以往上下左右移動一步,每個棋子必須按順序移動到一個給定的出口點,且棋子之間永遠不能相鄰,求移動完所有棋子的最小步數。
輸入
輸入數據的第一行是兩個整數N,M,表示棋盤大小爲N*N,和棋子個數M。
接下來有N行,每行N個數,x表示出口,o表示空地,1~M表示棋子,棋子1號棋子必須最先離開棋盤,2號其次,以此類推。
輸出
輸出一個數,即移動完所有棋子的最小步數,如果不能移動完則輸出-1。
輸入樣例1
3 2
x2o
ooo
oo1
輸出樣例1
7
輸入樣例2
3 3
xo1
o2o
3oo
輸出樣例2
-1
數據規模
所有數據,N<=4,M<=4

分析:
典型 寬搜

#include<cstdio>
#include<queue>
using namespace std;
int dx[]={1,-1,0,0};
int dy[]={0,0,-1,1};
struct tnode
{
    int x[10],y[10];
    int step;
    int ro;
};
bool hash[7100][7100];
bool E[8][8];
queue<tnode> que;
int n,m;
bool check(tnode &node,int x)
{
    for (int i=1;i<=m;i++)
    {
        if (i==x||node.x[i]==0) continue;
        for (int j=0;j<4;j++)
        {
            if (node.x[i]+dx[j]==node.x[x] && node.y[i]+dy[j]==node.y[x]) return 0;
        }
    }
    if (E[node.x[x]][node.y[x]])
    {
        if (node.ro==x)
        {
            node.ro++;
            if (node.ro>m)
            {
                printf("%d",node.step);
                exit(0);
            }
            node.x[x]=0;
            node.y[x]=0;
        }
        else
        {
            return 0;
        }
    }
    int l=0,r=0;
    for (int i=1;i<=m;i++) l=l*10+node.x[i],r=r*10+node.y[i];
    if (hash[l][r]) return 0;
    hash[l][r]=1;
    return 1;
}

int main()
{
    memset(E,0,sizeof(E));
    memset(hash,0,sizeof(hash));
    scanf("%d%d",&n,&m);
    tnode node,newnode;
    char a;
    for (int i=1;i<=n;i++)
    {
        scanf("\n");
        for (int j=1;j<=n;j++)
        {
            scanf("%c",&a);
            if (a=='x') E[i][j]=1;
            else if (a!='o') node.x[a-'0']=i,node.y[a-'0']=j;
        }
    }
    node.step=0;
    node.ro=1;
    check(node,1);
    que.push(node);
    while (!que.empty())
    {
        node=que.front();
        que.pop();
        for (int i=1;i<=m;i++)
        {
            newnode=node;
            for (int j=0;j<4;j++)
            {
                newnode.x[i]=node.x[i]+dx[j];
                newnode.y[i]=node.y[i]+dy[j];
                if (newnode.x[i]<1||newnode.y[i]<1||newnode.x[i]>n||newnode.y[i]>n) continue;
                newnode.step=node.step+1;
                if (check(newnode,i)) que.push(newnode);
            }
        }
    }
    printf("-1");
    return 0;
}

敵兵佈陣

題目 略

這麼經典的線段樹區間操作
對於更新一段區間值的操作,需要添加lazy標記來優化線段樹,lazy標記是線段樹的精華,必須掌握。主要思想:如果要更新的一段區間[x,y]完全包含查詢到的點u的區間[l,r],則可直接計算這段區間的sum=(r-l+1)*A,這時標記這個區間後可直接返回,因爲該點子孫的相關信息我們不需要了。如果下一次要更新或查詢上次標記過的點,則需要把該點的左右兒子的相關信息按照上一次的標記更新(PushDown),纔不會錯。

於是這裏就直接代碼

#include<cstdio>
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
#define LL long long
const int maxn = 200000 + 100;
LL sum[maxn << 2] , col[maxn << 2] ;

void pushup(int rt) {
    sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}

void pushdown(int rt,int m) {
    if(!col[rt]) return ;
    col[rt << 1] += col[rt] ;
    col[rt << 1 | 1] += col[rt];
    sum[rt << 1] += (LL)col[rt] * (m - (m >> 1));
    sum[rt << 1 | 1] += (LL)col[rt] * (m >> 1);
    col[rt] = 0 ;
}

void build(int l,int r,int rt) {
    if(l == r) {
        scanf("%d",&sum[rt]);
        return ;
    }
    int m = (l + r) >> 1 ;
    build(lson) ; build(rson) ;
    pushup(rt);
}

void update(int L,int R,int add,int l,int r,int rt) {
    if(L <= l && r <= R) {
        sum[rt] += (r - l + 1) * add ;
        col[rt] += add ;
        return ;
    }
    pushdown(rt , r - l + 1);
    int m = (l + r) >> 1 ;
    if(L <= m) update(L , R , add , lson);
    if(m < R) update(L , R , add , rson);
    pushup(rt) ;
}

LL query(int L,int R,int l,int r,int rt) {
    if(L <= l && r <= R) {
        return sum[rt];
    }
    pushdown(rt , r - l + 1) ;
    int m = (l + r) >> 1 ;
    LL res = 0 ;
    if(L <= m) res += query(L , R , lson);
    if(m < R) res += query(L , R , rson);
    return res ;
}

int main() {
    freopen("soldier.in","r",stdin);
    freopen("soldier.out","w",stdout);
    int n , m , op , a , b , x;
    scanf("%d",&n);
    build(1 , n , 1);
    scanf("%d",&m);
    while(m--) {
        scanf("%d",&op);
        if(op == 1) {
            scanf("%d%d%d",&a,&b,&x);
            update(a , b , x , 1 , n , 1);
        } else {
            scanf("%d%d",&a,&b);
            printf("%lld\n",query(a , b , 1 , n , 1));
        }
    }
    //while(1);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章