省赛训练,关键还是心态问题,心态放平,才能有高效发挥。
这一套题整体感觉质量还是挺好的,各种考验思维的题目,确实很有收获。
按顺序码了前三题,那就让我们来分析一下
A.Improving the GPA
思路:拿到题目后,第一思路就是贪心,压分数的上下值肯定就能得到最值结果。不过,两头的特殊数据就成了比较棘手的问题,考虑分数两边扩展不会影响结果,而扩展到最边上时由于端点的特殊性就可以得到最优值。
于是极限存放60和100,之后贪心其余的步骤。对于贪心的过程,由单步贪心进化到时时贪心。然后对于均分过高或过低的情况单独考虑。
不过对于60和100的数量问题上确实出了不少的问题,最终还是考虑不周而最终报废。
后来经研究发现,既然贪心就完全可以贪心得彻底一点,先将所有数据都处理成60(或100),经以上分析这两个值越多越好。然后对于多余(或不足)的数据,开始依次补进(或消去),直至85(或69),因为进一步的处理不会对分产生任何影响,因此将85(或69)作为为换点标志。
最后就是需要注意,均分过高或过低的情况,由于这种情况下,除最后一个数据外所有值都被设为85(或69),所以最后的结果一定过高(或过低),不过并不影响最终结果,直接按最值100(或60)处理即可。
由此可见,确实贪心过程需要明确的思路和冷静的大脑,不需要将过程复杂化,尽量找出较为普适的同一方程。
最后附上AC代码以及悲催狂WA代码(变量以w开头,位于注释部分),而两个程序结果(***开头为被WA的程序数据)差异经命令行比较(fc)结果如下图。
/*
Author:Owen_Q
*/
#include <bits/stdc++.h>
using namespace std;
double getscore(int x)
{
if(x<=69&&x>=60)
{
return 2.0;
}
else if(x<=74&&x>=70)
{
return 2.5;
}
else if(x<=79&&x>=75)
{
return 3.0;
}
else if(x<=84&&x>=80)
{
return 3.5;
}
else if(x<=100&&x>=85)
{
return 4.0;
}
return false;
}
int main()
{
int n,t,score;
while(scanf("%d",&t)!=EOF)
{
/*double wscore[110];
for(int i=60;i<=69;i++)
{
wscore[i] = 2.0;
}
for(int i=70;i<=74;i++)
{
wscore[i] = 2.5;
}
for(int i=75;i<=79;i++)
{
wscore[i] = 3.0;
}
for(int i=80;i<=84;i++)
{
wscore[i] = 3.5;
}
for(int i=85;i<=100;i++)
{
wscore[i] = 4.0;
}*/
while(t--)
{
scanf("%d%d",&score,&n);
double maxscore,minscore;
int totalscore = score * n;
int lastmaxscore = totalscore - 60 * n;
maxscore = 2.0 * (double)(n);
int maxid = n;
while(lastmaxscore)
{
if(lastmaxscore > 25 && maxid > 1)
{
lastmaxscore -= 25;
maxscore += 2.0;
}
else
{
if(lastmaxscore > 40)
{
maxscore += 2.0;
}
else
{
maxscore = maxscore + getscore(lastmaxscore + 60) - 2.0;
}
lastmaxscore = 0;
}
maxid--;
}
maxscore /= (double)(n);
int lastminscore = 100 * n - totalscore;
minscore = 4.0 * (double)(n);
int minid = n;
while(lastminscore)
{
if(lastminscore > 31 && minid > 1)
{
lastminscore -= 31;
minscore -= 2.0;
}
else
{
if(lastminscore > 40)
{
minscore -= 2.0;
}
else
{
minscore = minscore + getscore(100 - lastminscore) - 4.0;
}
lastminscore = 0;
}
minid --;
}
minscore /= (double)(n);
printf("%.4f %.4f\n",minscore,maxscore);
/*
int wn;
double k;
k = (double)(score);
wn = n;
//max
double wmaxsum = k*(double)(wn);
int wnmax = wn;
int wm60 = (int)(((89.0-k)*(double)(wn))/29.0);
double wmaxs = (double)(wm60)*2.0;
if(k>=85)
{
wmaxs = 4.0;
}
else
{
wmaxsum -= (double)(wm60)*60.0;
wnmax -= wm60;
//int wmaxave = (int)(wmaxsum / (double)(wnmax));
//int wmaxmod = (wmaxave % 5);
//int wmaxan = wmaxave - wmaxmod;
//wmaxs += wscore[wmaxan]*(double)(wnmax-1);
//int maxone = wmaxsum - (double)(wmaxan) * (double)(wnmax - 1);
//wmaxs += wscore[maxone];
//wmaxs /= double(wn);
//wmaxsum -= (double)(wm60)*60.0;
//wnmax -= wm60;
while(wnmax>1)
{
int wmaxave = (int)(wmaxsum / (double)(wnmax));
int wmaxmod = wmaxave % 5;
int wmaxan = wmaxave - wmaxmod;
wmaxs += wscore[wmaxan];
wmaxsum -= (double)(wmaxan);
wnmax--;
}
wmaxs += wscore[(int)(wmaxsum)];
wmaxs /= (double)(wn);
}
//min
double wminsum = k*(double)(wn);
int wnmin = wn;
int wm100 = (int)(((k-65.0)*(double)(wn))/35.0);
double wmins = (double)(wm100)*4.0;
if(k<=69)
{
wmins = 2.0;
}
else
{
wminsum -= (double)(wm100)*100.0;
wnmin -= wm100;
while(wnmin>1)
{
int wminave = (int)(wminsum / (double)(wnmin));
int wminmod = 4 - (wminave % 5);
int wminan = wminave + wminmod;
wmins += wscore[wminan];
wminsum -= (double)(wminan);
wnmin--;
}
wmins += wscore[(int)(wminsum)];
wmins /= (double)(wn);
//int wminave = (int)(wminsum / (double)(wnmin));
//int wminmod = 4 - (wminave % 5);
//int wminan = wminave + wminmod;
//wmins += wscore[wminan]*(double)(wnmin-1);
//int minone = wminsum - (double)(wminan) * (double)(wnmin - 1);
//wmins += wscore[minone];
//wmins /= double(wn);
}
printf("*****%.4f %.4f\n",wmins,wmaxs);
*/
}
}
return 0;
}
B.Killing Monsters
思路:塔防打怪兽问题,题目很清晰,就是区间线段型数据覆盖处理问题。一开始考虑到用树状数组可以降低求和的时间,然而却发现求和之前,将区间数据分配到对应点上就是个很麻烦的问题。而且,对于测试数据较多的情况,完全可以处理全部点,然后作为静态数据来处理,这就免去了反复运算维护的麻烦,将求和过程变成了独立的操作。
那么,现在最大的问题就是如何处理这些数据数据。
或许,这就有点数学导数的思维了,或者可以说信号里面冲激与阶跃的思想。将区间端点作为突变点来处理,存入第一个attack数组,第二个数组hurt对第一个attack数组进行累加,由突变向总值转变,得到每点的对应伤害值,最后引入第三个blood数组,从后往前累加求和,计算出后项累加和。三个数组的计算是三个相互独立的过程,因此相当巧妙地将时间复杂度降到了线性的O(n),完美解决。
当然,这种思维对于以后处理区间线段数据也会有很好的启示作用。
/*
Author:Owen_Q
*/
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
long long attack[maxn];
long long hurt[maxn];
long long blood[maxn];
int main()
{
int n,m,k;
while(scanf("%d",&n)!=EOF)
{
if(n==0)
{
break;
}
memset(attack,0,sizeof(attack));
scanf("%d",&m);
for(int i=0;i<m;i++)
{
int l,r,d;
scanf("%d%d%d",&l,&r,&d);
attack[l] += d;
attack[r+1] -= d;
}
long long temphurt = 0;
for(int i=1;i<=n;i++)
{
temphurt += attack[i];
hurt[i] = temphurt;
}
blood[n] = hurt[n];
for(int i=n-1;i>=1;i--)
{
blood[i] = blood[i+1] + hurt[i];
}
scanf("%d",&k);
int sum = 0;
for(int i=0;i<k;i++)
{
long long h;
int x;
scanf("%lld%d",&h,&x);
if(h>blood[x])
{
sum++;
}
}
printf("%d\n",sum);
}
return 0;
}
C.Task
思路:求解最优值,又是一道典型的贪心问题,乍一看有点类似于线性规划。如果只有一维因素,那肯定将最重要的任务分配给最强力的机器。
然而,当问题引入二元时,就不那么简单了,如果仅仅简单的区分两个变量的重要性来排序,当一维变量来排序,就会可能忽略一种很普遍的极端情况(高效高能机器处理中效低能任务,中效中能机器却无法处理低效高能任务)。
所以,开始考虑分别贪心的思路。
首先,当然还是按主次因素将任务和机器从大到小排序。
对于任务和机器两个对象,由于最终要计算与任务相关的金额,所以考虑将任务作为遍历对象。
如果分别遍历的话,O(n^2)的时间复杂的确实难以接受,于是开始考虑优化方法。
于是想到每次将满足第一变量时间效率的机器单独提出处理,这样所有机器都只会被处理一遍,时间复杂度被降为O(n+n)。
而对于被提取出的机器,找出满足要求的第二变量机器功能最小的机器,由于第一变量肯定满足无需再考虑,这样就可以给别的任务留出更高能的机器。然而很尴尬的问题是,就这么个找第二变量最小的满足功能的机器,也可能会成为一个长达O(n)的过程,而最坏情况又一次被降到了无法接受的O(n^2)。
仔细分析,发现,对于被提取出的机器,它的第一变量已经没有任何存在需要了(肯定满足),而两个变量之间的对应关系也就不那么重要了。分析输入数据可以发现,第二变量的取值范围只有100,那么就完全可以将所得机器的第二变量单独处理到一个大小仅为100的level数组中,通过数组计数来实现机器第二变量与任务的匹配,成功将时间复杂的降为O(n*100),完美AC
最后就是数组遍历的过程中细心点,谨防数组越界就好了
/*
Author:Owen_Q
*/
#include <bits/stdc++.h>
using namespace std;
typedef struct PRO
{
int x;
int y;
}Pro;
bool cmp(const Pro &x1,const Pro &x2)
{
if(x1.x>x2.x)
{
return true;
}
else if(x1.x==x2.x)
{
if(x1.y>x2.y)
{
return true;
}
}
return false;
}
const int maxn = 1e5+10;
Pro task[maxn];
Pro machine[maxn];
int level[105];
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(level,0,sizeof(level));
for(int i=0;i<n;i++)
{
scanf("%d%d",&machine[i].x,&machine[i].y);
}
sort(machine,machine+n,cmp);
for(int i=0;i<m;i++)
{
scanf("%d%d",&task[i].x,&task[i].y);
}
sort(task,task+m,cmp);
int sum = 0;
long long money = 0;
for(int i=0,j=0;j<m;j++)
{
while(machine[i].x>=task[j].x&&i<n)
{
level[machine[i].y]++;
i++;
}
for(int k=task[j].y;k<=100;k++)
{
if(level[k]>0)
{
level[k]--;
sum++;
money += 500 * task[j].x + 2 * task[j].y;
break;
}
}
}
printf("%d %lld\n",sum,money);
}
return 0;
}