B、 乐***对
题意:给出n个人,将这些人分为若干个组,每个人对应的一个值a[i],表示这个人所在队伍的人数只要有a[i]个人才可以,问最多可以组成多少个队伍?若不能组成队伍,则输出-1.
思路:我们考虑贪心的算法,我们令dp[i]表示当前到i的时候我们可以组成的最多的队伍数量。那么,我们可以直到,当我们到达i的时候,我们要考虑将i分到哪个组里面,我们可以将它们与i-1组成一组,如果i>=a[i]的话,我们也可以让[i-a[i]+1,i]这些组成一组,所以我们可以得到状态转移方程:
所以我们直接进行一个dp即可。
代码:
#include<bits/stdc++.h> using namespace std; int a[100010],dp[100010]; int ans=0; int main(){ int n; cin>>n; for(int i=1;i<=n;i++){ cin>>a[i]; } sort(a+1,a+1+n); if(n<a[n]){ cout<<"-1"<<endl; return 0; } for(int i=1;i<=n;i++){ if(i>=a[i]) ans=dp[i-a[i]]+1; dp[i]=max(ans,dp[i-1]); } cout<<ans<<endl; return 0; }
D、 巅峰对决
题意:给出一个序列,保证序列中每个数字都是不一样的,要求对序列进行单点修改和区间查询,查询区间里面的序列知否可以重新组成一个连续的序列。
思路:一个线段树板子题,可惜比赛时没注意看清题目,我们看到对于每次查询,我们确保我们序列里面的数字都是不同的,那么我们只要查询当前访问序列的最大和最小值,,然后与区间长度进行比较即可判断出来,这样,问题就转化为单点修改+区间查询最大和最小值了。
代码:
#include<bits/stdc++.h> using namespace std; const int N=100010; int tree1[N<<2],tree2[N<<2]; int a[N],n,q; void build(int node,int l,int r){ if(l==r){ tree1[node]=tree2[node]=a[l]; return; } int mid=(l+r)>>1,left_node=2*node,right_node=2*node+1; build(left_node,l,mid); build(right_node,mid+1,r); tree1[node]=min(tree1[left_node],tree1[right_node]); tree2[node]=max(tree2[left_node],tree2[right_node]); } void update(int node,int l,int r,int x,int val){ if(l==r){ a[x]=val; tree1[node]=tree2[node]=val; return; } int mid=(l+r)>>1,left_node=2*node,right_node=2*node+1; if(x<=mid&&x>=l) update(left_node,l,mid,x,val); else update(right_node,mid+1,r,x,val); tree1[node]=min(tree1[left_node],tree1[right_node]); tree2[node]=max(tree2[left_node],tree2[right_node]); } int query_mi(int node,int l,int r,int x,int y){ if(y>=r&&x<=l) return tree1[node]; if(l==r) return tree1[node]; int mid=(l+r)>>1,left_node=node*2,right_node=node*2+1; int ans=1e9+7; if(x<=mid) ans=min(ans,query_mi(left_node,l,mid,x,y)); if(y>mid) ans=min(ans,query_mi(right_node,mid+1,r,x,y)); return ans; } int query_mx(int node,int l,int r,int x,int y){ if(y>=r&&x<=l) return tree2[node]; if(l==r) return tree2[node]; int mid=(l+r)>>1,left_node=node*2,right_node=node*2+1; int ans=-1; if(x<=mid) ans=max(ans,query_mx(left_node,l,mid,x,y)); if(y>mid) ans=max(ans,query_mx(right_node,mid+1,r,x,y)); return ans; } int main(){ cin>>n>>q; for(int i=1;i<=n;i++) cin>>a[i]; build(1,1,n); while(q--){ int op,x,y; cin>>op>>x>>y; if(op==1){ update(1,1,n,x,y); }else{ int ans=query_mx(1,1,n,x,y)-query_mi(1,1,n,x,y)+1; if(y-x+1==ans) cout<<"YES"<<endl; else cout<<"NO"<<endl; } } return 0; }
E、使徒袭来
题意:给三个数的乘积,求出这三个数的和最小。
思路:数学问题,考察基本不等式。
代码:
#include<bits/stdc++.h> using namespace std; int main(){ double n; scanf("%lf",&n); double tmp=pow(n,1.0/3.0); double ans=3*tmp; printf("%.3lf\n",ans); return 0; }
F、核弹剑仙
题意:给出若干个比较,保证在这些比较合法的前提下,输出比每个武器更优的数量。
思路:我们先根据比较来进行建图,如果a优于b的话,我们可以建一条从从b到a的边。然后对于每个点从这该点开始进行一个dfs,即可求出我们的答案
代码:
#include<bits/stdc++.h> using namespace std; const int N=10010; int n,m,tot,sum; int head[N],ver[N],nxt[N]; bool vis[N]; void add(int x,int y){ ver[++tot]=y; nxt[tot]=head[x],head[x]=tot; } void dfs(int x){ sum++,vis[x]=true; for(int i=head[x];i;i=nxt[i]){ int y=ver[i]; if(vis[y]) continue; dfs(y); } } int main(){ cin>>n>>m; while(m--){ int a,b; cin>>a>>b; add(b,a); } for(int i=1;i<=n;i++){ sum=-1; memset(vis,false,sizeof(vis)); dfs(i); cout<<sum<<endl; } cout<<endl; return 0; }
G、虚空之力
题意:给出一个字符串,对于k,i,n,g可以对答案贡献1,对于k,i,n,g,i,n,g可以为答案贡献2.问答案最大为多少?
思路:一个很显然的贪心,我们肯定要有限满足kinging的情况,只需记录下这些字符的个数然后进行一个合理的分配即可。
代码:
#include<bits/stdc++.h> using namespace std; int main(){ map<int,int> mp; int n; cin>>n; string s; cin>>s; for(int i=0;i<n;i++){ mp[s[i]]++; } int ans=0; while(1){ if(mp['k']==0||mp['i']==0||mp['n']==0||mp['g']==0) break; else{ if(mp['i']>1&&mp['n']>1&&mp['g']>1){ ans+=2; mp['i']-=2,mp['n']-=2,mp['g']-=2; mp['k']--; } else{ ans++; mp['k']--; mp['i']--,mp['n']--,mp['g']--; } } } cout<<ans<<endl; return 0; }
H、社团游戏
题意:给出一个矩阵,找出满足矩阵里面每种字符不超过k个的最大矩阵,输出它的边长。
思路:哈希+二分。我们用num[i][j][k]表示i这个字符在矩阵行1-j,列为1-k的矩阵里面的个数,然后我们在二分答案的时候我们只需要比较是否超过k个即可,这里你也要对二维的哈希有个熟悉的了解。
代码:
#include<bits/stdc++.h> using namespace std; int n,m,k; int num[30][520][520]; char a[520][520]; bool judge(int x,int y,int mid){ for(int i=1;i<=26;i++){ int tx=x+mid-1,ty=y+mid-1; int sum=num[i][tx][ty]-num[i][x-1][ty]-num[i][tx][y-1]+num[i][x-1][y-1]; if(sum>k) return false; } return true; } int main(){ cin>>n>>m>>k; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++) cin>>a[i][j]; } for(int k=1;k<=26;k++){ for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ num[k][i][j]=num[k][i-1][j]+num[k][i][j-1]+(a[i][j]-'a'+1==k)-num[k][i-1][j-1]; } } } int l,r,mid; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ l=0,r=min(n-i+1,m-j+1); int ans=0; while(l<=r){ mid=(l+r)>>1; // cout<<"mid:"<<mid<<endl; if(judge(i,j,mid)){ l=mid+1; ans=mid; } else r=mid-1; } cout<<ans; if(j<m) cout<<" "; } cout<<endl; } return 0; }
I、名作之壁
题意:给你n个数字,第i个数字a[i]表示名作之壁第i天的销量。若某段区间[l,r]中最大值和最小值之差大于k,则称该区间为畅销区间。请问一共有多少个区间为畅销区间?
思路:既然是要对区间内的最大和最小值进行操作的话,我们可以用一个单调队列来对序列进行一个最大和最小值的维护,我们可以枚举每个区间的右端点,如果此时的r是一个畅销区间的话,那么直接对答案的贡献就是n-i+1,因为我们区间的右端点进行一个移动的话,当左端点不动的时候,我们可以知道这个区间里面的最大值肯定是大于等于[l,r]之间的最大值得,然后最小值同理一定是小于等于[l,r]之间的最小值的,那么区间里面的最大与最小值的差一定是大于等于k的,故得证!
代码:
#include<bits/stdc++.h> using namespace std; const int mod=1e9; typedef long long ll; int n,k,b,c; int a[10000010],q1[10000010],q2[10000010]; int f1=0,f2=0,r1=0,r2=0; //f1,r1用来维护区间最小值,f2,r2用来维护区间最大值 int main(){ cin>>n>>k; cin>>a[0]>>b>>c; ll ans=0; for(int l=1,r=1;r<=n;r++){ a[r]=((ll)a[r-1]*b+c)%mod; while(r1>f1&&a[q1[r1-1]]>=a[r]) r1--; //维护最小值 q1[r1++]=r; while(r2>f2&&a[q2[r2-1]]<=a[r]) r2--; //维护最大值 q2[r2++]=r; while(a[q2[f2]]-a[q1[f1]]>k){ ans+=n-r+1,l++; if(q1[f1]<l) f1++; //维护区间长度为正 if(q2[f2]<l) f2++; } } cout<<ans<<endl; return 0; }
J、逃跑路线
题意:求出一个数组里面的所有数字&&&...$最后所得的值。
思路:这个如果对二进制比较熟悉的话应该很好理解,我们知道上面公式后面&下来之后就是1,所以我们只要对数组里面的最低的和判断一下奇偶性就行了。
代码:
#include<bits/stdc++.h> using namespace std; int main(){ int n; cin>>n; int ans=0; while(n--){ string a; cin>>a; int len=a.size(); ans+=(a[len-1]-'0'); } if(ans&1) cout<<"1"<<endl; else cout<<"0"<<endl; return 0; }