2017暑假第二阶段第九场 总结

15 篇文章 0 订阅
9 篇文章 0 订阅

T1 丢钉子

问题描述

学校里一年一度的自行车大赛又开始了!!可是ZL同学却非常不高兴,因为他不会骑自行车!所以他决定干扰这次比赛。他已经了解到了这次参加比赛的m名参赛选手的资料。他决定要进行一次惊天动地的干扰。
我们假设比赛场地是一个从起点处向右和向前无限延伸的跑道。编号为1到m的参赛队员从左到右并列排列。从比赛开始后的第1秒末,第2秒末,第3秒末,第4秒末……第m秒末他分别会投一枚钉子到当前排名第一的自行车的前面使其爆胎!(爆胎的自行车自动退出比赛,不再计入排名),当有多个人同时并列第一时,由于ZL童鞋在起点左侧的观众席,他总是丢在最接近左侧边缘的那个第一名参赛选手前(字典序最小的第一名)。
而参赛队员的资料只有两个,一个是他第一秒能前进的距离Vi,一个是他第一秒末后每秒秒能前进的距离Ai。
现在要你来求,每秒钟,ZL童鞋把钉子丢在了编号为几的参赛选手前。

输入格式

第一行为一个整数m,表示有m名参赛选手。
接下来有m行,分别表示编号为 i(从1到m)的参赛队员的数据,每行两个整数Vi,Ai。

输出格式

一行,m个整数,第i个数,表示第i秒退赛的选手的编号

数据范围

1 <= m <= 50000
0 <= Vi <= 500
0 < Ai <= 100


最简单的模拟是O(M 2 )的,即在1~M秒内的每一秒讨论M个人。这样显然是会TLE的。这道题需要比较优雅的暴力解法。

充分利用数据范围的条件,下面两种解法都是可行的:
(下面的“速度”指第一秒之后的速度)

方法一:

如果两个人的速度不相同,那么差值至少为1。注意到V的最大值只有500,那么也就是说,在502秒时,如果还有人正在比赛,他们的相对位置一定不会发生改变了。只需要逐个讨论1~501秒的情况,之后的情况计算后排序即可。

#include<stdio.h>
#include<algorithm>
#define MAXM 50005
using namespace std;
int M,V[MAXM],A[MAXM];
bool mark[MAXM];

struct node{int dis,id;}can[MAXM];
bool operator<(node x,node y)
{
    if(x.dis==y.dis)return x.id<y.id;
    return x.dis>y.dis;
}

int main()
{
    int i,j,ID,Max,cnt=0;
    node tmp;

    scanf("%d",&M);
    for(i=1;i<=M;i++)scanf("%d%d",&V[i],&A[i]);

    for(i=1;i<=M&&i<=501;i++)
    {
        Max=0;
        for(j=1;j<=M;j++)
        {
            if(mark[j])continue;
            if(Max<A[j]*(i-1)+V[j])
            {
                Max=A[j]*(i-1)+V[j];
                ID=j;
            }
        }
        mark[ID]=true;
        printf("%d ",ID);
    }
    if(i>M)return 0;

    for(i=1;i<=M;i++)
    {
        if(mark[i])continue;
        cnt++;
        can[cnt].id=i;can[cnt].dis=A[i]*501+V[i];
    }

    sort(can+1,can+cnt+1);
    for(i=1;i<=cnt;i++)printf("%d ",can[i].id);
}

方法二:

显然,速度相同的人的相对位置只与V有关。注意到A的最大值只有100,那么可以以速度为标准分类并排序,每一秒只需要比较在未被踢出比赛的人当中,每个速度中V最大的人即可,也就是一次最多讨论100个人,讨论M次。

#include<stdio.h> 
#include<algorithm>
using namespace std;
#define MAXM 50005 
int M,A[MAXM],V[MAXM],head[101],cnt[101];
//head[i]记录当前速度为i的人当中V最大的编号,cnt[i]记录速度为i的人数
struct node{int v,id;}can[101][50005];
bool operator<(node x,node y)
{
    if(x.v==y.v)return x.id<y.id;
    return x.v>y.v;
}

inline int _R() 
{
    char s=getchar();int v=0,sign=0; 
    while((s!='-')&&(s>57||s<48))s=getchar(); 
    if(s=='-')sign=1,s=getchar(); 
    for(;s>47&&s<58;s=getchar())v=v*10+s-48; 
    if(sign)v=-v; 
    return v; 
} 

