AtCoder Beginner Contest 136
Contest Duration : 2019-08-04(Sun) 20:00 ~ 2019-08-04(Sun) 21:40
Website: AtCoder BC-136
后面几题都挺考思考角度D。
C - Build Stairs
题目描述:
有n座山从左到右排列,给定每一座山的高度\(Hi\),现在你可以对每座山进行如下操作至多一次:将这座山的高度降低1。
问是否有可能通过对一些山进行如上操作,使得最后从左至右,山的高度呈不下降序列。若可以输出
"Yes"
,否则输出"No"
。
数据范围:
\(1<=N<=10^5\),\(1<=Hi<=10^9\)
题解:
我们可以将问题进行转化:
现在有两个序列A,B,满足B[i]=A[i]-1 (i∈[1,n])
问能否构造一个新的序列C,使得C满足如下条件:
- 对于任意i∈[1,n],C[i]=A[i] 或者 C[i]=B[i]
- C为不降序列
那很明显在考虑C[i]时,在满足大于等于C[i-1]的前提下,我们选择较小的B[i];否则必须选择A[i]。如果题目将降1改成降k,做法也是一样的。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,a[N],b[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),b[i]=a[i]-1;
a[n+1]=1e9+1;
int lst=0;
for(int i=1;i<=n;i++){
if(b[i]>=lst)lst=b[i];
else if(a[i]>=lst)lst=a[i];
else{puts("No");return 0;}
}
puts("Yes");
}
D - Gathering Children
题目描述:
有n个排成一列的方格,每个方格上都写有
‘L‘
或‘R‘
,用一个长度为n的字符串表示(下标从1开始),且规定s[1]=‘R‘,s[n]=‘L‘
。一开始每个方格上都站了1个小朋友,每个人都会进行\(10^{100}\)次下述移动:
- 如果此时他站的位置写有L,则他向这个方格的左边移动;反之,向右移动。
问最后每个方格上站了多少个小朋友。
数据范围:
\(2<=n<=10^5\)
样例:
输入样例#1
RRLRL
输出样例#1
0 1 2 1 1
输入样例#2
RRRLLRLLRRRLLLLL
输出样例#2
0 0 3 2 0 2 1 0 0 0 4 4 0 0 0 0
题解:
还是跟思考的角度有关。一开始将所有小朋友视为整体,打了表发现会有循环,但是要想确定\(10^{100}\)次后的答案的话并不是很可做。
考虑将每个小朋友独立开来,单独统计他会贡献给谁。很容易发现一个规律:每个人最后一定会在‘RL‘间循环移动。那就用O(N)的时间预处理出对于每个点i,i的左边离他最近的‘R‘
的位置\(R[i]\),i的右边离他最近的‘L‘
位置\(L[i]\)。
统计答案时,如果一个点是‘L‘
,那这个点上的小朋友最后一定会在\(R[i]\)和\(R[i]+1\)间移来移去,只用考虑\(10^{100}\)的奇偶性即可。如果这个点是‘R‘
操作也类似。总之只跟\(10^{100}\)的奇偶性有关,之前还想了很久同余quq。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int R[N],L[N];
int cnt[N];
char s[N];
int main(){
scanf("%s",s+1);
int n=strlen(s+1),pos=0;
for(int i=1;i<=n;i++){
if(s[i]=='R')pos=i;
R[i]=pos;
}
pos=n;
for(int i=n;i>=1;i--){
if(s[i]=='L')pos=i;
L[i]=pos;
}
for(int i=1,step;i<=n;i++){
if(s[i]=='L'){
step=i-R[i];
cnt[R[i]+step%2]++;
}
else{
step=L[i]-i;
cnt[L[i]-step%2]++;
}
}
for(int i=1;i<=n;i++)printf("%d ",cnt[i]);
}
E - Max GCD
题目描述:
有一个含n个元素的序列A。你可以进行如下操作\([0,k]\)次:
- 任意选择\(i,j∈[1,n]且i≠j\),操作后:\(Ai=Ai+1,Aj=Aj-1\)(这意味着可能会出现负数)
请你求出最大的正整数ans,使得对于任意Ai都有\(Ai=x*ans (x为任意整数)\)
数据范围&输入输出格式:
- \(2≤N≤500\)
- \(1≤Ai≤10^6\)
- \(0≤K≤10^9\)
- All values in input are integers.
样例:
输入样例#1
2 3
8 20
输出样例#1
7
输入样例#2
4 5
10 1 2 22
输出样例#2
7
题解:
根据题意,得出如下等式——其中xi表示对Ai进行操作的增加值\((xi∈[-k,k])\)
\(A1+x1=ans*z1\)
\(A2+x2=ans*z2\)
\(......\)
\(An+xn=ans*zn\)
所有等式累加得:
\((A1+A2+..+An)+..(x1+x2+..+xn)=ans*(z1+z2+..+zn)\)
由于所有x累加后和为0,故ans必为A序列的总和的因数。基本框架可以得出了,枚举sum(A序列的总和)的因数ans,然后进行check(ans),若可行则更新最终答案。
而难点在于如何去配平上面的等式,并且使得所有大于等于0的xi之和小于等于k。
拿样例#2来看,当我们check(7)时:
\(10+x1=7*z1\)
\(1+x2=7*z2\)
\(2+x3=7*z3\)
\(22+x4=7*z4\)
很容易想到x的构造方案,\(x=-(xmod7)\)或\(x=7-xmod7\),正确性和最优性一目了然可以联系题目理解下:
\[
\left\{
\begin{array}{c}
x1=-3或+4……①\ x2=-1或+6……②\ x3=-2或+5……③\ x4=-1或+6……④\ \end{array}
\right.
\]
那现在面临的问题就是,对于每个xi,要么取左边的,要么取右边的,使得最后x的总和为0,且所有x的绝对值的总和/2要小于等于k。
下面有一个这类问题的贪心方案:
将xi根据前一列的大小排序——忽略正负号先,得出:
1 1 2 3
。接着枚举中轴,然后只要当左右的和相等且均小于等于k时,则ans可行。
正确性显然,看代码感性理解下8。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int sum=0;
int a[510],ans,n,k;
int sum1[510],sum2[510],p[510];
bool check(int x){
int cnt=0;
for(int i=1;i<=n;i++)p[i]=a[i]%x;
sort(p+1,p+n+1);
for(int i=1;i<=n;i++)sum1[i]=sum1[i-1]+p[i];
for(int i=n;i>=1;i--)sum2[i]=sum2[i+1]+x-p[i];
int mi=k;
for(int i=1;i<=n;i++){
if(sum1[i]==sum2[i+1]){
if(sum1[i]<=k)return 1;
else return 0;
}
}
return 0;
}
signed main(){
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]),sum+=a[i];
for(int i=1;i*i<=sum;i++)if(sum%i==0){
if(check(i))ans=max(ans,i);
if(check(sum/i))ans=max(ans,sum/i);
}
cout<<ans;
}
F - Enclosed Points
题目描述:
给定n个点,用坐标\((xi,yi)\)表示,设这n个点构成集合S,求对于任意S的非空子集T的\(f(T)\)总和。
对于函数\(f(T)\)的定义如下:设T包含的点中,x值最小的为\(Xmin\),x最大的为\(Xmax\),y值最小的为\(Ymin\),y最大的为\(Ymax\)。则该函数的值为:满足\(Xmin<=x<=Xmax且Ymin<=y<=Ymax\)的点(x,y)的个数。
数据范围&输入输出格式
- \(1≤N≤2×10^5\)
- \(?10^9≤xi,yi≤10^9\)
- \(xi≠xj(i≠j)\),\(yi≠yj(i≠j)\)
- All values in input are integers.
样例:
输入样例#1
3
-1 3
2 1
3 -2
输出样例#1
13
输入样例#2
10
19 -11
-3 -12
5 3
3 -15
8 -14
-9 -20
10 -9
0 2
-7 17
6 -6
输出样例#2
7222
题解:
找到正确的思考角度就比较好做了。
思路step 1:独立每个点对答案的贡献。
思路step 2:求每个点不被哪些集合包含,最后将总方案数减一下。(当然如果正着做也可以,反着会比较简单)
前置知识:对于一个含n个点的集合,它的非空子集数为\(2^n-1\)
所有在这道题里。
注意题面,保证了\(xi≠xj,yi≠yj\)。
先将坐标离散一下,然后将每个点按照x坐标排序。
图中黄点表示当前遍历到的点\(x\)。
区域一,区域二中分别含有的点的个数用\(s[x][1]\),\(s[x][2]\)表示,可以在\(O(nlogn)\)的时间内用树状数组维护得到。区域三,区域四中点的个数也是一样的统计方法,倒着扫一遍即可。
现在我们得出了每个区域含有点的个数。对于\(x\)这个点而言,我们现在的问题是统计:不含有x的集合的个数。
很明显就是将图中四个绿色的区域的方案总数减去图中四个紫色区域的方案总数。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+10,mod=998244353;
int a[N],b[N],c[N],ans;
int s[N][3],two[N],n;
struct node{
int x,y;
}p[N];
inline bool cmp(node p,node q){return p.x<q.x;}
void add(int x){while(x<=n)c[x]++,x+=x&(-x);}
int sum(int x){
int res=0;
while(x){
res+=c[x];
x-=x&(-x);
}
return res;
}
void calc(int tot,int d){
ans=(ans+(two[tot]-1)*d%mod)%mod;
}
signed main(){
scanf("%lld",&n);
two[0]=1;
for(int i=1;i<=n;i++)two[i]=two[i-1]*2%mod;
for(int i=1;i<=n;i++){
scanf("%lld%lld",&p[i].x,&p[i].y);
a[i]=p[i].x;
b[i]=p[i].y;
}
sort(a+1,a+n+1);sort(b+1,b+n+1);
sort(p+1,p+n+1,cmp);
for(int i=1;i<=n;i++){
p[i].x=lower_bound(a+1,a+n+1,p[i].x)-a;
p[i].y=lower_bound(b+1,b+n+1,p[i].y)-b;
}
for(int i=1;i<=n;i++){
int y=p[i].y;
s[i][2]=sum(y-1),s[i][1]=i-s[i][2]-1;
add(y);
}
memset(c,0,sizeof(c));
for(int i=n;i>=1;i--){
int y=p[i].y;
int s4=sum(y-1),s3=n-i-s4;
int tmp=ans;
calc(s[i][1]+s3,1);
calc(s[i][1]+s[i][2],1);
calc(s[i][2]+s4,1);
calc(s3+s4,1);
calc(s[i][1],-1);
calc(s[i][2],-1);
calc(s3,-1);
calc(s4,-1);
add(y);
}
printf("%lld",(n*(two[n]-1)%mod-ans+mod)%mod);
}
原文地址:https://www.cnblogs.com/Tieechal/p/11303995.html