2019牛客多校训练(一)
比赛链接:
https://ac.nowcoder.com/acm/contest/881#question
A. Equivalent Prefixes
题意:
给出两个序列,求出一个最大的$p$,使$RMQ(v,l,r)=RMQ(u,l,r),1\leq l\leq r\leq p)$
$RMQ(v,l,r)$代表在$v$数组中,区间$[l,r]$的最小值下标
分析:
单调栈找到每个数向左延升第一个比它小的数,得到两个新的序列
找到最大相同前缀,就是题目所求的$p$
ac代码:
#include<bits/stdc++.h> #define ll long long #define PI acos(-1.0) #define pa pair<int,int> using namespace std; const int maxn=1e5+10; const ll mod=1e9+7; int n; int a[maxn],b[maxn]; int za[maxn],zb[maxn]; int top; pa stac[maxn]; //找到第一个比它大的值 int main() { while(scanf("%d",&n)==1) { for(int i=1;i<=n;i++)scanf("%d",&a[i]); for(int i=1;i<=n;i++)scanf("%d",&b[i]); for(int i=n;i>=1;i--) { while(top>=1&&stac[top].first>a[i])za[stac[top].second]=i,top--; stac[++top]=make_pair(a[i],i); } while(top)za[stac[top].second]=0,top--; for(int i=n;i>=1;i--) { while(top>=1&&stac[top].first>b[i])zb[stac[top].second]=i,top--; stac[++top]=make_pair(b[i],i); } while(top)zb[stac[top].second]=0,top--; int ans=0; for(int i=1;i<=n;i++) { // cout<<za[i]<<" "<<zb[i]<<endl; if(za[i]==zb[i]) ans++; else break; } printf("%d\n",ans); } return 0; }
E. ABAB
题意:
求出能分解成$n$个$AB$子序列和$m$个$BA$子序列的字符串种类数
分析:
首先分析如何贪心判断一个字符串是否可以分解成$n$个$AB$和$m$个$BA$,如果遇到$A$并且总数少于$n$那么肯定合法,总数大于$n$,那么就让多余的$A$与前面的$B$结合
定义$dp[i][j]$长度位$i+j$并且有$i$个$A$和$j$个$B$的前缀方案数
例如 $dp[0][0]$方案数为$1$,只有空字符串一种
转移方程:看代码
ac代码:
#include<bits/stdc++.h> #define ll long long #define ull unsigned long long #define pa pair<int,int> using namespace std; const int maxn=2e3+5; const int maxm=1e7+10; const ll mod=1e9+7; int n,m; int dp[maxn][maxn]; int main() { while(scanf("%d %d",&n,&m)==2) { for(int i=0;i<=n+m;i++) for(int j=0;j<=n+m;j++) dp[i][j]=0; for(int i=0;i<=n;i++)dp[i][0]=1; for(int i=0;i<=m;i++)dp[0][i]=1; for(int i=1;i<=n+m;i++) { for(int j=1;j<=n+m;j++) { if(i<=n||j-i+1+n>0) dp[i][j]=(dp[i][j]+dp[i-1][j])%mod; if(j<=m||i-j+1+m>0) dp[i][j]=(dp[i][j]+dp[i][j-1])%mod; } } printf("%d\n",dp[n+m][n+m]); } return 0; }
F. Random Point in Triangle
题意:
求出在一个三角形中加一个点,三条边与这个点构成的三个三角形的最大三角形的期望面积*36
保证最后的结果是整数
分析:
既然保证是整数,那么结果应该不是很复杂,期望面积很可能与原三角形的面积有关
一种暴力的方法是,在给定的三角形中撒上$1e7$个点,求出的平均面积接近期望面积
网上也有大佬证明,看不懂
ac代码:
#include<bits/stdc++.h> #define ll long long #define PI acos(-1.0) #define pa pair<int,int> using namespace std; const int maxn=1e5+10; const ll mod=1e9+7; struct Point { ll x,y; }point[3]; int main() { while(scanf("%lld %lld",&point[0].x,&point[0].y)==2) { scanf("%lld %lld",&point[1].x,&point[1].y); scanf("%lld %lld",&point[2].x,&point[2].y); point[1].x-=point[0].x; point[1].y-=point[0].y; point[2].x-=point[0].x; point[2].y-=point[0].y; ll area=abs(point[1].x*point[2].y-point[1].y*point[2].x); printf("%lld\n",area*11); } return 0; }
J.Fraction Comparision
题意:
给出$x,y,a,b$
求出$\frac{x}{a}$与$\frac{y}{b}$的大小关系
分析:
Java大数类可以直接过
ac代码:
import java.math.BigInteger; import java.util.Scanner; public class Main{ public static void main (String[] args) { int n; Scanner cin=new Scanner(System.in); while(cin.hasNext()) { BigInteger x=cin.nextBigInteger(); BigInteger a=cin.nextBigInteger(); BigInteger y=cin.nextBigInteger(); BigInteger b=cin.nextBigInteger(); x=x.multiply(b); y=y.multiply(a); int key=x.compareTo(y); if(key==0)System.out.println("="); else if(key==-1)System.out.println("<"); else if(key==1)System.out.println(">"); } } }
B. Integration
题意:
求一个连乘表达式的积分
分析:
参考博客:https://www.cnblogs.com/Dillonh/p/11209476.html
大致思路,算出表达式的系数,化简
ac代码:
#include<bits/stdc++.h> #define ll long long #define PI acos(-1.0) #define pa pair<int,int> using namespace std; const int maxn=1e3+10; const ll mod=1e9+7; ll a[maxn]; ll qpow(ll x,ll y) { ll res=1,k=x; while(y){ if(y%2)res=res*k%mod; k=k*k%mod; y/=2; } return res; } int main() { int n; while(scanf("%d",&n)==1){ ll ans=0; for(int i=1;i<=n;i++)scanf("%lld",&a[i]); for(int i=1;i<=n;i++){ ll res=1; for(int j=1;j<=n;j++){ if(i==j)continue; res=res*(a[j]*a[j]%mod-a[i]*a[i]%mod+mod)%mod; } res=qpow(res,mod-2)%mod; res=res*qpow(a[i],mod-2)%mod; ans=(ans+res)%mod; } ans=ans*qpow(2,mod-2)%mod; printf("%lld\n",ans); } return 0; }
H. XOR
题意:
给出一个数组,求所有异或和为0的子集$size$累加和
分析
对于$a_i$,它对答案的贡献就是包含它的异或和为0的子集种类数
我们先对整体的数组求线性基,假设共$r$个元素作为基底
线性基外面的每个元素的贡献为$2^{n-r-1}$,因为基底外的元素可以被基底唯一表示
对于线性基里面的元素$a_i$,对$n-1$个数求一次线性基,如果$a_i$能插入,那么$a_i$没有贡献,否则同上计算
ac代码:
#include <bits/stdc++.h> #define ll long long using namespace std; const int maxn = 1e5 + 5; const ll mod = 1e9 + 7; ll base1[70],base2[70],temp[70]; ll num[maxn],sk1[maxn],sk2[maxn]; int top1,top2,zz; ll qpow(int x,ll y) { ll res=1,k=x; while(y){ if(y&1)res=res*k%mod; k=k*k%mod; y/=2; } return res; } bool insert(ll base[],ll x) { for(int i=0;i<62;i++){ if(x&((ll)1<<i)){ if(base[i]==0){ base[i]=x; return 1; } x^=base[i]; } } return 0; } int main() { int n; // cout<<((ll)1<<62)<<endl; while(scanf("%d",&n)==1){ for(int i=0;i<62;i++)base1[i]=base2[i]=0; top1=top2=zz=0; for(int i=1;i<=n;i++){ scanf("%lld",&num[i]); if(insert(base1,num[i]))sk1[++top1]=num[i]; else sk2[++top2]=num[i]; } ll ans=top2*qpow(2,top2-1)%mod;//基底外元素的贡献 // cout<<ans<<endl; for(int i=1;i<=top2;i++) if(insert(base2,sk2[i]))zz++;//先把基底外元素插入 for(int i=1;i<=top1;i++){ for(int j=0;j<62;j++)temp[j]=base2[j]; int cnt=0; for(int j=1;j<=top1;j++) if(j!=i)if(insert(temp,sk1[j]))cnt++;//插入基底内元素 if(insert(temp,sk1[i])==0)ans=(ans+qpow(2,n-cnt-1-zz))%mod;//此元素插不进时才可以计算贡献 // cout<<ans<<endl; } printf("%lld\n",ans); } return 0; }
C. Euclidean Distance
题意:
给出$a$数组,构造一个$p$数组,满足$p_{i}\geq 0,\sum p_i=1$
求$\sum (a_{i}-p_{i})^{2}$的最小值
分析
贪心地将比较大的数变小,如果数相同,那么均摊给它们
ac代码:
#include <bits/stdc++.h> #define ll long long using namespace std; const int maxn = 1e4 + 5; const ll mod = 1e9 + 7; ll a[maxn]; bool cmp(ll a,ll b) { return a>b; } int main() { int n,m; while(scanf("%d %d",&n,&m)==2){ for(int i=1;i<=n;i++)scanf("%lld",&a[i]); sort(a+1,a+1+n,cmp); int cnt=0; for(int i=1;i<=n;i++){ if(i<n&&(a[i]-a[i+1])*i+cnt<=m){ cnt+=(a[i]-a[i+1])*i; }else{ ll ans=(m-cnt-i*a[i])*(m-cnt-i*a[i]); for(int j=i+1;j<=n;j++) ans=ans+i*a[j]*a[j]; ll d=(ll)i*m*m; if(ans==0){ printf("0\n"); break; } ll p=__gcd(d,ans); ans/=p; d/=p; if(d==1)printf("%lld\n",ans); else printf("%lld/%lld\n",ans,d); break; } } } return 0; }
I. Points Division
题意:
给出一些点,把它们划分成两个部分,第二部分点只有左上角可以出现第一部分的点
分析
$dp[i]$定义状态为以i点为划分折线上最后一个第二部分点
插入一个$x$极小,$y$极小的点,方便转移和计算全是第一部分的情况
这dp太难了,弱鸡理解了,但是很难正推到这样的dp
参考博客:https://blog.csdn.net/u013534123/article/details/96465704
ac代码:
#include<bits/stdc++.h> #define ll long long #define pa pair<int,int> using namespace std; const int maxn=1e5+10; struct Node{ int x,y,a,b; bool operator < (const Node &e)const{ if(x!=e.x)return x<e.x; return y>e.y; } }ne[maxn]; ll tree[maxn*4],lazy[maxn*4]; int sk[maxn],n,ran[maxn]; void build(int st,int en,int rt) { if(st==en){ tree[rt]=-1e18; lazy[rt]=0; return ; } int md=(st+en)/2; build(st,md,rt*2); build(md+1,en,rt*2+1); tree[rt]=max(tree[rt*2],tree[rt*2+1]); } void update1(int x,ll y,int st,int en,int rt) { if(st==en){ tree[rt]=max(y,tree[rt]); return ; } if(lazy[rt]){ tree[rt*2+1]+=lazy[rt]; tree[rt*2]+=lazy[rt]; lazy[rt*2]+=lazy[rt]; lazy[rt*2+1]+=lazy[rt]; lazy[rt]=0; } int md=(st+en)/2; if(x<=md)update1(x,y,st,md,rt*2); else update1(x,y,md+1,en,rt*2+1); tree[rt]=max(tree[rt*2],tree[rt*2+1]); } void update2(int l,int r,int y,int st,int en,int rt) { if(l>r)return ; if(l>en||r<st){ return ; } if(l<=st&&r>=en){ tree[rt]+=y; lazy[rt]+=y; return; } if(lazy[rt]){ tree[rt*2+1]+=lazy[rt]; tree[rt*2]+=lazy[rt]; lazy[rt*2]+=lazy[rt]; lazy[rt*2+1]+=lazy[rt]; lazy[rt]=0; } int md=(st+en)/2; update2(l,r,y,st,md,rt*2); update2(l,r,y,md+1,en,rt*2+1); tree[rt]=max(tree[rt*2],tree[rt*2+1]); } ll quer(int l,int r,int st,int en,int rt) { if(r<st||l>en)return -1e18; if(l<=st&&r>=en)return tree[rt]; if(lazy[rt]){ tree[rt*2+1]+=lazy[rt]; tree[rt*2]+=lazy[rt]; lazy[rt*2]+=lazy[rt]; lazy[rt*2+1]+=lazy[rt]; lazy[rt]=0; } int md=(st+en)/2; ll res=max(quer(l,r,st,md,rt*2),quer(l,r,md+1,en,rt*2+1)); tree[rt]=max(tree[rt*2],tree[rt*2+1]); return res; } int main() { while(scanf("%d",&n)==1){ for(int i=1;i<=n;i++){ scanf("%d %d %d %d",&ne[i].x,&ne[i].y,&ne[i].a,&ne[i].b); sk[i]=ne[i].y; } sort(ne+1,ne+1+n); sort(sk+1,sk+1+n); int cnt=unique(sk+1,sk+1+n)-sk-1; for(int i=1;i<=n;i++)ran[i]=lower_bound(sk+1,sk+1+cnt,ne[i].y)-sk+1; cnt++; //cout<<cnt<<endl; build(1,cnt,1); update1(1,0,1,cnt,1); for(int i=1;i<=n;i++){ update1(ran[i],quer(1,ran[i],1,cnt,1)+ne[i].b,1,cnt,1); update2(1,ran[i]-1,ne[i].a,1,cnt,1); update2(ran[i]+1,cnt,ne[i].b,1,cnt,1); } printf("%lld\n",tree[1]); } return 0; }