2023校赛补题

打怪

题目描述:你在刷副本打怪,这个副本里只有一种怪,对战规则很简单,就是你砍它一刀,它砍你一刀,都是1v1,你先出手。你初始攻击力是1,防御力是0,血量是无穷。怪的防御力永远为0,只有攻击力AD和初始血量HP。双方每次受到攻击会掉(对手的攻击力-自己的防御力)的血量,如果自己防御更高,则不掉血。众所周知,打怪会爆金币,打死一个怪爆1金币,金币可以用于加点,攻击或防御提升1需要消耗1金币。你希望称霸副本。怎么才能算是称霸呢?你能毫发无损的把一个怪打死。那么,在你称霸副本之前最少受到多少伤害呢?

分析:“把dp的表打出来可以发现规律:一定是先连续加攻击再连续加防御。一个性质是攻击会加到血量的因数,枚举攻击加到多少即可”——题解

第二句话的意思就是,对于怪的血量HP,攻击力在[l,r]这个区间内时所需要的攻击轮数是一样的,那么我们先加攻击力时就只要加到l即可,然后再去把防御力增加到AD,计算这种情况下的承受伤害;然后下一次攻击力就直接加到r+1,重复这个操作直到攻击力加到HP,此时就不需要把防御力加到AD了,所有上述情况下的承受伤害取min即可。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define int __int128 
int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=1e5+5;
const int M=150005;
const int mod=1e9+7;
void write(int x) {
     if(x<0)putchar('-'),x=-x;
     if(x>9)write(x/10);
     putchar(x%10+'0');
}
signed main() {
	int HP,AD;
	AD=read();HP=read();
	if(HP==1){
		cout<<"0"<<endl;
		return 0;
	}
	int r,s=0,ans=1e38;
	for(int l=1;l<HP;l=r+1){
		int k=HP/l;//当前攻击力为l时,要打k轮才能消灭怪兽 
		if(HP%l==0)--k;//如果刚好整除,则还可以少打一轮 
		r=HP/k;//算出最大的攻击力,满足也是打k轮消灭怪兽,即攻击力在l到r之间都是打k轮 
		if(HP%k==0)--r;//一样的,如果恰好整除,说明r实际上只要打k-1轮,因此r-1 
		int cnt=s*AD+k*AD*(AD+1)/2;//前面是攻击力升到l所需承受伤害,后面是防御力升到AD所需承受伤害 
		s+=(r-l+1)*k;//攻击力从l升到r再到r+1所需攻击轮数,每次都是k轮 
		ans=min(ans,cnt); 
	}
	ans=min(ans,s*AD);//最后计算只升攻击力到HP的情况 
	//cout<<ans<<endl;
	write(ans); 
    return 0; 
}

端水大师

题目描述:有n个同学要来小Q家做客,他打算倒n杯水来招待他们。小Q先给每个杯子里倒上一些水,可是因为技术不佳,每杯水的水量可能有所差别,第i个杯子里有\(a_i\)毫升水,不过小Q希望每个人杯子里的水量差别不要太大。现在他每秒可以把一个杯子里的一毫升水倒到另一个杯子里,他想知道至少需要多少时间才能保证所有人杯子里的水量的二阶原点矩不超过x。二阶原点矩为\(\dfrac{\sum\limits_{i=1}^na_i^2}{n}\),即平方的均值。

分析:首先二分时间(操作次数)很好想到,这道题主要是check不太好写;如果一步一步操作,我们肯定知道每一次都是要把水量最大的倒入水量最小的,把杯子按照水量从小到大排序,然后我们在模拟这个操作的过程中,会发现如果我们改变(增加)了前m个杯子的水量,那么这m个杯子最终水量的最大差值为1,而且最小的水量不低于第m个杯子的水量,最大的水量不超过第m+1个杯子中的水量;同时对于最后M个杯子,一样地我们会改变(减少)水量,那么这M个杯子最终水量的最大差值为1,而且最大的水量不超过倒数第M个杯子的水量,最小的水量不低于倒数第M+1个杯子的水量。

