总体感受
https://cn.vjudge.net/contest/183919#overview
本次的6题非常非常难,前两题极坑,后面研究了好久完全没有规律,我连爆搜都不会写.
我在比赛中途已经发呆了一个多小时,现在离结束还有9分钟,我已经放弃了.
T1
题目
唯一一道勉强能A的题,输入一个只有0,1的数组,去掉一些数字,使得所有的1之后都没有0,求剩下的数字最多有多少.
思路
花了我2小时才想出了递推的方法.我先讲讲我研究了1小时的错误做法.
首先把数组中前面的0和后面的1加到ans里(肯定不用去掉),然后对于每一个1,0分割线,把前面1的数量和后面0的数量比较一下取较大数目.
问题在于,如果数据变大到100个,中间有的时候你并不能取1而只能取0,这个时候这个程序就挂了.错误代码如下.
#include<bits/stdc++.h>
using namespace std;
int a[102];
int main()
{
int n,i,j,k,s;
cin>>n;
for (i=0;i<=101;i++) a[i]=2;
for (i=1;i<=n;i++) cin>>a[i];
for (i=1;i<=n;i++) if (a[i]==1) break;
s=i-1;
for (i=n;i>=1;i--) if (a[i]==0) break;
s+=n-i-1;
for (i=1;i<=n-1;i++) if (a[i]==1&&a[i+1]==0)
{
j=i;k=i+1;
while (a[j]==1) j--;
while (a[k]==0) k++;
s+=max(k-i-1,i-j);
}
cout<<s+1;
}
所以我知道这个题要递推.对于输入的第i个数,到它为止能取的最多的数字用s1表示,在它之前有的0的个数用s表示.那么对于每一个1,到它为止能取的一种方法是,取前面所有的0和它,或者取它前面的1时候的s.故可得到如果输入1则s1=max(s1+1,s+1);输入0则s+1.故代码实现如下,反而简单了很多.
#include<bits/stdc++.h>
using namespace std;
int a[102];
int main()
{
int n,i,s=0,s1=0;
cin>>n;
for (i=1;i<=n;i++)
{
cin>>a[i];
if (a[i]==1) s1=max(s+1,s1+1);
else s++;
}
cout<<max(s,s1);
}
T2
题目
有m道一样的题目,每道题分成n个部分,每个部分想要解出需要a[i]点实力值,解出之后获得1分.如果你把一道题的n个部分全部解出,你能获得n+1分.已知你的实力值,求你能得到最大的分数.
思路
数据范围不大,O(n^3)的爆搜也不会超时,可是此题极坑!!!!!
很明显有两种搜法,第一种是一排一排做,每次做一整道题,直到剩余的实力值不能做一整道题再从头开始每次做花费最小的部分,直到用完实力值,计算结果;第二种是直接从头开始搜,一直做花费最小的,直到结束.
坑点在哪里?首先坑爹在即使你是用的第2种方法,你在搜到最后一个数值时做完之后要+1,因为你做完了一整道题!可是这样还没完!当你搜到倒数第二个数的时候,这时如果最后一个数和倒数第2个数一样,并且你还能再做一个部分,这时候你不能做下一题的倒数第2个部分,而是要做这题的最后一个部分,这样能多1分!当我知道最后一个坑点,我无能为力了.
这个代码用的是爆搜,由于数据范围小,就搞定了.
#include<bits/stdc++.h>
using namespace std;
long long a[50];
int main()
{
long long m,n,i,j,k,l,answer=-100000;
cin>>m>>n>>k;
a[0]=0;
for (i=1; i<=n; i++) cin>>a[i];
sort(a,a+n+1);
for (i=1; i<=n; i++) a[i]+=a[i-1];
for (i=0; i<=n-1; i++) for (j=0; j<=m; j++) for (l=0; l<=m-j; l++) if (m*a[i]+j*(a[n]-a[i])+l*(a[i+1]-a[i])<=k)//大神解释一下这句话和下面这句的意思吧,我抄来了代码,看了好久也没看懂
answer=max(answer,i*m+j*(n+1-i)+l);
cout<<answer;
}
T3
题目
我们定义一个函数sum(i,j)表示从数组中的a[i]+a[i+1]+……+a[j-1].(i算,j不算)给出数组第一个的下标为0,找出其中三个分界点d1,d2,d3,使得sum(0,d1)-sum(d1,d2)+sum(d2,d3)-sum(d3,n)最大,输出其中一种结果。
思路
利用num数组计算前缀和,sum(d1,d2)=num[d2-1]-num[d1-1].
第一种方法O(n^3)时间复杂度,暴枚d2的所有位置,对于每一个位置,有0<=d1<=d2,d2<=d3<=n,在范围内暴枚d1和d3,肯定会TLE。
所以我这么想:计算式的结果等于2*num[d1-1]-2*num[d2-1]+2*num[d3-1]-num[n].
在比较时,同时减num[n]可以舍去,剩下的可以除以2。最后其实比较的很少,就是num[d1]-num[d2]+num[d3].
那么枚举的时候可以枚举num[d1]和num[d3]的最大值计算就可以了。
#include<bits/stdc++.h>
using namespace std;
long long a[5010]={0};
int main()
{
long long n,i,j,k,i1,j1,k1,maxi,maxj,maxk,sum=-2147483647;
cin>>n;
for (i=1; i<=n; i++) cin>>a[i];
for (i=1; i<=n+1; i++) a[i]+=a[i-1];
for (j=0; j<=n; j++)
{
j1=j;
i1=k1=j1;
for (i=0; i<=j; i++)
{
if (a[i]>=a[i1]) i1=i;//枚举a[i1]是最大值
}
for (k=j; k<=n; k++)
{
if (a[k]>=a[k1]) k1=k;//同理
}
if (a[i1]+a[k1]-a[j1]>=sum)
{
sum=a[i1]+a[k1]-a[j1];
maxi=i1;
maxj=j1;
maxk=k1;
}
}
cout<<maxi<<" "<<maxj<<" "<<maxk;
}
T4
题目
恰好9月9日凌晨零点我A了这个题。一台已经老旧的电视机,每隔一个时间点,电视机的一个像素就会坏掉。我们认为电视机上坏掉的像素点能组成一个k*k的正方形,则电视机就坏了。输入电视机屏幕大小n,m和k,以及要坏掉的像素点个数q,以下每一个像素点的位置坐标和坏掉的时间,问多久之后电视机坏掉?若电视机在所有像素点坏掉之后都不会坏,输出“-1”。
思路
我在写的时候把电视机屏幕n和m输入反了,找了半个小时才找到错。首先我对所有像素点的时间进行排序。我一开始用的是冒泡排序,但是发现因为q<=250000,冒泡排序会炸,所以我花了好久手写了一个快排,结果在第16个点WA了。如何判断坏掉的点生成了一个正方形?在电视机中有(n-k+1)*(m-k+1)个k*k的正方形,我对每一个正方形的右下角标记,如下。
#include<bits/stdc++.h>
using namespace std;
int x[250010],y[250010],t[250010],broken[510][510]={0};
void qsort(int l,int r)
{
int i=l,j=r,mid=t[(l+r)/2];
if (l>r) return;
while (i<=j)
{
while (t[i]<mid) i++;
while (t[j]>mid) j--;
if (i<=j)
{
swap(x[i],x[j]);
swap(y[i],y[j]);
swap(t[i],t[j]);//快排同时交换x[i],y[i],t[i],故不能用sort
i++;j--;
}
}
if (i<r) qsort(i,r);
if (j>l) qsort(l,j);
}
int main()
{
int n,m,k,p,i,j,l;
cin>>n>>m>>k>>p;
for (i=1;i<=p;i++) scanf("%d%d%d",&x[i],&y[i],&t[i]);
qsort(1,p);//快排
for (i=1;i<=p;i++)
for (j=x[i];j<=x[i]+k-1;j++)
for (l=y[i];l<=y[i]+k-1;l++)//暴枚每一个正方形的右下角,当该点坏掉时,存在该点的正方形右下角++
{
broken[j][l]++;
if (broken[j][l]>=k*k) {cout<<t[i]; return 0;}
}
cout<<-1;
}
这个代码的时间复杂度是O(q*k^2),数据一大还是会T。而且它在第16个点就WA了,我也无话可说了。所以我看了别人的代码,又手打了一遍,结果把n,m打反了,怎么样都输出-1。这个代码用的是二分法,把q变成了log(q),果然厉害。
#include<bits/stdc++.h>
using namespace std;
int a[510][510]={0},d[510][510]={0};
int main()
{
int m,n,k,q,x,y,t,i,j,l,r,result=-1,mid;
cin>>n>>m>>k>>q;
memset(a,63,sizeof(a));
for (i=1;i<=q;i++)
{
scanf("%d%d%d",&x,&y,&t);
a[x][y]=t;
}
l=0;r=1000000000;
while (l<=r)//二分法,通过枚举时间小于等于mid的点来估计到什么时候电视会坏。
{
mid=(l+r)/2;bool nico=0;
memset(d,0,sizeof(d));
for (i=1;i<=n;i++) for (j=1;j<=m;j++)
{
d[i][j]=1+min(d[i-1][j-1],min(d[i][j-1],d[i-1][j]));//如果左,上,左上三个点都不是0,则该点能继续算下去,否则就没有正方形了。
if (a[i][j]>mid) d[i][j]=0;//排掉时间大于一半的点
if (d[i][j]>=k) nico=1;
}
if (nico==1)
{
result=mid;
r=mid-1;
}
else l=mid+1;
}
cout<<result;
}
T5
题目
你要做实验,实验室里有n种材料,每种材料数量为b[i],你需要的每种材料数量为a[i]。
你可以按照一定规律转化材料,输入n-1条数据x[i],k[i],表明k[i]单位的x[i]能够转化为1单位的i,同理,1单位的i能转化成1单位的x[i].输入以上数据,判断你是否能获得足够的材料来做此次实验,如果是,输出“YES”,否则输出”NO”.
思路
首先把a[i]-b[i],判断手上的材料与需要的材料的数量是多了还是少了。然后从后往前,如果材料多出来,把它转化为x[i] (只考虑1单位的i能转化成1单位的x[i]),否则用x[i]补上它,最后看看第一个是不是刚好或者多出来就可以了。
#include<bits/stdc++.h>
using namespace std;
double ans[100001];long long x[100001],k[100001];
int main()
{
int n,i;
cin>>n;
for (i=1;i<=n;i++) cin>>ans[i];
for (i=1;i<=n;i++)
{
long long a;
cin>>a;
ans[i]=a-ans[i];//负数是多出来的,正数是不足的
}
for (i=2;i<=n;i++) cin>>x[i]>>k[i];
for (i=n;i>1;i--)
{
if (ans[i]>0) ans[x[i]]+=k[i]*ans[i];//少的按照系数k[i]往上补
else ans[x[i]]+=ans[i];//多出来的直接转化
}
if (ans[1]<0.5) cout<<"YES";//如果第一种材料刚好或多出来,当然就够了
else cout<<"NO";
}
T6
题目
给定一个数组,求随机选两个位置之间不同数字个数的期望值.
思路
对于不同位置,一共有n*n种选法.对于每两个位置选的时候有l,r和r,l两种可能,乘以2.重复的数字不进行计算.中间公式这里还是看不懂.
#include<bits/stdc++.h>
using namespace std;
int head[1111111]={0};
int main()
{
int n,x,i;
cin>>n;
long long int tot=-n;
for (i=1;i<=n;i++)
{
cin>>x;
tot+=(long long int)(i-head[x])*(n-i+1)*2;//看不懂
head[x]=i;
}
printf("%.10lf",(double)tot/((double)n*n));
}