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>j≥1 。那么 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都想错了,而且还卡了很久。现在看起来真是觉得很显然。结合到这三天赶作业睡得都很晚,应该调整作息时间,保证良好的精神状态。