发现了这个规律之后,我们在二分出时间mid之后,直接枚举我们改变(增加)前m个杯子的水量,设前m个杯子的原来总水量为sum,那么改变(增加)后这m个杯子的总水量就是sum+mid,那么这m个杯子的最终水量就是(sum+mid)/m或者(sum+mid)/m+1,在满足水量限制的情况(上述规律)下再去枚举后面M个杯子,进行类似的计算和操作,得到最终每个杯子的水量。

预处理出一般前缀和和平方前缀和之后,最终的二阶原点矩可以O(1)算出。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=1e5+5;
const int M=150005;
const int mod=1e9+7;
int n,a[N],b[N];
ll c[N];
ll pre[N],pre2[N];
bool check(ll mid,ll m){
	for(int i=1;i<=n;++i)c[i]=a[i];
	ll tot=pre2[n];
	for(int i=1;i<=n;++i){
		//if(b[i]<=c[i])break;
		ll now_sum1=pre[i]+mid;
		ll now_ave1=now_sum1/i;
		if(now_ave1>c[i+1])continue;
		if(now_ave1<c[i])break;
		ll now_ret1=now_sum1%i;
		for(int j=n;j>i;--j){
			//if(c[j]<=b[j])break;
			ll now_sum2=pre[n]-pre[j-1]-mid;
			ll now_ave2=now_sum2/(n-j+1);
			if(now_ave2<c[j-1])continue;
			if(now_ave2>c[j])break;
			ll now_ret2=now_sum2%(n-j+1);
			ll now_ans=tot-pre2[i]+(1ll*now_ave1*now_ave1*(i-now_ret1))+(1ll*(now_ave1+1)*(now_ave1+1)*now_ret1)-(pre2[n]-pre2[j-1])+(1ll*now_ave2*now_ave2*(n-j+1-now_ret2))+(1ll*(now_ave2+1)*(now_ave2+1)*now_ret2);
			if(now_ans<=m)return true;
		}
	}
	return false;
}
int main() {
	ll x;
	cin>>n>>x;
	ll m=1ll*n*x;
	ll sum=0;
	ll now=0;
	for(int i=1;i<=n;++i){
		a[i]=read();
		sum+=a[i];
		now+=1ll*a[i]*a[i];
	}
	if(now<=m){
		cout<<"0"<<endl;
		return 0;
	}
	int ave=sum/n;
	int ret=sum%n;
	ll minn=0;
	for(int i=1;i<=ret;++i){
		minn+=1ll*(ave+1)*(ave+1);
		b[i]=ave+1;
	}
	for(int i=ret+1;i<=n;++i){
		minn+=1ll*ave*ave;
		b[i]=ave;	
	}
	if(minn>m){
		cout<<"-1"<<endl;
		return 0;
	}
	sort(a+1,a+n+1);
	sort(b+1,b+n+1);
	for(int i=1;i<=n;++i){
		pre[i]=pre[i-1]+a[i];
		pre2[i]=pre2[i-1]+1ll*a[i]*a[i];
	}
	ll l=1,r=0;
	for(int i=1;i<=n;++i){
		if(a[i]<b[i])r+=(b[i]-a[i]);
	}
	ll ans;
	while(l<=r){
		ll mid=(l+r)/2;
		if(check(mid,m))ans=mid,r=mid-1;
		else l=mid+1;
	}
	cout<<ans<<endl;
    return 0; 
}

Andeviking开公司(hard)

分析:五个操作分别是单点修改、区间覆盖、区间修改、单点查询和区间查询,很容易想到要用线段树来维护,但是这里的区间操作维护对象是某节点同一深度的所有子节点,那什么情况下这些子节点才会是连续的一段区间呢?——树的bfs序。因此在建树之前,我们需要给树中的节点一个bfs序值,即可以在原序和bfs序之间一一映射即可。

