csps模拟赛总结

youyou的垃圾桶

1.题意简述

现在有n个敌人,第i个敌人的初始攻击力为正整数ai。初始生命值为正整数W。定义如下流程为一场战斗:从第1个敌人开始,每个敌人依次循环进行攻击。第i个敌人发起攻击时,生命值W减去ai,同时ai翻倍。当W≤0时,本场战斗立刻结束。然后重置生命值W以及所有敌人的攻击力ai。定义本次战斗的评分为接受敌人攻击的次数(不包括致命攻击)。q次询问,每次询问给出三个数l,r,d,表示对第[l,r]个敌人进行强化,使每个敌人的ai增加d,然后立刻进行一场战斗。输出此次战斗的评分。询问之间相互影响。

2.大体思路

设所有垃圾桶当前攻击力总和为S。

考虑暴力,因为生命值W≤1018,攻击力是翻倍递增的,因此最多只会打logW轮。因此暴力的时间复杂度是O(nqlogW)。可以获得20分。假设这场战斗完整地打了k轮,那么这k轮需要消耗的生命值为S×(2^0+ 2^1+···+ (2k^−1)) =S×((2^k)−1)。即要求出最大的m,使得∑m i=1ai≤W−S×(2k−1)。答案即为k×n+m。

显然,对于每一次修改,S只会增加(ri−li+ 1)×di。对于每一个询问,我们需要求出最大的k。发现k不需要每次都枚举,因为每次操作后,答案只会变小,也就是k是递减的。对于相同的k,显然m也在递减。于是用指针维护m的值,同时用两个差分数组维护区间加即可。对于不同的k,暴力求解即可。

cpp 复制代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll read(){
	ll X = 0,r = 1;
	char ch = getchar();
	while(!isdigit(ch) && ch != '-') ch = getchar();
	if(ch == '-') r = -1,ch = getchar();
	while(isdigit(ch)) X = X*10+ch-'0',ch = getchar();
	return X*r;
}
const int N = 2e5+10;
int n,q;
ll W,a[N],t[N<<2],tag[N<<2];
#define ls rt<<1
#define rs rt<<1|1
inline void push_up(int rt){
	t[rt] = t[ls]+t[rs];
}
void build(int rt,int l,int r){
	if(l == r){
		t[rt] = a[l];
		return ;
	}
	int mid = (l+r) >> 1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	push_up(rt);
}
inline void change(int rt,int l,int r,ll d){
	t[rt] += (r-l+1)*d;
	tag[rt] += d;
}
inline void push_down(int rt,int l,int r){
	if(tag[rt]){
		int mid = (l+r) >> 1;
		change(ls,l,mid,tag[rt]);
		change(rs,mid+1,r,tag[rt]);
		tag[rt] = 0;
	}
}
void update(int rt,int l,int r,int ql,int qr,ll d){
	if(ql <= l && r <= qr) return change(rt,l,r,d);
	push_down(rt,l,r);
	int mid = (l+r) >> 1;
	if(ql <= mid) update(ls,l,mid,ql,qr,d);
	if(qr >  mid) update(rs,mid+1,r,ql,qr,d);
	push_up(rt);
}
int query(int rt,int l,int r,ll now,ll bas){
	if(l == r) return l-1;
	push_down(rt,l,r);
	int mid = (l+r) >> 1;
	if(bas*t[ls] < now) return query(rs,mid+1,r,now-bas*t[ls],bas);
	return query(ls,l,mid,now,bas);
}
void solve(){
	ll sum = t[1],ans = 0,now = W,bas = 1;
	while(now > sum){
		now -= sum;
		ans += n;
		sum *= 2;
		bas *= 2;
	}
	ans += query(1,1,n,now,bas);
	cout << ans << "\n";
}
int main(){
	n = read(); q = read(); W = read();
	for(int i=1;i<=n;i++) a[i] = read();
	build(1,1,n);
	while(q--){
		int l = read(),r = read();
		ll d = read();
		update(1,1,n,l,r,d);
		solve();
	}
	return 0;
}

youyou不喜欢夏天

1.考虑youyou选出的连通块左右端点被确定为l,r。显然,全黑列他会都去选择,全白列他只会选择一个格子,因为这些不受yy的影响。考虑一黑一白的列。假如他两个格子都选择,那么贡献为0,如果只选择一个黑色的格子,虽然贡献是1,但是可能被yy操作变成−1。于是他有两种策略:所有的一黑一白列我们都选择两个,这样yy没办法操作。将x个一黑一白列选择一个格子,其余选择两个。这样yy可以操作。

2.发现若youyou选择策略二为优,当且仅当至少有2m个一黑一白列他选择了一个格子。否则,我们可以将这些列选择两个格子,显然连通块仍连通,对答案的贡献为0;而原来对答案的贡献为x−2m<0。因此,youyou的策略二,可以视作在不考虑操作的情况下选出一个连通块。我们只需求这个连通块的最大权值最后减去2m,这一部分可以用dp实现。youyou的策略一是经典最大子段和问题,也可以使用dp实现。