int main() 
{ 
    int i,j,ID,Max,Ans;
    node tmp;

    M=_R(); 
    for(i=1;i<=M;i++)
    {
        V[i]=_R(),A[i]=_R();
        can[A[i]][++cnt[A[i]]].id=i;
        can[A[i]][cnt[A[i]]].v=V[i];
    } 

    for(i=1;i<=100;i++)head[i]=1;
    for(i=1;i<=100;i++)sort(can[i]+1,can[i]+1+cnt[i]);

    for(i=1;i<=M;i++) 
    { 
        Max=0;ID=1e9;
        for(j=1;j<=100;j++) 
        {
            if(head[j]>cnt[j])continue;
            tmp=can[j][head[j]];
            if(Max<tmp.v+(i-1)*j) 
            { 
                Max=tmp.v+(i-1)*j;
                ID=j;
                Ans=tmp.id;
            }
            else if(Max==tmp.v+(i-1)*j)
            {
                if(tmp.id<Ans)ID=j,Ans=tmp.id;
            }
        } 
        head[ID]++;
        printf("%d ",Ans); 
    }
}

T2 比赛

问题描述

有三个小伙伴组队去参加 ACM 比赛,这场比赛共有n道题目,他们的比赛策略是这样的:每个队员都会对题目通看一遍,然后对每个题的难度进行估算,难度范围为 1~9。当然,由于每个队员的水平和特点, 他们对同一道题的估算不一定相同。
接下来他们会对所有题目进行分配。三个人分配的题目刚好是所有题目,且不会有交集,而且每个人分配的题目的编号必须是连续的,每人至少要 分一道题。请问,如何分配题目可以使得三个人拿到的题目的难度之和最小。每个人对自己 分配到的题目只按自己的估算值求和。

输入格式

第一行一个数 n,表示题目的数量。
接下来有 3 行,每行表示一个学生,每行有 n 个数,表示该生对n道题的估算难度,难度介于 1~9。

输出格式

一个整数。表示最小的估算难度之和

样例输入

样例1:

3
1 3 3
1 1 1
1 2 3

样例2:

5
4 1 5 2 4
3 5 5 1 1
4 1 4 3 1

样例输出

样例1:

4

样例2:

11

提示

样例1解释:第一个同学选第1题,第二个同学选第3题,第三个同学选第2题
样例2解释:第一个同学选第3题,第二个同学选第4,5题,第三个同学选第1,2题
数据范围:
对于 20% 的数据:3 <= N <= 1000
对于 100% 的数据:3 <= N <= 200000


这道题有巧妙但不优秀的图论做法:枚举6种情况,建图+最短路。建图方法是:对于每一行,前面的点向后面的点连一条权值为终点难度的有向边;对于第一行与第二行,向右下方也连一条权值为终点难度的有向边。将左上角作为起点,右下角作为终点,初始化时将起点的dis值赋为它的难度,跑一次最短路,答案就是右下角的dis值。

#include<stdio.h>
#include<iostream>
#include<queue>
#include<cstring>
#define MAXN 600005
#define MAXM 1000005
using namespace std;
int N,Ans=1e9,data[4][200005],v[5];

int cal(int x,int y){return (x-1)*N+y;}

int en[MAXM],las[MAXN],len[MAXM],nex[MAXM],tot;
void ADD(int x,int y,int z)
{
    en[++tot]=y;
    nex[tot]=las[x];
    las[x]=tot;
    len[tot]=z;
}

bool mark[MAXN];
int dis[MAXN];
queue<int>Q;

void SPFA(int t)
{
    int i,x,y;

    memset(dis,60,sizeof(dis));
    dis[1]=t;
    Q.push(1);
    while(Q.size())
    {
        x=Q.front();Q.pop();mark[x]=false;
        for(i=las[x];i;i=nex[i])
        {
            y=en[i];
            if(dis[y]>dis[x]+len[i])
            {
                dis[y]=dis[x]+len[i];
                if(!mark[y])mark[y]=true,Q.push(y);
            }
        }
    }
}

void Solve()
{
    memset(las,0,sizeof(las));
    tot=0;

    int i,j;
    for(i=1;i<4;i++)
    for(j=1;j<N;j++)ADD(cal(i,j),cal(i,j+1),data[v[i]][j+1]);

    for(i=1;i<3;i++)
    for(j=1;j<N;j++)ADD(cal(i,j),cal(i+1,j+1),data[v[i+1]][j+1]);

    SPFA(data[v[1]][1]);
    Ans=Ans>dis[3*N]?dis[3*N]:Ans;
}

int main()
{
    int i,j,k;

    scanf("%d",&N);
    for(i=1;i<4;i++)
    for(j=1;j<=N;j++)scanf("%d",&data[i][j]);

    for(i=1;i<4;i++)
    for(j=1;j<4;j++)
    for(k=1;k<4;k++)
    {
        if(i==k||i==j||j==k)continue;
        v[i]=1;v[j]=2;v[k]=3;
        Solve();
    }

    printf("%d",Ans);
}

标程给的是DP的做法。

6种情况还是要枚举。求出三行的前缀和,设当前a号人取第一段,b号人取第二段,c号人取第三段。由于前两人确定后,第三人的情况也跟着确定,所以考虑前两人的情况。

