写在前面:
这套题本来可以做出更多的,但是由于自己太菜,被cygg队落了六道题
深深的认识到自己的菜。还是要好好学习。
这套题思路都可以,也不算难,就整理一下。
A
题意:工作时间喝咖啡。总共要喝n杯咖啡,且一天中有n个时间点(第几分钟)可以喝咖啡,问你最快几天可以喝完。当然如果只有这些条件,第一天就可以喝完了。但是有限制条件:两次喝咖啡时间必须不小于d,也就是说如果同一天喝两杯咖啡,t2-t1>d才可以,不然第二杯没法喝。
思路:贪心的思想。先把所有可以喝的时间从小到大排序。第一天肯定喝时间最早的那个,然后从第二个可以喝的时间开始遍历,然后和队首相比:
相差时间大于d,那么可以喝,当前的天数就是队首喝咖啡那一天,然后把队首pop掉,当前这个入队(是为了更新这一天最新喝咖啡的时间)
如果相差时间不大于d,那么就不可以喝,所以新开一天,当前这一杯是在cnt+1天喝,这个总的cnt记录开到了第几天。
思路关键是用队列维护更新每一天最晚喝咖啡的时间,如果某一天最晚一次喝的咖啡时间被更新了,那先前一次喝咖啡的时间就pop掉了,所以每次只和队首的时间比即可(已经是那一天的最晚时间,且是后面所有天数里最早的,总之就是最合适的!(你品,你细品)
下面是AC代码
const int maxn=2e5+10;
int n,m,d;
struct node{
int id,t,cnt;//cnt为天数
bool operator<(const node &oth)const{
return t<oth.t;
}
}a[maxn];
int ans[maxn];
queue<node> q;
int main() {
scanf("%d%d%d",&n,&m,&d);
rep(i,1,n){
sd(a[i].t);
a[i].id=i;
}
sort(a+1,a+n+1);
int cnt=1;
a[1].cnt=cnt;
ans[a[1].id]=a[1].cnt;//最早的第一天用
node tp=a[1];
q.push(tp);
rep(i,2,n){//从第二个开始
if(a[i].t-q.front().t>d){//若可以塞在今天
a[i].cnt=q.front().cnt;
ans[a[i].id]=a[i].cnt;
q.push(a[i]);
q.pop();
}else{
a[i].cnt=++cnt;
ans[a[i].id]=a[i].cnt;
q.push(a[i]);
}
}
printf("%d\n",cnt);
rep(i,1,n){
printf("%d",ans[i]);
putchar(i==n?'\n':' ');
}
return 0;
}
B
题意:从飞机上往下跳伞,跳之后水平方向和竖直方向相同速度前进。如果遇到上升气流,就不下降。上升气流分段,问你从哪里跳,平移距离最大。
思路:自己没有思路。。。还是太菜了。题解说是前缀和+二分找答案,前缀和是方便求区间和,二分是upperbound 找之后第一个满足条件的区间。
即,我们出发肯定是从某个上升气流的起点开始的。所以我们这样划分区间:
求出与n个上升气流区间相对应的间隔区间。每个间隔区间是对应的上升气流前面那个(编号相同)。还有第n+1个间隔区间是最后一个气流之后,是无限远。
所以我们从1~n枚举所有间隔区间,其实就是枚举起点,然后二分查找后面所有间隔区间第一个大于等于b[i]+h的坐标(第一个间隔区间长度为0,作为前缀和的时候其实就是找h,其实每个都是找h 找b[i]+h其实是因为前缀和的特质。
那么每次找到的坐标p和当前枚举的坐标i 自己所平移总长度是原高度+气流区间经过长度
因为编号的问题(同编号 间隔区间在气流前面) 可知是a[p-1]-a[i-1]+h
然后看到mmk有个On的方法 看不懂(懒得看。。。) 就用这个nlogn的吧。。。
下面是AC代码
const int maxn=2e5+10;
//问从哪里开始飘的最远
//枚举每一个开始的下降区间即起点
int n,h;
struct node{
int l,r;
}q[maxn];
int a[maxn],b[maxn];//a是不下降的 前缀和 b是下降的
int main() {
sdd(n,h);
rep(i,1,n){
sdd(q[i].l,q[i].r);
}
a[1]=q[1].r-q[1].l;
b[1]=0;//第一个间隔在第一个不下降前面 因为要枚举前缀和为0的状态
rep(i,2,n){//处理前缀和
a[i]=a[i-1]+q[i].r-q[i].l;
b[i]=b[i-1]+q[i].l-q[i-1].r;
}
b[n+1]=inf;//最后一个下降长度为无穷
int ans=0;
rep(i,1,n){//遍历起点
int p=lower_bound(b+1,b+n+2,b[i]+h)-b;
ans=max(ans,a[p-1]-a[i-1]+h);
}
printf("%d\n",ans);
return 0;
}
C
题意:一堆数,两个相同的可以变成一个两倍。如果只有一个相同的,可以买,问至少需要买几个,如果怎样都不行,就输出-1
思路:优先队列判就行了 如果当前这个和后面的不想等,且当前的两倍大于后面,那么永远不行。
有个坑点!就是cnt是ll类型的,如果赋值-1会出问题。要好好赋值,评测机的内存空间可能很不靠谱。wa了n发。。。
using namespace std;
const int maxn=2e5+10;
int n;
priority_queue<ll,vector<ll>,greater<ll> > q;
int main() {
sd(n);
rep(i,1,n) {
ll tp;
scanf("%lld",&tp);
q.push(tp);
}
ll cnt=0;
while(!q.empty()){
ll tp=q.top();
q.pop();
if(q.empty()) break;
if(tp==q.top()){
q.push(tp*2);
q.pop();
}else{//若不等于堆顶
if(tp*2>q.top()){
cnt=(ll)-1;
break;
}
q.push(tp*2);
cnt++;
}
}
printf("%lld\n",cnt);
return 0;
}
D
题意:给你一堆数<=1e7, 问你 对于每个数,能不能输出这个数的两数乘积形式,且用过的乘积形式不能用 比如6可以分成 1 6 6 1 2 3 3 2 如果第四次出现6,就不行!
思路:我原来思路是对每个数枚举1~这个数,不过有个rec记录当前枚举到哪了,但是这样枚举还是太多了。
题解思路是:从1枚举到sqrt这个数,而且若不是平方*平方,那设置一个标志记录交换过来也可以用,这样大大降低了复杂度,一个小优化。挺好的。
const int maxn=2e5+10;
int c[maxn];
int a[maxn],b[maxn];
int rec[10000010];//rec[i]记录当前这个值到几了
bool con[10000010];//con[i]表示当前这个能不能用
int main() {
int n;
sd(n);
rep(i,1,n) sd(c[i]);
bool flag=true;
rep(i,1,n){
if(!con[c[i]]){//若当前这个值所对应的记录不能用
//寻找第一个可以整除c[i]的
if(rec[c[i]]==0) rec[c[i]]++;//从1开始
if(rec[c[i]]*rec[c[i]]>c[i]){
flag=false;
break;
}
while(1){
if(c[i]%rec[c[i]]) {
rec[c[i]]++;
if(rec[c[i]]*rec[c[i]]>c[i]){//若超了
flag=false;
break;
}
}else{
a[i]=rec[c[i]];
b[i]=c[i]/rec[c[i]];
if(a[i]!=b[i]) con[c[i]]=true;
else rec[c[i]]++;//如果平方数的话,只能用这一次
break;
}
}
if(!flag) break;
}else{
b[i]=rec[c[i]];
a[i]=c[i]/rec[c[i]];
con[c[i]]=false;
rec[c[i]]++;
}
}
if(!flag){
printf("NO\n");
}else{
printf("YES\n");
rep(i,1,n){
printf("%d %d\n",a[i],b[i]);
}
}
return 0;
}
E
题意:给你一个区间一串数,比如1 2 3 1 2 1 然后给你一系列操作,比如 2 1
然后你要把2之间的都变成2 然后1之间的都变成1
思路:cygg说浩神是模拟的 自己不会,所以用了之前的线段树lazy标记就变成了半个板子题。
注意用双端队列存储某个颜色(3e5个颜色)的左右区间 每次修改看看是否符合条件 然后去队首队尾修改
最后输出用query查询输出 因为很多不会询问到叶子,所以不到nlogn
下面是ac代码
const int maxn=3e5+10;
int n;
int a[maxn];
deque<int> q[maxn];//记录每种颜色还存在于哪些位置
struct node{
int l,r,c,lazy;//lazy也是颜色
}t[maxn<<2];
void pushup(int k){
if(t[k<<1].c==t[k<<1|1].c&&t[k<<1].c!=-1)
t[k].c=t[k<<1].c;
else t[k].c=-1;//-1代表不止一种颜色
}
void build(int k,int l,int r){
t[k].l=l,t[k].r=r,t[k].lazy=0;
if(l==r){
t[k].c=a[l];
}else{
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
pushup(k);
}
return;
}
void pushdown(int k){
if(t[k].lazy){
t[k<<1].c=t[k<<1|1].c=t[k].lazy;
t[k<<1].lazy=t[k<<1|1].lazy=t[k].lazy;
t[k].lazy=0;
}
return;
}
void update(int k,int l,int r,int c){//将l~r改成c
if(l<=t[k].l&&t[k].r<=r){
t[k].c=c;
if(t[k].l!=t[k].r) t[k].lazy=c;
}else{
pushdown(k);
int mid=(t[k].l+t[k].r)>>1;
if(l<=mid) update(k<<1,l,r,c);//更新这里的目标区间不变!!!
if(mid<r) update(k<<1|1,l,r,c);
pushup(k);
}
}
int query(int k,int p){//查询位置p的是什么
if(t[k].l==t[k].r){
return t[k].c;
}else{
if(t[k].c!=-1) return t[k].c;
if(t[k].lazy) return t[k].lazy;
pushdown(k);
int mid=(t[k].l+t[k].r)>>1;
int ans;
if(p<=mid) ans=query(k<<1,p);
else ans=query(k<<1|1,p);
return ans;
}
}
int main() {
sd(n);
rep(i,1,n){
sd(a[i]);
q[a[i]].push_back(i);//放入位置
}
build(1,1,n);
int m,tp;
sd(m);
while(m--){
sd(tp);
if(q[tp].size()>1){
while(!q[tp].empty()&&query(1,q[tp].front())!=tp) q[tp].pop_front();
while(!q[tp].empty()&&query(1,q[tp].back())!=tp) q[tp].pop_back();
if(q[tp].size()>1){
int s=q[tp].front(),t=q[tp].back();
update(1,s,t,tp);
}
}
}
rep(i,1,n){
printf("%d%c",query(1,i),i==n?'\n':' ');
}
return 0;
}
F
前缀和就行 没什么说的
const int maxn=1e6+10;
int a[maxn];
int b[28][maxn];
inline int get_un(int num){
return abs(num/100000+num/10000%10+num/1000%10-num/100%10-num/10%10-num%10);
}
int main() {
rep(i,0,999999){
a[i]=get_un(i);
b[a[i]][i]=1;
}
rep(i,0,27){
rep(j,1,999999){
b[i][j]+=b[i][j-1];
}
}
int t;sd(t);
while(t--){
int num;
char s[10];
scanf("%s",s);
num=(s[0]-'0')*100000+(s[1]-'0')*10000+(s[2]-'0')*1000+(s[3]-'0')*100+(s[4]-'0')*10+(s[5]-'0');
int q=get_un(num);
int ans=0;
rep(i,0,q-1){
ans+=b[i][num-1];
}
printf("%d\n",ans);
}
return 0;
}
G
题意:构造树,要求满足给出的边 断开之后两边最大的结点是所给的两个
思路:首先其中一个必须是n 不然肯定不行 然后我们造单链。
先将所有结点编号从小到大排列,然后从小的往上加。
如果当前这个编号比前一个大,直接插入n和与n最近的p之间。
如果这个编号和前一个相等,就从比当前这个中找一个更小且没用过的挂在n与最近的p之间
如果找不到就NO
//给你n-1对点 代表两个集合中编号最大的点
const int maxn=1010;
int n;
int a[maxn];//记录与n分成两部分的点们
bool con[maxn];//记录某点有没有用过
int pre[maxn];
int main() {
sd(n);
bool flag=true;
rep(i,1,n-1){
con[i]=false;
int x,y;
sdd(x,y);
if(y!=n) flag=false;
else a[i]=x;
}
if(!flag){
printf("NO\n");
}else{
sort(a+1,a+n);//排好序了
//a[1]是最小的
//还得设置一个变量代表离n最近的点
int p=a[1];//记住第一次得赋值!
pre[a[1]]=n;
con[a[1]]=true;
rep(i,2,n-1){
if(a[i]>a[i-1]){
pre[a[i]]=n;
pre[p]=a[i];//这里不一定对!!!
p=a[i];
con[a[i]]=true;
}else{
bool flag=false;
rep(j,1,a[i]-1){
if(!con[j]){
flag=true;//找到了
pre[p]=j;
pre[j]=n;
con[j]=true;
p=j;
break;//注意要break!!
}
}
if(!flag){
printf("NO\n");
return 0;
}
}
}
printf("YES\n");
pre[n]=-1;
int t=a[1];
while(pre[t]!=-1){
printf("%d %d\n",t,pre[t]);
t=pre[t];
}
}
return 0;
}
H
题意:铺砖。1x2的砖 只能横着放 如果放不下需要劈开 统计阴影四周 所有成单的列再乘以行数/2即可
三行代码题
int main() {
int n,m,a,b,c,d;
sdd(n,m);sdd(a,b);sdd(c,d);
printf("%d\n",((a-1)*(m&1)+(n-c)*(m&1)+(c-a+1)*((b-1)&1)+(c-a+1)*((m-d)&1)+1)/2);
return 0;
}
I
水题:刚开始没想明白各种做 就是看这个序列之间少了几个就行了
int n;
int a[1010];
int main() {
sd(n);
rep(i,1,n) sd(a[i]);
sort(a+1,a+n+1);
printf("%d\n",a[n]-a[1]+1-n);
return 0;
}
J
题意:电视机和发射源,组合题,画画图能看出来关系
using namespace std;
ll gcd(ll a,ll b){
if(b==0) return a;
return gcd(b,a%b);
}
int main() {
ll a,b,x,y;
cin>>a>>b>>x>>y;
ll g=gcd(x,y);
x/=g;
y/=g;
printf("%lld\n",a/x<b/y?a/x:b/y);
return 0;
}
K
题意:问你把一个序列怎样分才能使每一个序列中位数都不小于k 问最多分成几个这样的序列
思路:首先小于中位数的数量一定不大于n/2,然后要先用这些 剩下的单个成序列即可
const int maxn=5050;
int a[maxn];
int main() {
int n,m;
sdd(n,m);
int cnt=0;
rep(i,1,n){
sd(a[i]);
if(a[i]<m) cnt++;
}
int num=0;
if(n&1){
if(cnt>=(n+1)/2){
cnt=0;
}else{
int nn=n;
nn-=(2*cnt+1);
num++;
num+=nn;
}
}else{
if(cnt>=n/2){
cnt=0;
}else{
int nn=n;
nn-=(2*cnt+1);
num++;
num+=nn;
}
}
printf("%d\n",num);
return 0;
}
啊 !好累啊!