A. Non-zero
题意:给一串数字,每次可以对一个数字+1,问最少多少次操作之后所有数的和和乘积均不为0。
思路:乘积更容易处理,只要所有数字非0即可,于是对所有0(可以没有)操作一次,然后如果当前数字和不为0,则结束,为0,则对一个非负数操作一次(必然存在)。
代码中特判了全为-1的情况,事实上毫无意义,因为全为-1的情况在前面就会背判断掉。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int inf=105; 17 using namespace std; 18 int T; 19 int n; 20 int a[inf]; 21 int main(){ 22 // freopen("in.txt","r",stdin); 23 rd(T); 24 while(T--){ 25 rd(n);for(int i=1;i<=n;i++)rd(a[i]); 26 int x=0,y=1,ans=0; 27 for(int i=1;i<=n;i++)x+=a[i],y*=a[i]; 28 if(x != 0 && y != 0)printf("0\n"); 29 else if(y == 0){ 30 for(int i=1;i<=n;i++)if(!a[i])x++,ans++; 31 if(x != 0)printf("%d\n",ans); 32 else printf("%d\n",ans+1); 33 } 34 else { 35 bool flg=0;for(int i=1;i<=n;i++)if(a[i] != -1)flg=1;// 其实没有意义 36 if(flg)printf("1\n");else printf("2\n"); 37 } 38 } 39 return 0; 40 } 41 /**/
B. Assigning to Classes
题意:给定2n个数字,分为两组,每组均为奇数个,使得两组的中位数之差最小,求出这个差值。
思路:答案是排序后第n个数字和第n+1个数字之差,证明方法有两种。
法一:先排序,枚举第一组的中位数x(x为其排序后的下标,后同),考虑x<=n的情况,先使其独自成组,此时第二组中位数一定是n+1,再考虑第一组有三个人的情况,由于中位数还是x,所以必须加入一个小于x的数字,而加入的另一个数字如果也小于等于n,第二组的中位数将变为n+2,不优,所以一个比较优的办法是加入2n,这样第二组中位数仍旧是n+1,我们可以发现在可以实现的范围内,中位数之差最优情况一定是x和n+1之差,而x取最优解时为n,所以中位数之差最小值为n和n+1的差。当x>n的情况和上述情况类似,结果仍旧是n和n+1之差。
法二:分为两组,第一组2k+1个,第二组2l+1个,k+l=n-1,分别排序,不妨令第一组的中位数k(表示其在自己组中的下标,后同)小于等于第二组的中位数l,那么有k+l+1个数字大于k即n个数字大于k,同理有n个数字小于l,所以k在原2n个数中的排名小于等于n,l大于n,二者之差最小时即分别为n和n+1时,然后只需构造出一组解即可,n独自成组即可。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int inf=1e5+10; 17 using namespace std; 18 int T; 19 int n,a[inf<<1]; 20 int main(){ 21 // freopen("in.txt","r",stdin); 22 rd(T); 23 while(T--){ 24 rd(n);for(int i=1;i<=n*2;i++)rd(a[i]); 25 sort(a+1,a+2*n+1); 26 printf("%d\n",a[n+1]-a[n]); 27 } 28 return 0; 29 } 30 /**/
反思:2n个数字分为两组中位数之差最小值即为第n小和第n+1小之差。
C. Anu Has a Function
题意:定义f(a,b)=(a|b)-b,给定序列an,求一个序列使得a1依次对a2~an进行此操作之后得到的a1值最大。
思路:观察的得到f(a,b)的操作实质上就是二进制下把b中为1的位对应到a中变为0,也就是说二进制下a1中最后剩下的位是a2~an中均不存在的位,对于a2~an中为1的位,对其求与即可得到,于是预处理前缀与和后缀与,枚举哪个数字作为a1即可。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int inf=1e5+10; 17 using namespace std; 18 int n; 19 int a[inf],s1[inf],s2[inf]; 20 int main(){ 21 // freopen("in.txt","r",stdin); 22 rd(n); 23 for(int i=1;i<=n;i++)rd(a[i]),s1[i]=s1[i-1]|a[i]; 24 for(int i=n;i>=1;i--)s2[i]=s2[i+1]|a[i]; 25 int mx=-1,id=0; 26 for(int i=1;i<=n;i++){ 27 int tmp=s1[i-1]|s2[i+1]; 28 if((a[i]|tmp)-tmp > mx){mx=(a[i]|tmp)-tmp;id=i;} 29 } 30 printf("%d ",a[id]); 31 for(int i=1;i<=n;i++)if(i != id)printf("%d ",a[i]); 32 puts(""); 33 return 0; 34 } 35 /**/
反思:(a|b)-b=a&(~b)
D. Aerodynamic
题意:以顶点的形式给一个凸多边形,如果凸多边形内部可以画出一个向量(x,y),那么就把(x,y)加入到一个点集中,可以证明这个点集最终构成的是一个图形且为凸多边形,你需要判断新凸多边形和原凸多边形是否相似。
思路:新图形一定是中心对称图形,因为可以画出向量(x,y)一定可以画出与它平行反向的向量(-x,-y),所以如果原凸多边形不是中心对称一定不相似。下面需要证明如果原凸多边形中心对称则一定相似。
考虑将原图的对称中心平移至原点处,那么对于(x0,y0)和(-x0,-y0)构成的向量,如果(x0,y0)在原图内,那么一定存在向量(2x0,2y0),即(2x0,2y0)在新图内,如果(x0,y0)不在原图内,考虑原图与其相交的两条平行边,引一条垂直于这两条边的直线,则在原图中所有向量在该直线上的投影小于等于两平行边距离之差,也就小于(x0,y0)与(-x0,-y0)所构成的向量在此直线上的投影,所以原图中不可能存在(2x0,2y0)这一向量,新图中也不可能出现(2x0,2y0)这个点,所以新图与原图相似。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int inf=1e5+10; 17 using namespace std; 18 int n,x[inf],y[inf]; 19 int main(){ 20 // freopen("in.txt","r",stdin); 21 rd(n); 22 for(int i=1;i<=n;i++)rd(x[i]),rd(y[i]); 23 if(n&1)printf("NO\n"); 24 else { 25 int flg=0,X=x[1]+x[n/2+1],Y=y[1]+y[n/2+1]; 26 for(int i=1;i*2<=n;i++)if(X != (x[i]+x[n/2+i]) || Y != (y[i]+y[n/2+i]))flg=1; 27 if(!flg)printf("YES\n");else printf("NO\n"); 28 } 29 return 0; 30 } 31 /* 32 对d题的猜测,初始p按凸包上点的顺序排序,然后每个点只需要加入它"正对"的那个点以及其两边共计三个点(奇数边为两点)与该点所形成的向量 33 到t中,然后对t求凸包,找到最上面的点中最左面的,开始同时枚举两个凸包,看他们是否成比例且比例相同即可。 34 */ 35 /*很遗憾,猜测是错误的,因为题目意思都理解错了*/
E. Water Balance
题意:给n个数字,每次操作是对l到r内的数字求平均值并对其赋值,经过若干操作之后使得序列字典序最小,求操作后的序列是什么。
思路:
先记录一下我当时考场的思路(与题解关系不大):修改后的序列一定是单增的,不然字典序可以更小。操作与操作是不相交的,对于a<b<c<d(以下对于区间的表示可能不太严谨,需要意会),考虑(a,c)和(b,d)操作可以被两个不重叠的区间操作取代,如果cd段的平均值小于等于ac,那么修改ad段更优,如果大于,修改ac和cd段更优。这样所有的操作便没有了先后顺序之分。于是有一个n^2的算法,从左往右枚举左端点,再枚举右端点,取平均值最小的情况即可。
下面讲题解思路,我们观察到这个操作表现在前缀和数组p上面的话,就是将一段变为等差数列,(l,r)的操作也就是相当于在(l-1,p[l-1])和(r,p[r])之间画一条线,其中间的点都在线上。我们还发现如果前缀和数组字典序最小,那么原数组字典序最小,反之亦然,于是就是要求前缀和数组字典序最小,可以发现字典序最小表现在数轴上就是求出给定点的下凸壳,需要注意的地方是为了实现对1的操作,我们需要加入(0,0)这个点。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int inf=1e6+10; 17 const dl eps=1e-7; 18 using namespace std; 19 int n; 20 int a[inf]; 21 dl p[inf],b[inf]; 22 struct Point{dl x,y;}; 23 typedef Point Vector; 24 Vector operator - (Point a,Point b){return (Vector){a.x-b.x,a.y-b.y};} 25 dl Cross(Point a,Point b,Point c){ 26 Vector u=b-a,v=c-a; 27 return u.x*v.y-v.x*u.y; 28 } 29 Point f[inf],sta[inf]; 30 int top; 31 dl Dot(Vector a,Vector b){return a.x*b.x+a.y*b.y;} 32 dl Length(Vector a){return sqrt(Dot(a,a));} 33 bool cmp1 (Point a,Point b){return a.x < b.x || (a.x == b.x && a.y < b.y);} 34 bool cmp2 (Point a,Point b){ 35 dl u=Cross(sta[1],a,b); 36 return u > eps || (abs(u) <= eps && Length(a-sta[1]) < Length(b-sta[1])); 37 } 38 int main(){ 39 // freopen("in.txt","r",stdin); 40 rd(n); 41 for(int i=1;i<=n;i++){rd(a[i]);p[i]=p[i-1]+a[i];f[i]=(Point){i,p[i]};b[i]=a[i];} 42 sort(f+1,f+n+1,cmp1);top++;sta[++top]=f[1];sort(f+2,f+n+1,cmp2); 43 for(int i=2;i<=n;i++){ 44 while(top >= 2 && Cross(sta[top-1],sta[top],f[i]) <= eps)top--; 45 sta[++top]=f[i]; 46 } 47 for(int i=1;i<top;i++){ 48 int l=sta[i].x+1,r=sta[i+1].x; 49 for(int j=l;j<=r;j++)b[j]=(p[r]-p[l-1])/(r-l+1); 50 } 51 for(int i=1;i<=n;i++)printf("%.9lf\n",b[i]); 52 return 0; 53 } 54 /* 55 n^2暴力应该是正确的,从左往右枚举,每次修改一个前缀使得当前位最小即可。 56 修改的区间是不相交的,这可能是解题的关键 57 是否有简单方法维护区间平均值?如果有的话问题应该就解决了 58 修改后的序列一定是单调递增的 59 */ 60 /* 61 思路依旧和题解不沾边 62 */
反思:将重叠操作变为不重叠操作之后操作与操作之间就没有了先后顺序之分,可以达到简化题目的效果。前缀和数组与原数组字典序相同。区间取平均值操作表现在前缀和数组就是把一段变为等差数列,表现在二维数轴上就是连接两个点。
来源:https://www.cnblogs.com/hyghb/p/12296663.html