设前两个人取到i号的最小难度总和为 f[i] ,那么 f[i]=min(sumb[i]sumb[j]+suma[j])i>j1 。那么 f[2] f[N] 都可以在O(N)内算出来,并且可以在计算 f <script type="math/tex" id="MathJax-Element-369">f</script>的同时更新答案。

#include<stdio.h>
#define Min(x,y) ((x<y)?(x):(y))
#define MAXN 200005
int N,sum[5][MAXN],Ans=1e9;

void DP(int x,int y,int z)
{
    int i,A=1e9,B=1e9;
    for(i=1;i<N-1;i++)
    {
        A=Min(A,(sum[x][i]-sum[y][i]));
        B=Min(B,(A+sum[z][N]-sum[z][i+1]+sum[y][i+1]));
    }
    Ans=Min(Ans,B);
}

int main()
{
    int i,j,k,x;

    scanf("%d",&N);
    for(i=1;i<4;i++)
    for(j=1;j<=N;j++)scanf("%d",&x),sum[i][j]=sum[i][j-1]+x;

    for(i=1;i<4;i++)
    for(j=1;j<4;j++)
    for(k=1;k<4;k++)
    {
        if(i==j||j==k||i==k)continue;
        DP(i,j,k);
    }
    printf("%d",Ans);
}

T3 英雄

问题描述

  在玩DOTA时,你经常会遇到这样尴尬的情况,那就是你的队友全都死了,游戏中只剩下你一个人独自迎战N个敌人。
   在游戏中,你操控着一个英雄,每个英雄都有两项属性:生命值(HP)和杀伤力(DPS)。你的英雄有无限的HP值,但是DPS值为1。也就是每一次攻击,你只能让敌方英雄损失1点的生命值。
   假设我们玩的DOTA游戏是回合制的。每一个回合,你可以选择攻击敌方的一个英雄,被攻击的敌方英雄的HP值会减1。同时,所有活着的敌方英雄会攻击你,你的HP值减少的数量等于他们的杀伤力的总和。如果一个英雄的HP值降至0,他就死掉了,不会再攻击你了。
   你能否赢得这场游戏呢?所以请你计算最少损失多少HP值,才可以消灭所有的敌方英雄。

输入格式

第一行,一个整数 N 。
接下来N行,每行两个空格间隔的整数,分别表示一个敌方英雄的DPS值和HP值。

输出格式

一个整数,表示打败所有敌方英雄,最少需要损失的HP值。

样例输入

2
100 1
1 100

样例输出

201

提示

1 <= N <= 20
1 <= DPS, HP <= 1000


其实这道题的N可以给得很大……常常说可以从数据范围看出算法,结合题目,这样的数据范围显然是状压DP。然而我一开始想到的是这一题:体检。然而觉得O(nlogn)的算法对于这样小的数据范围不是显得有些不和谐吗?于是还是写的状压DP。

然而,体检的做法也是正解,把题目条件转译一下之后过程几乎相同,这里不再赘述。

要实现差劲得多的状压DP,要利用一个显然的结论:不要东打一下西打一下,要打就一个一个地打倒。这样我们就可以实现状态转移了。

#include<stdio.h> 
#include<cstring> 
#define ll long long 
#define Min(x,y) ((x<y)?(x):(y)) 
#define MAX 1148576 
const ll inf=(1LL<<60); 
int N,HP[25],DPS[25]; 
ll f[MAX],sum[MAX]; 

inline int _R() 
{ 
    char s=getchar();int v=0,sign=0; 
    while((s!='-')&&(s>57||s<48))s=getchar(); 
    if(s=='-')sign=1,s=getchar(); 
    for(;s>47&&s<58;s=getchar())v=v*10+s-48; 
    if(sign)v=-v; 
    return v; 
} 

int main() 
{ 
    int i,j,Lim,tmp;
    N=_R(); 
    Lim=(1<<N)-1; 
    for(i=1;i<=N;i++)DPS[i]=_R(),HP[i]=_R(),sum[Lim]+=DPS[i]; 

    for(i=Lim-1;i>=0;i--) 
    { 
        for(j=1;j<=N;j++)if((i>>j-1)&1)sum[i]+=DPS[j]; 
        f[i]=inf; 
    } 

    for(i=Lim;i>=0;i--) 
    { 
        for(j=1;j<=N;j++) 
        { 
            if((i>>j-1)&1) 
            { 
                tmp=i-(1<<j-1); 
                f[tmp]=Min(f[tmp],(f[i]+sum[i]*HP[j])); 
            } 
        } 
    } 

    printf("%lld",f[0]); 
}

总结

这次又打得不理想,三场rank1三场爆炸,发挥很不稳定。这次考试的题目还算简单,也没有什么不熟悉的知识点。T3十分钟不到就写好了,但是在写T1的时候把ID和head[j]搞混了,结果把100分写成了30分;写T2的时候显然的DP都想错了,而且还卡了很久。现在看起来真是觉得很显然。结合到这三天赶作业睡得都很晚,应该调整作息时间,保证良好的精神状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值