然后考虑怎么求节点 x 的子树中比 x 深度大 k 的节点的 bfs 序区间。首先用vector来记录每个深度的所有节点,在bfs的时候记录那么这些节点的bfs序也是一段一段连续的,然后在深度为dep[x]+k的节点序列中二分,分别找出左右端点,使得这个区间内的k级祖先都是节点x。之所以可以二分还是因为bfs序,区间左边的节点的k级祖先的bfs序都小于x的bfs序,区间右边的节点的k级祖先的bfs序都大于x的bfs序。

然后就是注意一下区间覆盖操作有点特殊,连带着延迟标记及其下穿操作也要稍加修改。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=1e5+5;
const int M=2e5+5;
const int mod=1e9+7;
const int inf=1e9;
int n,maxdep[N],a[N],q[N],dep[N],num1[N],num2[N],f[N][21];
int tot,head[N],nxt[M],to[M];
vector<int>p[N];
struct node{
	int l,r,tag;
	ll sum,add;
}t[N<<2];
void add(int a,int b){
	nxt[++tot]=head[a];head[a]=tot;to[tot]=b;
}
void bfs(){
	q[1]=1;dep[1]=1;num1[1]=1;num2[1]=1;
	p[1].push_back(1);
	int l=0,r=1;
	while(l<r){
		int u=q[++l];
		for(int j=1;j<=20;++j)f[u][j]=f[f[u][j-1]][j-1];
		for(int i=head[u];i;i=nxt[i]){
			int v=to[i];
			if(dep[v])continue;
			dep[v]=dep[u]+1;
			p[dep[v]].push_back(v);
			q[++r]=v;
			num1[v]=r;
			num2[r]=v;
			f[v][0]=u;
		}
	}
}
void dfs(int u,int fa){
	maxdep[u]=dep[u];
	for(int i=head[u];i;i=nxt[i]){
		int v=to[i];
		if(v==fa)continue;
		dfs(v,u);
		maxdep[u]=max(maxdep[u],maxdep[v]);
	}
}
void build(int p,int l,int r){
	t[p].l=l;t[p].r=r;t[p].add=0;t[p].tag=inf;
	if(l==r){
		t[p].sum=a[num2[l]];
		return;
	}
	int mid=(l+r)/2;
	build(p<<1,l,mid);
	build((p<<1)+1,mid+1,r);
	t[p].sum=t[p<<1].sum+t[(p<<1)+1].sum;
}
void spread(int p){
	if(t[p].l==t[p].r)return;
	if(t[p].tag!=inf) {
        t[p*2].sum=1ll*t[p].tag*(t[p*2].r-t[p*2].l+1);
        t[p*2+1].sum=1ll*t[p].tag*(t[p*2+1].r-t[p*2+1].l+1);
        t[p*2].tag=t[p*2+1].tag=t[p].tag;
        t[p*2+1].add=t[p*2].add=0;
    }
 	t[p*2].sum+=1ll*t[p].add*(t[p*2].r-t[p*2].l+1);
	t[p*2+1].sum+=1ll*t[p].add*(t[p*2+1].r-t[p*2+1].l+1);
    t[p*2].add+=t[p].add;
    t[p*2+1].add+=t[p].add;
    t[p].add=0;
    t[p].tag=inf;
}
void change(int p,int x,int y,int v,int op){
    if(x<=t[p].l&&y>=t[p].r){
    	if(op==1){
    		t[p].sum=1ll*v*(t[p].r-t[p].l+1);
    		t[p].add=0;
    		t[p].tag=v;
		}
		else{
			t[p].sum+=1ll*v*(t[p].r-t[p].l+1);
    		t[p].add+=v;
		}
    	return;
    }
    spread(p);
    int mid=(t[p].l+t[p].r)/2;
    if(x<=mid)change(p*2,x,y,v,op);
    if(y>mid)change(p*2+1,x,y,v,op);
    t[p].sum=t[p*2].sum+t[p*2+1].sum;
}
ll ask(int p,int l,int r){
    if(l<=t[p].l&&r>=t[p].r)return t[p].sum;
    spread(p);
    int mid=(t[p].l+t[p].r)/2;
    ll ans=0;
    if(l<=mid)ans+=ask(p*2,l,r);
    if(r>mid)ans+=ask(p*2+1,l,r);
    return ans;
}
int check(int mid,int depth,int k){
	int x=p[depth][mid];
	for(int i=20;i>=0;--i){
		if((1<<i)&k)x=f[x][i];
	}
	return num1[x];
}
int main() {
	n=read();
	for(int i=1;i<=n;++i)a[i]=read();
	for(int i=1;i<n;++i){
		int x=read(),y=read();
		add(x,y);add(y,x);
	}
	bfs();
	dfs(1,0);
	build(1,1,n);
	int Q=read();
	while(Q--){
		int op=read();
		if(op==1){//单点修改 
			int pp=read(),x=read();
			change(1,num1[pp],num1[pp],x,1);
		}
		else if(op==2){//区间覆盖 
			int pp=read(),k=read(),x=read();
			int depth=dep[pp]+k;
			if(depth>maxdep[pp])continue;
			int l1=0,r1=p[depth].size()-1,l,r;
			if(r1==-1)continue;
			while(l1<=r1){
				int mid=(l1+r1)/2;
				if(check(mid,depth,k)>=num1[pp])l=mid,r1=mid-1;
				else l1=mid+1;
			}
			l1=0,r1=p[depth].size()-1;
			while(l1<=r1){
				int mid=(l1+r1)/2;
				if(check(mid,depth,k)<=num1[pp])r=mid,l1=mid+1;
				else r1=mid-1;
			}
			change(1,num1[p[depth][l]],num1[p[depth][r]],x,1);
		}
		else if(op==3){//区间修改 
			int pp=read(),k=read(),x=read();
			int depth=dep[pp]+k;
			if(depth>maxdep[pp])continue;
			int l1=0,r1=p[depth].size()-1,l,r;
			if(r1==-1)continue;
			while(l1<=r1){
				int mid=(l1+r1)/2;
				if(check(mid,depth,k)>=num1[pp])l=mid,r1=mid-1;
				else l1=mid+1;
			}
			l1=0,r1=p[depth].size()-1;
			while(l1<=r1){
				int mid=(l1+r1)/2;
				if(check(mid,depth,k)<=num1[pp])r=mid,l1=mid+1;
				else r1=mid-1;
			}
			change(1,num1[p[depth][l]],num1[p[depth][r]],x,2);
		}
		else if(op==4){//单点查询 
			int pp=read();
			cout<<ask(1,num1[pp],num1[pp])<<endl;
		}
		else{//区间查询 
			int pp=read(),k=read();
			int depth=dep[pp]+k;
			if(depth>maxdep[pp]){
				cout<<"0"<<endl;
				continue;
			}
			int l1=0,r1=p[depth].size()-1,l,r;
			if(r1==-1){
				cout<<"0"<<endl;
				continue;
			} 
			while(l1<=r1){
				int mid=(l1+r1)/2;
				if(check(mid,depth,k)>=num1[pp])l=mid,r1=mid-1;
				else l1=mid+1;
			}
			l1=0,r1=p[depth].size()-1;
			while(l1<=r1){
				int mid=(l1+r1)/2;
				if(check(mid,depth,k)<=num1[pp])r=mid,l1=mid+1;
				else r1=mid-1;
			}
			cout<<ask(1,num1[p[depth][l]],num1[p[depth][r]])<<endl;
		} 
	}
    return 0; 
}

posted on 2023-06-04 13:13  PPXppx  阅读(10)  评论(0编辑  收藏  举报