不咕咕咕是一种美德【大雾】
头一次体会到爆肝写题解???
这次考试我们没赶上,是后来掐着时间每个人自己考的。我最后的分数能拿到152…熟悉的一题AC两题爆炸。
强烈吐槽出题人起名走心
T1联:
发现每一次加入一个区间的操作,只有区间的l或者r+1有可能成为答案。那么考虑能不能用这两个点代表一整个区间,维护全局最靠左的0在什么地方。
把每个操作的l和r+1都存下来,离散化,建一棵线段树。每一次区间操作都针对线段树上的a[l]-a[r+1]-1这部分(a[x]为x离散化以后的排序,即线段树里的位置)。
维护线段树的区间和,每一次查询就是寻找最左边的区间和不等于区间长度的地方。对于反转操作,打一个可下传的标记,让现有区间和变成区间长度减去原区间和。
要注意一开始答案为1,保存下所有l和r+1还要额外加一个1。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int m,cnt=1; long long b[300010]; struct node{ int opt; long long l,r; }a[300010]; struct tree{ int l,r,sum,tag1,tag2; }t[1000010]; void build(int p,int l,int r){ t[p].l=l,t[p].r=r; t[p].tag1=-1; if(l==r)return; int mid=(l+r)/2; build(p*2,l,mid); build(p*2+1,mid+1,r); } void pushdown(int p){ if(t[p].tag1!=-1){ t[p*2].tag1=t[p].tag1; t[p*2].sum=(t[p*2].r-t[p*2].l+1)*t[p].tag1; t[p*2].tag2=0; t[p*2+1].tag1=t[p].tag1; t[p*2+1].sum=(t[p*2+1].r-t[p*2+1].l+1)*t[p].tag1; t[p*2+1].tag2=0; t[p].tag1=-1; } if(t[p].tag2){ t[p*2].tag2^=1; t[p*2].sum=t[p*2].r-t[p*2].l+1-t[p*2].sum; t[p*2+1].tag2^=1; t[p*2+1].sum=t[p*2+1].r-t[p*2+1].l+1-t[p*2+1].sum; t[p].tag2=0; } } void change(int p,int l,int r,int val){ if(l<=t[p].l&&t[p].r<=r){ t[p].sum=(t[p].r-t[p].l+1)*val; t[p].tag1=val; t[p].tag2=0; return; } pushdown(p); int mid=(t[p].l+t[p].r)/2; if(l<=mid)change(p*2,l,r,val); if(r>mid)change(p*2+1,l,r,val); t[p].sum=t[p*2].sum+t[p*2+1].sum; } void change1(int p,int l,int r){ if(l<=t[p].l&&t[p].r<=r){ t[p].tag2^=1; t[p].sum=t[p].r-t[p].l+1-t[p].sum; return; } pushdown(p); int mid=(t[p].l+t[p].r)/2; if(l<=mid)change1(p*2,l,r); if(r>mid)change1(p*2+1,l,r); t[p].sum=t[p*2].sum+t[p*2+1].sum; } long long query(int p){ if(t[p].l==t[p].r){ return b[t[p].l]; } pushdown(p); int mid=(t[p].l+t[p].r)/2; long long val; if(t[p*2].sum!=t[p*2].r-t[p*2].l+1)val=query(p*2); else if(t[p*2+1].sum!=t[p*2+1].r-t[p*2+1].l+1)val=query(p*2+1); t[p].sum=t[p*2].sum+t[p*2+1].sum; return val; } int main() { scanf("%d",&m); b[1]=1; for(int i=1;i<=m;i++){ scanf("%d%lld%lld",&a[i].opt,&a[i].l,&a[i].r); a[i].r++; b[++cnt]=a[i].l; b[++cnt]=a[i].r; } sort(b+1,b+cnt+1); int n=unique(b+1,b+cnt+1)-b-1; build(1,1,n); for(int i=1;i<=m;i++){ a[i].l=lower_bound(b+1,b+n+1,a[i].l)-b; a[i].r=lower_bound(b+1,b+n+1,a[i].r)-b; if(a[i].opt==1)change(1,a[i].l,a[i].r-1,1); else if(a[i].opt==2)change(1,a[i].l,a[i].r-1,0); else change1(1,a[i].l,a[i].r-1); printf("%lld\n",query(1)); } return 0; }
T2赛:
考试的时候错误贪心骗到了40分…
正解是枚举满足两个人都喜欢的物品的个数x,然后对于每个人再补上k-x个他喜欢的物品,不足m个就再从剩下的物品里补全。用线段树维护剩下的物品以支持查询前多少个最小值的和。
注意边界问题。
#include<iostream> #include<cstdio> #include<queue> #include<algorithm> using namespace std; int n,m,k,A,B,cnt,k1,k2; int v[200010],v1[200010],va[200010],vb[200010],val[200010]; int a[200010],b[200010],ab[200010],d[200010]; long long sum,ans=1e18,suma[200010],sumb[200010],sumab; struct node{ int l,r,cnt; long long sum; }t[1000010]; void build(int p,int l,int r){ t[p].l=l,t[p].r=r; if(l==r)return; int mid=(l+r)/2; build(p*2,l,mid); build(p*2+1,mid+1,r); } void change(int p,int l,int r,int y){ if(l<=t[p].l&&t[p].r<=r){ t[p].cnt+=y; t[p].sum+=y*(val[l]); return; } int mid=(t[p].l+t[p].r)/2; if(l<=mid)change(p*2,l,r,y); if(r>mid)change(p*2+1,l,r,y); t[p].cnt=t[p*2].cnt+t[p*2+1].cnt; t[p].sum=t[p*2].sum+t[p*2+1].sum; } long long query(int p,int y){ if(t[p].l==t[p].r){ return t[p].sum/t[p].cnt*y; } // int mid=(t[p].l+t[p].r)/2; if(t[p*2].cnt>y)return query(p*2,y); else if(t[p*2].cnt==y)return t[p*2].sum; else{ long long z=0; z+=t[p*2].sum; return z+query(p*2+1,y-t[p*2].cnt); } } int main() { scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=n;i++){ scanf("%d",&v[i]); val[++cnt]=v[i]; } sort(val+1,val+cnt+1); int num=unique(val+1,val+cnt+1)-val-1; scanf("%d",&A); for(int i=1,x;i<=A;i++){ scanf("%d",&x); va[x]=1; } scanf("%d",&B); for(int i=1,x;i<=B;i++){ scanf("%d",&x); vb[x]=1; } if(A<k||B<k||k>m||n<m){ printf("-1"); return 0; } for(int i=1;i<=n;i++){ v1[i]=lower_bound(val+1,val+num+1,v[i])-val; if(va[i]&&vb[i]){ ab[++ab[0]]=v1[i]; } else if(va[i]){ a[++a[0]]=v1[i]; } else if(vb[i]){ b[++b[0]]=v1[i]; } else d[++d[0]]=v1[i]; } if(k*2-ab[0]>m){ printf("-1"); return 0; } build(1,1,num); sort(ab+1,ab+ab[0]+1); sort(a+1,a+a[0]+1); sort(b+1,b+b[0]+1); sort(d+1,d+d[0]+1); for(int i=1;i<=a[0];i++){ suma[i]=suma[i-1]+val[a[i]]; if(i>k)change(1,a[i],a[i],1); } for(int i=1;i<=b[0];i++){ sumb[i]=sumb[i-1]+val[b[i]]; if(i>k)change(1,b[i],b[i],1); } // for(int i=1;i<=ab[0];i++){ // change(1,ab[i],ab[i],1); // } for(int i=1;i<=d[0];i++){ change(1,d[i],d[i],1); } for(int i=0;i<=min(ab[0],k);i++){ if(2*k-m>i||k-i>a[0]||k-i>b[0]){ if(i>0){ // change(1,ab[i],ab[i],-1); sumab+=val[ab[i]]; if(a[k-i+1])change(1,a[k-i+1],a[k-i+1],1); if(b[k-i+1])change(1,b[k-i+1],b[k-i+1],1); } } else if(i==0){ sum=suma[k]+sumb[k]; sum+=query(1,m-2*k); ans=min(sum,ans); } else{ // change(1,ab[i],ab[i],-1); sumab+=val[ab[i]]; if(a[k-i+1])change(1,a[k-i+1],a[k-i+1],1); if(b[k-i+1])change(1,b[k-i+1],b[k-i+1],1); sum=sumab+suma[k-i]+sumb[k-i]; sum+=query(1,m-i-2*(k-i)); ans=min(ans,sum); } } printf("%lld\n",ans); return 0; }
T3题:
对于每个苹果,考虑它如果要存活下来需要满足哪些条件,求出它如果存活需要ban掉的苹果的集合。最后统计答案,枚举两个苹果,如果它们都有机会存活且存活需要ban掉的集合没有交集,那么答案++。
于是一开始假设一个苹果最后活着,从后往前逆推出需要满足的集合。逆推的意义为,若要让现有集合存活到这个操作之后,需要让前面的集合满足什么样子。
如果一个操作的两个苹果都属于现有集合,那么作为目标存活到最后的苹果显然必死。如果一个操作中有一个苹果属于现有集合,那么为了让这个苹果存活下来以满足后面的操作,另一个苹果肯定属于这个操作之前的集合。
考试的时候想到了集合也想到了逆推,愣是没搞出来怎么写,大约是含义还有不明确的地方。
#include<iostream> #include<cstdio> #include<bitset> #include<cstring> using namespace std; int n,m,vis[410],flag,ans,x[50010],y[50010],num; bitset<410>a[410]; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ scanf("%d%d",&x[i],&y[i]); } for(int i=1;i<=n;i++){ a[i][i]=1; for(int j=m;j>=1;j--){ int u=x[j],v=y[j]; if(a[i][u]&&a[i][v]){ vis[i]=1; break; } else if(a[i][u]||a[i][v]){ a[i][u]=a[i][v]=1; } } } for(int i=1;i<=n;i++){ for(int j=i+1;j<=n;j++){ if(!vis[i]&&!vis[j]&&(a[i]&a[j]).count()==0)ans++; } } printf("%d",ans); return 0; }
几次考试暴露出来的问题越来越多,有点应付不过来。
趁着国庆集训赶紧追进度…不然怕是真的要在一个多月以后彻底结束了。
原文地址:https://www.cnblogs.com/chloris/p/11614792.html