牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ (nowcoder.com)
一共有n天,每天学校有ri个教室可供租借,给你一系列租借订单,问你所有订单是否都可满足,若不满足,求出从第几个计划开始不满足。
每个计划的格式是:从第L天到第R天,租借x个房间
二分+树状数组
树状数组用来维护每一天的空余教室数,即差分用法
每次二分一个计划编号mid,将mid以前的所有订单都维护进树状数组,然后检查每一天的空余教室数是否存在小于0的情况,如存在,说明到这个订单已不能满足,向前二分,否则向后二分。
如果一直找不到不合法情况,则说明所有订单可满足,否则二分出来的ans即为第一个不满足的订单
#include
using namespace std;
typedef long long ll;
inline int R(){int a=0,b=1;char c=getchar();while(c<'0'||c>'9'){if(c=='-')b=-1;c=getchar();}while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}return a*b;}
int n,m;
int a[1000010],c[1000010];
struct query{
int d,l,r;
}q[1000010];
int lb(int x){
return x&(-x);
}
void Add(int x,int d){
for(int i=x;i<=n;i+=lb(i)){
c[i]+=d;
}
}
int Sum(int x){
int ans=0;
for(int i=x;i>0;i-=lb(i)){
ans+=c[i];
}
return ans;
}
void init(){
for(int i=1;i<=n;++i){
Add(i,a[i]); // 初始化,将a[i]转移至树状数组c[i]
Add(i+1,-a[i]);
}
}
int last;
bool check(int now){
if(now>last){
for(int i=last+1;i<=now;++i){
Add(q[i].l,-q[i].d);
Add(q[i].r+1,q[i].d);
}
}else if(last>now){
for(int i=last;i>=now+1;--i){
Add(q[i].l,q[i].d);
Add(q[i].r+1,-q[i].d);
}
}
last=now;
for(int i=1;i<=n;++i){
if(Sum(i)<0)return false;
}
return true;
}
int main(){
n=R();m=R();
for(int i=1;i<=n;++i){
a[i]=R();
}
init();
for(int i=1;i<=m;++i){
q[i].d=R();q[i].l=R();q[i].r=R();
}
int L=0,R=m,mid,ans=-1;
while(L<=R){
mid=L+R>>1;
if(check(mid)){
L=mid+1;
}else{ // 不合法
R=mid-1;
ans=mid;
}
}
if(ans==-1)printf("0");
else printf("%d\n%d",-1,ans);
return 0;
}
给一个序列,m次询问,每次询问一个区间内不同数的个数
离线+树状数组
将询问存下来,按照右端点从小到大排序
维护树状数组:
第一次碰到一个数,直接在这个位置加1
若非第一次,将这个数上一次出现的位置减1,在当前位置加1,
如此一来,对于询问[L,R],ans=sum®-sum(L-1)
#include
using namespace std;
typedef long long ll;
inline int R(){int a=0,b=1;char c=getchar();while(c<'0'||c>'9'){if(c=='-')b=-1;c=getchar();}while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}return a*b;}
int n,m,a[50010],la[1000010],ans[200010];
struct query{
int l,r,id;
}q[200010];
int c[50010];
int lb(int x){
return x&(-x);
}
void Add(int x,int d){
for(int i=x;i<=n;i+=lb(i)){
c[i]+=d;
}
}
int Sum(int x){
int ans=0;
for(int i=x;i>0;i-=lb(i)){
ans+=c[i];
}
return ans;
}
void init(){
for(int i=1;i<=n;++i){
Add(i,a[i]); // 初始化,将a[i]转移至树状数组c[i]
}
}
bool cmp(query a,query b){
return a.r<b.r;
}
int main(){
n=R();
for(int i=1;i<=n;++i){
a[i]=R();
}
m=R();
for(int i=1;i<=m;++i){
q[i].l=R();q[i].r=R();
q[i].id=i;
}
sort(q+1,q+1+m,cmp);
int j=1;
for(int i=1;i<=n;++i){
if(la[a[i]])Add(la[a[i]],-1);
Add(i,1);
la[a[i]]=i;
while(i==q[j].r){
ans[q[j].id]=Sum(i)-Sum(q[j].l-1);
++j;
}
}
for(int i=1;i<=m;++i){
printf("%d\n",ans[i]);
}
return 0;
}
与上题类似,给定一个数组,m次询问,每次询问区间内【数字个数】大于1的【数字】的个数
离线+树状数组
将询问按左端点从小到大排序
一开始初始化一个链表,每个位置的next指向和当前位置数字相同的下一个位置
然后把每一种数字第二次出现的地方在树状数组里加1
要处理[L,R]的询问时,将L之前的数字全部处理掉:
从前往后遍历,每遇到一个数字,如果这个数字在后面还出现过,那么next一定指向它,且去掉当前数后,next指向的数将成为第一次出现,而不是第二次,所以next指向的位置减1(树状数组中减1)
若next还存在next,则将其所在位置加1,因为它成了新的第二次出现
#include
using namespace std;
inline int R(){int a=0,b=1;char c=getchar();while(c<'0'||c>'9'){if(c=='-')b=-1;c=getchar();}while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}return a*b;}
const int MAXN=2000010;
int N,C,M;
int c[MAXN];
int a[MAXN],head[MAXN],Next[MAXN],ans[MAXN];
struct node{
int l,r,id;
}q[MAXN];
bool cmp(node a,node b){
return a.l<b.l;
}
void add(int x,int d){
for(int i=x;i<=N;i+=i&(-i)){
c[i]+=d;
}
}
int sum(int x){
int ans=0;
for(int i=x;i>=1;i-=i&(-i)){
ans+=c[i];
}
return ans;
}
int main(){
N=R();C=R();M=R();
for(int i=1;i<=N;++i){
a[i]=R();
}
for(int i=N;i>=1;--i){
Next[i]=head[a[i]];
head[a[i]]=i;
}
for(int i=1;i<=M;++i){
q[i].l=R();q[i].r=R();
q[i].id=i;
}
for(int i=1;i<=C;++i){
int t=Next[head[i]];
if(t){
add(t,1);
}
}
sort(q+1,q+1+M,cmp);
int now=1;
for(int i=1;i<=M;++i){
int l=q[i].l;
while(now<l){
int t=Next[now];
if(t){
add(t,-1);
if(Next[t]){
add(Next[t],1);
}
}
now++;
}
ans[q[i].id]=sum(q[i].r);
}
for(int i=1;i<=M;++i){
printf("%d\n",ans[i]);
}
return 0;
}
线段树板子题
1 l r 询问区间[l,r]内的元素和
2 l r 询问区间[l,r]内的元素的平方 和
3 l r x 将区间[l,r]内的每一个元素都乘上x
4 l r x 将区间[l,r]内的每一个元素都加上x
熟悉一下线段树的结构,模块化实现,细心
#include
using namespace std;
#define ls i<<1
#define rs i<<1|1
typedef long long ll;
inline ll R(){ll a=0,b=1;char c=getchar();while(c<'0'||c>'9'){if(c=='-')b=-1;c=getchar();}while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}return a*b;}
ll n,m,a[10010];
struct Tree{
ll l,r,s1,s2;
ll sum,mul;
}o[10010*4];
ll ans;
void pushup(ll i){
o[i].s1=o[ls].s1+o[rs].s1;
o[i].s2=o[ls].s2+o[rs].s2;
}
void pushdown(ll i){
ll m=o[i].mul,s=o[i].sum;
if(m!=1){
o[ls].mul*=m;
o[rs].mul*=m;
o[ls].s2*=m*m;
o[rs].s2*=m*m;
o[ls].s1*=m;
o[rs].s1*=m;
o[i].mul=1;
}
ll ln=o[ls].r-o[ls].l+1;
ll rn=o[rs].r-o[rs].l+1;
if(o[i].sum){
o[ls].sum+=s;
o[rs].sum+=s;
o[ls].s2+=2*o[ls].s1*s+ln*s*s;
o[rs].s2+=2*o[rs].s1*s+rn*s*s;
o[ls].s1+=ln*s;
o[rs].s1+=rn*s;
o[i].sum=0;
}
}
void build(ll i,ll L,ll R){ //初始化
o[i].l=L;o[i].r=R;
o[i].sum=0;
o[i].mul=1;
if(L==R){
o[i].s1=a[L];
o[i].s2=a[L]*a[L];
return;
}
ll mid=L+R>>1;
build(ls,L,mid);
build(rs,mid+1,R);
pushup(i);
}
ll Ask(ll i,ll L,ll R,ll op){ //询问区间
if(R<o[i].l||L>o[i].r)return 0;
if(L<=o[i].l&&o[i].r<=R){
if(op==1){
return o[i].s1;
}else{
return o[i].s2;
}
}
ll ans=0;
pushdown(i);
ans+=Ask(i<<1,L,R,op);
ans+=Ask(i<<1|1,L,R,op);
return ans;
}
void mul(ll i,ll L,ll R,ll d){ //修改区间
ll l=o[i].l,r=o[i].r;
if(R<l||L>r)return;
if(L<=l&&r<=R){
o[i].s1*=d;
o[i].s2*=d*d;
o[i].mul*=d;
o[i].sum*=d;
return;
}
pushdown(i);
mul(ls,L,R,d);
mul(rs,L,R,d);
pushup(i);
}
void add(ll i,ll L,ll R,ll d){ //修改区间
ll l=o[i].l,r=o[i].r;
if(R<l||L>r)return;
if(L<=l&&r<=R){
o[i].s2+=2*o[i].s1*d+(r-l+1)*d*d;
o[i].s1+=(r-l+1)*d;
o[i].sum+=d;
return;
}
pushdown(i);
add(ls,L,R,d);
add(rs,L,R,d);
pushup(i);
}
int main(){
n=R();m=R();
for(ll i=1;i<=n;++i){
a[i]=R();
}
build(1,1,n);
while(m--){
ll op=R();
ll l,r,x;
switch(op){
case 1:{
l=R();r=R();
printf("%lld\n",Ask(1,l,r,1));
break;
}
case 2:{
l=R();r=R();
printf("%lld\n",Ask(1,l,r,2));
break;
}
case 3:{
l=R();r=R();x=R();
mul(1,l,r,x);
break;
}
case 4:{
l=R();r=R();x=R();
add(1,l,r,x);
break;
}
}
}
return 0;
}
板子题加一丢丢的答案处理
1 l r v,区间a[l]到a[r]的所有数字全部加v。
2 l r v,区间a[l]到a[r]的所有数字全部乘v。
3 l r,求区间a[l]到a[r]之间两两之间数字的乘积和(例如:2,3,4,5两两之间乘积和为 2*3+2*4+2*5+3*4+3*5+4*5)
只需要维护区间和sum以及平方和pf,即可
最后的答案为 (sum*sum-pf)/2
需要用到逆元,由于模数是质数,所以直接用费马小定理
#include
using namespace std;
typedef long long ll;
inline ll R(){ll a=0,b=1;char c=getchar();while(c<'0'||c>'9'){if(c=='-')b=-1;c=getchar();}while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}return a*b;}
const ll MAXN=100010;
#define ls i<<1
#define rs i<<1|1
ll a[MAXN],n,m,p;
struct Tree{
ll l,r,sum,pf;
ll add,mul; //懒标记
}o[MAXN<<2];
ll KSM(ll a,ll b){ //快速幂,计算a^b
ll ans=1;
while(b){
if(b&1)ans=(__int128)ans*a%p; //if(b%2==1)
a=(__int128)a*a%p;
b>>=1; //此处等价于power=power/2
}
return ans;
}
void pushup(ll i){
o[i].sum=(o[ls].sum+o[rs].sum)%p;
o[i].pf=(o[ls].pf+o[rs].pf)%p;
}
void deal_add(ll i,ll d){ //懒标记处理
ll r=o[i].r,l=o[i].l;
o[i].pf=(o[i].pf+2*o[i].sum%p*d%p+d*d%p*(r-l+1)%p)%p;
o[i].sum=(o[i].sum+(r-l+1)*d%p)%p;
o[i].add=(d+o[i].add)%p;
}
void deal_mul(ll i,ll d){ //懒标记处理
ll r=o[i].r,l=o[i].l;
o[i].pf=o[i].pf*d%p*d%p;
o[i].sum=o[i].sum*d%p;
o[i].mul=o[i].mul*d%p;
o[i].add=o[i].add*d%p;
}
void pushdown(ll i){ //标记下传
ll s=o[i].add,m=o[i].mul;
ll ln=o[ls].r-o[ls].l+1;
ll rn=o[rs].r-o[rs].l+1;
if(m!=1){
deal_mul(ls,m);
deal_mul(rs,m);
o[i].mul=1;
}
if(s){
deal_add(ls,s);
deal_add(rs,s);
o[i].add=0;
}
}
void build(ll i,ll L,ll R){ //初始化
o[i].l=L;o[i].r=R;
o[i].add=0;
o[i].mul=1;
if(L==R){
o[i].sum=a[L]%p; //叶子节点信息
o[i].pf=a[L]*a[L]%p;
return;
}
ll mid=L+R>>1;
build(ls,L,mid);
build(rs,mid+1,R);
pushup(i);
}
ll Ask(ll i,ll L,ll R,ll op){ //询问区间
ll l=o[i].l,r=o[i].r;
if(R<l||L>r)return 0;
if(L<=l&&r<=R){
if(op==1)return o[i].sum;
else return o[i].pf;
}
ll ans=0;
pushdown(i);
ans+=Ask(ls,L,R,op);
ans+=Ask(rs,L,R,op);
return ans%p;
}
void Add(ll i,ll L,ll R,ll d){ //修改区间
ll l=o[i].l,r=o[i].r;
if(R<l||L>r)return;
if(L<=l&&r<=R){
deal_add(i,d);
return;
}
pushdown(i);
Add(ls,L,R,d);
Add(rs,L,R,d);
pushup(i);
}
void Mul(ll i,ll L,ll R,ll d){ //修改区间
ll l=o[i].l,r=o[i].r;
if(R<l||L>r)return;
if(L<=l&&r<=R){
deal_mul(i,d);
return;
}
pushdown(i);
Mul(ls,L,R,d);
Mul(rs,L,R,d);
pushup(i);
}
int main(){
// freopen("R.txt","r",stdin);
ll T=R();
while(T--){
n=R();m=R();p=R();
for(ll i=1;i<=n;++i) a[i]=R();
build(1,1,n);
ll l,r,v;
for(ll i=1;i<=m;++i){
ll op=R();
switch(op){
case 1:{
l=R();r=R();v=R();
Add(1,l,r,v);
break;
}
case 2:{
l=R();r=R();v=R();
Mul(1,l,r,v);
break;
}
case 3:{
l=R();r=R();
ll sum=Ask(1,l,r,1);
ll mull=Ask(1,l,r,2);
printf("%lld\n",((sum*sum%p-mull+p)%p*KSM(2,p-2)%p+p)%p);
break;
}
}
}
}
return 0;
}
有n堆鸡蛋,n个桶,每个桶的容量是m,每个桶最多装k堆鸡蛋,求每一堆鸡蛋装进的桶
每堆鸡蛋将放入编号最小的可放入的桶,若放不了输出-1
这道题只是用到了线段树的结构和pushup,没有修改和查值操作
维护已装鸡蛋的最小值,若已装了k堆,则直接将桶的已装鸡蛋数赋为m表示不能再装
想象线段树的结构,若左边有桶能装下我,则进入左子树,否则进入右子树
#include
using namespace std;
typedef long long ll;
inline ll R(){ll a=0,b=1;char c=getchar();while(c<'0'||c>'9'){if(c=='-')b=-1;c=getchar();}while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}return a*b;}
#define ls i<<1
#define rs i<<1|1
const ll MAXN=300010;
ll n,m,k;
struct Tree{
ll l,r,mi,cnt;
}o[MAXN<<2];
inline ll Min(ll a,ll b){
return (a<b?a:b);
}
void pushup(ll i){
o[i].mi=Min(o[ls].mi,o[rs].mi);
}
void build(ll i,ll L,ll R){ //初始化
o[i].l=L;o[i].r=R;
if(L==R){
o[i].mi=0; //叶子节点信息
o[i].cnt=0;
return;
}
ll mid=L+R>>1;
build(ls,L,mid);
build(rs,mid+1,R);
pushup(i);
}
ll findpos(ll i,ll d){ //询问区间
if(o[i].l==o[i].r){
if(o[i].mi+d<=m&&o[i].cnt<k){
o[i].mi+=d;
o[i].cnt++;
if(o[i].cnt==k)o[i].mi=m;
return o[i].l;
}else{
return -1;
}
}
ll ans=-1;
bool bj=0;
if(o[ls].mi+d<=m){
ans=findpos(ls,d);
}else{
ans=findpos(rs,d);
}
pushup(i);
return ans;
}
int main(){
// freopen("R.txt","r",stdin);
ll T=R();
while(T--){
n=R();m=R();k=R();
build(1,1,n);
for(ll i=1;i<=n;++i){
ll t=R();
ll pos=findpos(1,t);
printf("%lld\n",pos);
}
}
return 0;
}