cpp 复制代码
#include<bits/stdc++.h>
#define ffor(i,a,b) for(int i=(a);i<=(b);i++)
#define roff(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
const int MAXN=2e6+10;
int c,T,n,m,a[3][MAXN],dp[MAXN][4]; 
int main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>c>>T;
	while(T--) {
		cin>>n>>m;
		string S,T;
		cin>>S>>T;
		ffor(i,1,n) a[1][i]=S[i-1]-'0';
		ffor(i,1,n) a[2][i]=T[i-1]-'0';
		ffor(i,0,3) dp[0][i]=-0x3f3f3f3f;
		int ans=0;
		ffor(i,1,n) {
			int ad[4];
			ad[0]=0,ad[1]=2*a[1][i]-1,ad[2]=2*a[2][i]-1,ad[3]=2*a[1][i]+2*a[2][i]-2;	
			if(a[1][i]!=a[2][i]) ad[1]=ad[2]=-0x3f3f3f3f;
			ffor(j,0,3) dp[i][j]=ad[j];
			ffor(k,0,3) ffor(j,0,3) if(j&k) dp[i][j]=max(dp[i][j],dp[i-1][k]+ad[j]);
			ffor(j,0,3) ans=max(ans,dp[i][j]);
		}
		ffor(i,1,n) {
			int ad[4];
			ad[0]=0,ad[1]=2*a[1][i]-1,ad[2]=2*a[2][i]-1,ad[3]=2*a[1][i]+2*a[2][i]-2;	
			ffor(j,0,3) dp[i][j]=ad[j];
			ffor(k,0,3) ffor(j,0,3) if(j&k) dp[i][j]=max(dp[i][j],dp[i-1][k]+ad[j]);
			ffor(j,0,3) ans=max(ans,dp[i][j]-2*m);
		}
		cout<<ans<<'\n';
	}
	return 0;
}

youyou的序列II

1.首先进行特判,如果询问的区间中含有大于w1的数字,那么youyou显然必败。因为所有数均为非负整数,发现yy选择长度正好为c2的子区间是一定不劣的。以下称长度为c2,总和大于w2的子区间为"合法区间"。也即yy可以操作的区间。重要结论1:对于序列中不在任何一个"合法区间"内的数,一定不会对答案产生影响。对于这些数字,youyou可以用任意多次回合将它们染红,且yy无法重新将它们染蓝。而由于回合数是可视作无限的,youyou总有时间去将这些数字染色。

性质1:对于不存在任何一个"合法区间"的序列,youyou显然必胜。

性质2:存在"合法区间",若youyou可以一次性染红所有未染红的合法区间,则youyou必胜。

性质3:存在"合法区间",若youyou不可以做到一次性染红所有未染红的合法区间,则yy必胜。

40分:暴力求值 70分:暴力更新所有修改时受影响的区间 100分:线段树上二分求出l,r。

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
#define ffor(i,a,b) for(int i=(a);i<=(b);i++)
#define roff(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
const int MAXN=3e5+10;
int n,q,c1,c2,w1,w2,pre[MAXN],a[MAXN],opt[MAXN],x[MAXN],y[MAXN];
int lim[MAXN];
vector<int> upd[MAXN];
#define lson (k<<1)
#define rson (k<<1|1)
#define mid (l+r>>1)
int sum[MAXN<<2],mx[MAXN<<2];
void update_s(int k,int l,int r,int pos,int v) {
	if(l==r) return sum[k]+=v,void();
	if(pos<=mid) update_s(lson,l,mid,pos,v);
	else update_s(rson,mid+1,r,pos,v);
	return sum[k]=sum[lson]+sum[rson],void();
}
void update_m(int k,int l,int r,int pos,int v) {
	if(l==r) return mx[k]+=v,void();
	if(pos<=mid) update_m(lson,l,mid,pos,v);
	else update_m(rson,mid+1,r,pos,v);
	return mx[k]=max(mx[lson],mx[rson]),void();	
}
int bfind(int k,int l,int r,int lim) { //找到第一个 > lim 的位置 
	if(sum[k]<=lim) return r+1;
	if(l==r) return l;
	if(sum[lson]>lim) return bfind(lson,l,mid,lim);
	return bfind(rson,mid+1,r,lim-sum[lson]);
}
struct INFO {int sum,mx;};
INFO operator +(INFO A,INFO B) {return {A.sum+B.sum,max(A.mx,B.mx)};}
INFO query(int k,int l,int r,int x,int y) {
	if(x<=l&&r<=y) return {sum[k],mx[k]};
	if(y<=mid) return query(lson,l,mid,x,y);
	if(x>mid) return query(rson,mid+1,r,x,y);
	return query(lson,l,mid,x,y)+query(rson,mid+1,r,x,y);	
}
set<int> st;
vector<int> psl[MAXN];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>q>>c1>>c2>>w1>>w2;
	c1=min(c1,n),c2=min(c2,n);
	ffor(i,1,n) cin>>a[i],pre[i]=pre[i-1]+a[i];
	ffor(i,1,q) cin>>opt[i]>>x[i]>>y[i];
	ffor(i,1,q) if(opt[i]==1) upd[x[i]].push_back(i);
	ffor(i,1,c2) update_s(1,1,q,1,a[i]);
	ffor(i,1,c2) for(auto t:upd[i]) update_s(1,1,q,t,y[t]);
	ffor(i,1,n-c2+1) {
		lim[i]=bfind(1,1,q,w2);
		for(auto t:upd[i]) update_s(1,1,q,t,-y[t]);
		update_s(1,1,q,1,a[i+c2]-a[i]);
		for(auto t:upd[i+c2]) update_s(1,1,q,t,y[t]);
		psl[lim[i]].push_back(i);
	}
	memset(sum,0,sizeof(sum));
	ffor(i,1,n) update_s(1,1,n,i,a[i]),update_m(1,1,n,i,a[i]);
	ffor(i,1,q) {
		for(auto id:psl[i]) st.insert(id);
		if(opt[i]==2) {
			if(y[i]-x[i]+1<c2) {
				auto info=query(1,1,n,x[i],y[i]);
				int flg=1;
				if(info.mx>w1) flg=0;
				if(info.sum>w2) {
					if(info.sum>w1||c1<y[i]-x[i]+1) flg=0;	
				}
				if(flg) cout<<"cont\n";
				else cout<<"tetris\n";
			}
			else {
				int flg=1,l=x[i],r=y[i]-c2+1;
				auto it1=st.lower_bound(l),it2=st.upper_bound(r);
				if(it1!=it2) {
					it2--;
					int L=*it1,R=*it2;
					auto info=query(1,1,n,L,R+c2-1);
					if(c1<R+c2-1-L+1||info.sum>w1) flg=0;
				}
				auto info=query(1,1,n,x[i],y[i]);
				if(info.mx>w1) flg=0;
				if(flg) cout<<"cont\n";
				else cout<<"tetris\n";
			}
		}
		else update_s(1,1,n,x[i],y[i]),update_m(1,1,n,x[i],y[i]);
	}
	return 0;
}

youyou的三进制数

1.注意到3×10^5的三进制表示只有12位。考虑建图,发现题目所述与圆方树的性质很像,用tarjan跑出圆方树。圆方树最初是处理"仙人掌图"(每条边在不超过一个简单环中的无向图)的一种工具,不过发掘它的更多性质,有时我们可以在一般无向图上使用它。

2.维护的信息:

对于每个点,我们维护以下三个信息:

sumt表示若有一条以节点t为结尾的序列c,它的总贡献。

ssumt表示对于以节点t为根的整棵子树的总贡献。

sumsont表示对于节点t的所有儿子,它们的ssum之和。维护sumson数组是容易的,考虑如何维护sum数组和ssum数组。

3.由于这棵树的高度较低(本题中不会超过23),考虑对每一个圆点暴力往上跳,计算答案。对于当前跳到的点,我们考虑当前点的贡献和兄弟节点的贡献。有了预处理好的数组,这部分的计算就容易了。

相关推荐
有Li2 分钟前
跨视角差异-依赖网络用于体积医学图像分割|文献速递-生成式模型与transformer在医学影像中的应用
人工智能·计算机视觉
新加坡内哥谈技术22 分钟前
Mistral推出“Le Chat”,对标ChatGPT
人工智能·chatgpt
GOTXX31 分钟前
基于Opencv的图像处理软件
图像处理·人工智能·深度学习·opencv·卷积神经网络
IT古董35 分钟前
【人工智能】Python在机器学习与人工智能中的应用
开发语言·人工智能·python·机器学习
CV学术叫叫兽1 小时前
快速图像识别:落叶植物叶片分类
人工智能·分类·数据挖掘
WeeJot嵌入式1 小时前
卷积神经网络:深度学习中的图像识别利器
人工智能
脆皮泡泡1 小时前
Ultiverse 和web3新玩法?AI和GameFi的结合是怎样
人工智能·web3
机器人虎哥1 小时前
【8210A-TX2】Ubuntu18.04 + ROS_ Melodic + TM-16多线激光 雷达评测
人工智能·机器学习
码银2 小时前
冲破AI 浪潮冲击下的 迷茫与焦虑
人工智能
飞哥数智坊2 小时前
使用扣子实现一个文章收集智能体(升级版)
人工智能