24.7.14(板刷数据结构,警钟长鸣)

上周六:

算是暑假训练第一天,期末考完了真好

cf round951 div2 D cf传送门

之前用正解补过,又臭又长,写完就跑了,这次用哈希补一发

思路:目标字符串就两种可能,0开头和1开头,所以可直接算出目标子串的hash值,为num

因为是双哈希,所以东西较多,ha11,第一个1表示正着hash或反着hash,第二个1表示第一套base和mod或第二套,num01,第一个0表示0或1开头,第二个1表示第一套或第二套base,mod

遍历答案,例如字符串长为9,123456789,i为3,那么应该变为 456789 321,用处理出来的hash数组拼凑出来,然后和 num值进行比较即可

代码如下:

cpp 复制代码
const int N=2e5+10;
const int mod1=1e9+7,mod2=998244353;
ll n;
ll p1[N],p2[N],ha11[N],ha12[N],ha21[N],ha22[N];
const int ba1=13331,ba2=131;
void solve(){
	int k; cin >> n >> k;
	string s; cin >> s; s=" "+s;
	ll num01=0,num02=0,num11=0,num12=0;
	p1[0]=p2[0]=1;
	for(int i=1;i<=n;i++){
		p1[i]=p1[i-1]*ba1%mod1;
		p2[i]=p2[i-1]*ba2%mod2;
		ha11[i]=(ha11[i-1]*ba1+s[i])%mod1;
		ha12[i]=(ha12[i-1]*ba2+s[i])%mod2;
		if((i-1)/k&1){
			num01=(num01*ba1+'1')%mod1;
			num02=(num02*ba2+'1')%mod2;
			num11=(num11*ba1+'0')%mod1;
			num12=(num12*ba2+'0')%mod2;
		}else{
			num01=(num01*ba1+'0')%mod1;
			num02=(num02*ba2+'0')%mod2;
			num11=(num11*ba1+'1')%mod1;
			num12=(num12*ba2+'1')%mod2;
		}
	}
	ha21[n+1]=ha22[n+1]=0;
	for(int i=n;i;i--){
		ha21[i]=(ha21[i+1]*ba1+s[i])%mod1;
		ha22[i]=(ha22[i+1]*ba2+s[i])%mod2;
	}
	for(int i=1;i<=n;i++){
		ll ha1=(ha11[n]-ha11[i]*p1[n-i]%mod1+mod1)%mod1*p1[i]%mod1;
		ll ha2=(ha12[n]-ha12[i]*p2[n-i]%mod2+mod2)%mod2*p2[i]%mod2;
		ha1+=(ha21[1]-ha21[i+1]*p1[i]%mod1+mod1)%mod1,ha1%=mod1;
		ha2+=(ha22[1]-ha22[i+1]*p2[i]%mod2+mod2)%mod2,ha2%=mod2;
		if((ha1==num01&&ha2==num02) || (ha1==num11&&ha2==num12)){cout << i << "\n"; return ;}
	}
	cout << "-1\n";
}

上周日:

补cf round 944 div4 F cf传送门

思路:从1到r,二分找范围内点集的上下界,注意判断边界情况

代码如下:

cpp 复制代码
ll n;
void solve(){
	cin >> n;
	ll ans=0;
	for(int i=1;i<=n;i++){
		ll l=0,r=i,res1=-1;
		while(l<=r){
			ll mid=l+r>>1;
			ll y=mid;
			double dis=sqrt(1.0*i*i+1.0*y*y);  // >=r
			if(dis<n) l=mid+1;
			else res1=mid,r=mid-1;
		}
		l=0,r=i;ll res2=-1;
		while(l<=r){
			ll mid=l+r>>1;
			ll y=mid;
			double dis=sqrt(1.0*i*i+1.0*y*y);  // < r+1
			if(dis>=n+1) r=mid-1;
			else res2=mid,l=mid+1;
		}
		if(res1==-1 || res2==-1) continue;
		if(!res1) ans+=4,res1++;
		if(res2==i) ans-=4;
		ans+=(res2-res1+1)*8;
	}
	cout << ans << "\n";
}

板刷构造 1600 cf传送门

题意:构造排列使得任意相邻数的差值在 2-4间

思路:从奇偶性角度考虑,俩相邻奇数或偶数差值即2,先顺着放奇数,然后放一个和最大奇数匹配的偶数即 ma1-3,然后逆着放偶数,最大的差值刚好为4

代码如下:

cpp 复制代码
ll n;

void solve(){
	cin >> n;
	if(n<4){cout << "-1\n"; return ;}
	if(n==4){cout << "3 1 4 2\n"; return ;}
	int ma1=n&1?n:n-1,ma0=n&1?n-1:n;        //最大的奇数和偶数
	int ou=ma1-3;
	for(int i=1;i<=n;i+=2) cout << i << " ";
	cout << ou << " ";
	for(int i=ma0;i;i-=2){
		if(i==ou) continue;
		cout << i << " ";
	}
	cout << "\n";
}

周一:

上午个人赛

补ABC 131 E 构造好题 atc传送门

题意:构造一个图,n个点且满足有 k个点对最短距离为2

思路:n很小,且对边数无限制,可构造一个菊花图,此时满足距离为2的点对数为C n-1 2,即 (n-1)*(n-2)/ 2,这是最多点对的情况,若小于 k输出 -1,若大于 k,在任意两点间连接边,则这两点不再满足即点对数减一,添边直至点对==k

代码如下:

cpp 复制代码
ll n;
void solve(){
	int k; cin >> n >> k;
	ll p=(n-1)*(n-2)/2;
	if(p<k){cout << -1; return ;}
	vector<int>ve[110];
	ll sum=0;
	for(int i=1;i<n;i++) ve[n].push_back(i),sum++;
	for(int i=1;i<n && p>k;i++){
		for(int j=i+1;j<n && p>k;j++){
			ve[i].push_back(j);
			sum++;
			p--;
		}
	}
	cout << sum << "\n";
	for(int i=1;i<=n;i++)
		for(auto j:ve[i]) 
			cout << i << " " << j << "\n";
}

牛客板刷线段树 题一 牛客传送门

读错题加上脑子抽了一度卡壳,其实基础线段树题

思路:1到n天,对每个订单检查能否满足,即从 s到 t剩余教室数是否都大于 d,可维护最小值,满足后再更新 s到 t天的剩余教室数

代码如下:

cpp 复制代码
const int N=2e6+10;
ll n;
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
	struct nod{
		int l,r;
		ll mi,add;
	}t[N];
	ll ql,qr,a[N];
	nod merge(nod a,nod b){
		nod res;
		res.l=a.l,res.r=b.r;
		res.mi=min(a.mi,b.mi);
		res.add=0;
		return res;
	}
	void pushup(int p){t[p]=merge(t[lc],t[rc]);}
	void pushdn(int p){
		if(!t[p].add) return ;
		t[lc].mi+=t[p].add;
		t[rc].mi+=t[p].add;
		t[lc].add+=t[p].add;
		t[rc].add+=t[p].add;
		t[p].add=0;
	}
	void bd(int p,int l,int r){
		t[p]={l,r,0,0};
		if(l==r){
			t[p].mi=a[l];
			return ;
		}
		int m=l+r>>1;
		bd(lc,l,m);
		bd(rc,m+1,r);
		pushup(p);
	}
	void update(int p,int v){
		if(ql<=t[p].l && qr>=t[p].r){
			t[p].mi+=v;
			t[p].add+=v;
			return ;
		}
		int m=t[p].l+t[p].r>>1;
		pushdn(p);
		if(ql<=m) update(lc,v);
		if(qr>m) update(rc,v);
		pushup(p);
	}
	nod query(int p){
		if(ql<=t[p].l && qr>=t[p].r) return t[p];
		int m=t[p].l+t[p].r>>1;
		pushdn(p);
		if(ql>m) return query(rc);
		if(qr<=m) return query(lc);
		return merge(query(lc),query(rc));
	}
	void updt(int l,int r,int v){
		ql=l,qr=r;
		update(1,v);
	}
	ll ask_min(int l,int r){
		ql=l,qr=r;
		return query(1).mi;
	}
#undef lc
#undef rc
}tr;
void solve(){
	int m; cin >> n >> m;
	for(int i=1;i<=n;i++) cin >> tr.a[i];
	tr.bd(1,1,n);
	for(int i=1;i<=m;i++){
		int d,s,t; cin >> d >> s >> t;
		if(tr.ask_min(s,t)<d){cout << "-1\n" << i; return ;}
		tr.updt(s,t,-d);
	}
	cout << 0;
}

板刷数据结构 题二 数贝壳 牛客传送门

长见识了

思路:有很多种做法,这里使用的是树状数组。首先需要把查询记录下来离线处理,按 r 排序。 对于一个区间内的相同数字,只关心最右边那个,vi记录数字的最后一次出现位置,例如 1 4 5 1,考虑前三个时,vi【1】=1,vi【4】=2,vi【5】=3,树状数组形为 1 1 1 0,然后下一个查询的 r为4,a【4】已被记录,那么就更新 vi【1】为4,树状数组形为 0 1 1 1。ask( r) - ask( l-1)即为查询的答案

代码如下:

cpp 复制代码
const int N=2e6+10,M=210;
ll n;
ll t[N],a[N];
struct nod{
	int l,r;
	int id,ans;
}q[N];
int vi[N];
int lowbit(int x){return x&-x;}
void add(int x,int v){
	for(int i=x;i<=n;i+=lowbit(i)) t[i]+=v;
}
ll ask(int x){
	ll res=0;
	for(int i=x;i;i-=lowbit(i)) res+=t[i];
	return res;
}
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++) cin >> a[i];
	int m; cin >> m;
	for(int i=1;i<=m;i++){
		cin >> q[i].l >> q[i].r;
		q[i].id=i;
	}
	sort(q+1,q+m+1,[](nod a,nod b){
		return a.r<b.r;
	});
	for(int i=1;i<=m;i++){
		for(int j=q[i-1].r+1;j<=q[i].r;j++){
			if(!vi[a[j]]) add(j,1),vi[a[j]]=j;
			else{
				add(vi[a[j]],-1);
				add(j,1);
				vi[a[j]]=j;
			}
		}
		q[i].ans=ask(q[i].r)-ask(q[i].l-1);
	}
	sort(q+1,q+m+1,[](nod a,nod b){
		return a.id<b.id;
	});
	for(int i=1;i<=m;i++) cout << q[i].ans << "\n";
}

星期二:

板刷数据结构 题三

因为读错题(没注意样例,被耽误了一晚上和半个早上。。

以后读完题记得一定看样例!!!

思路:和数贝壳很像,注意这题一种采了的颜色对答案的贡献只有1! 把询问按 r大小排序,升序处理,把第一次出现的颜色标记一下,但并不赋值,此颜色第二次以及更多次出现时,才对上一次出现的位置+1,且对上上次的位置-1

代码如下:

cpp 复制代码
const int N=2e6+10;
ll n;
ll a[N],t[N];
struct nod{
	int l,r;
	int id,ans;
}q[N];
PII vi[N];
int lowbit(int x){return x&-x;}
void add(int x,int v){
	for(int i=x;i<=n;i+=lowbit(i)) t[i]+=v;
}
ll ask(int x){
	ll res=0;
	for(int i=x;i;i-=lowbit(i)) res+=t[i];
	return res;
}
void solve(){
	int c,m; cin >> n >> c >> m;
	for(int i=1;i<=n;i++) cin >> a[i];
	for(int i=1;i<=m;i++){
		cin >> q[i].l >> q[i].r;
		q[i].id=i;
	}
	sort(q+1,q+m+1,[](nod a,nod b){
		return a.r<b.r;
	});
	for(int i=1;i<=m;i++){
		for(int j=q[i-1].r+1;j<=q[i].r;j++){
			if(!vi[a[j]].first) vi[a[j]].first=j;
			else if(!vi[a[j]].second) add(vi[a[j]].first,1),vi[a[j]].second=j;
			else{
				add(vi[a[j]].first,-1),vi[a[j]].first=vi[a[j]].second;
				add(vi[a[j]].first,1),vi[a[j]].second=j;
			}
		}
		q[i].ans=ask(q[i].r)-ask(q[i].l-1);
	}
	sort(q+1,q+m+1,[](nod a,nod b){
		return a.id<b.id;
	});
	for(int i=1;i<=m;i++) cout << q[i].ans << "\n";
}

最后半小时才知道有个训练赛

ABC 357 C atc传送门

思路:递归赋值

代码如下:

cpp 复制代码
ll n;
char c[740][740];
void kk(int a,int b,int x,int y){
	if(a==x){c[a][b]='#'; return ;}
	int len=x-a+1;
	len/=3;
	for(int i=0;i<3;i++) kk(a,b+i*len,a+len-1,b+(i+1)*len-1);  //前三个格子
	kk(a+len,b,a+2*len-1,b+len-1);
	for(int i=a+len;i<a+2*len;i++)
		for(int j=b+len;j<b+2*len;j++)
			c[i][j]='.';
	kk(a+len,b+2*len,a+2*len-1,b+3*len-1);                     //中间三个格子
	for(int i=0;i<3;i++) kk(a+2*len,b+i*len,a+3*len-1,b+(i+1)*len-1); //最后三个格子
}
void solve(){
	cin >> n;
	int x=1,y=1;
	for(int i=1;i<=n;i++) x*=3,y*=3;
	kk(1,1,x,y);                        //左上坐标为1,1,右下坐标为x,y
	for(int i=1;i<=x;i++){
		for(int j=1;j<=y;j++) cout << c[i][j];
		cout << "\n";
	}
}

补ABC 325 D atc传送门

思路:贪心,但比较麻烦。将物品开始 st和结束 ed时间作为pair数据升序排列,在所有 st<=ti的物品中,选择 ed最小的打印,用优先队列实现找最小 ed,每打印一个 ti++,时间++时注意把可开始的物品放入优先队列,若队列为空,则此时无物品可打印,将时间跳转到下一个物品的 st

代码如下:

cpp 复制代码
const int N=2e6+10,M=210;
ll n;
PII a[N];
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		ll t,d; cin >> t >> d;
		a[i].first=t;
		a[i].second=t+d;
	}
	sort(a+1,a+n+1);
	priority_queue<ll,vector<ll>,greater<ll>>pq;
	ll ti=0,ans=0;
	for(int i=1;;ti++){
		if(pq.empty()){
			if(i==n+1) break;
			ti=a[i].first;
		}
		while(i<=n && a[i].first==ti) pq.push(a[i++].second);
		while(!pq.empty() && pq.top()<ti) pq.pop();
		if(!pq.empty()) pq.pop(),ans++;
	}
	cout << ans;
}

补ABC 334 F atc传送门

思路:dp【i】表示送完了前 i家并回到起点的最短距离,dis【i】为一直走到 i点距离即前缀和 ,ho【i】为与起点的距离

转移式即为:dp【i】= dp【j】+ ho【j+1】+ dis【i】- dis【j+1】+ ho【i】( j >= i-k)

转移式比较麻烦,但推出来后不难想到单调队列优化

最后注意dis【i】是从起点开始算,到 i的距离,而不是第一家小孩开始算

代码如下:

cpp 复制代码
const int N=2e6+10,M=210;
ll n;
ll x[N],y[N];
double dis[N],ho[N];
double dp[N];
void solve(){
	int k; cin >> n >> k;
	int sx,sy; cin >> sx >> sy;
	dis[1]=sqrt((x[1]-sx)*(x[1]-sx)+(y[1]-sy)*(y[1]-sy));
	for(int i=1;i<=n;i++){
		cin >> x[i] >> y[i];
		if(i>1) dis[i]=sqrt((x[i]-x[i-1])*(x[i]-x[i-1])+(y[i]-y[i-1])*(y[i]-y[i-1]));
		dis[i]+=dis[i-1];
		ho[i]=sqrt((x[i]-sx)*(x[i]-sx)+(y[i]-sy)*(y[i]-sy));
	}
	priority_queue<pair<double,int>,vector<pair<double,int>>,greater<pair<double,int>>>pq;
	dp[0]=0;
	pq.push({-dis[1]+ho[1],0});
	for(int i=1;i<=n;i++){
		while(pq.top().second<i-k) pq.pop();
		dp[i]=pq.top().first+dis[i]+ho[i];
		pq.push({dp[i]-dis[i+1]+ho[i+1],i});
	}
	cout << fixed << setprecision(8) << dp[n];
}

补 ABC 299 E atc传送门

思路:除了必须白色的点,全涂为黑色,然后用 bfs来check是否合法,用 dfs进行check会wa, 目前不知道其原因

dfs错误的原因想到了,x和dis代表的是点以及和起点的距离,但因dfs是深度优先搜索,dis会出错

代码如下:

cpp 复制代码
ll n;
vector<int>ve[2020];
int p[2020],d[2020];
string s;
bool vi[2020];
bool ifd;
bool dfs(int x,int dis){
	if(dis>0 && s[x-1]=='1'){ifd=0; return 0;}
	if(dis==0) return s[x-1]=='1';
	vi[x]=1;
	bool if1=0;
	for(auto i:ve[x])
		if(!vi[i]) vi[i]=1,if1|=dfs(i,dis-1);
	if(!ifd) return 0;
	return if1;
}
bool bfs(int x,int d){
	queue<PII>q;
	q.push({x,0});
	vi[x]=1;
	while(!q.empty()){
		auto [x,y]=q.front(); q.pop();
		if(y==d){
			if(s[x-1]=='1') return 1;
			continue;
		}
		for(auto i:ve[x])
			if(!vi[i]) vi[i]=1,q.push({i,y+1});
	}
	return 0;
}
void solve(){
	int m; cin >> n >> m;
	for(int i=1;i<=m;i++){
		int u,v; cin >> u >> v;
		ve[u].push_back(v);
		ve[v].push_back(u);
	}
	int k; cin >> k;
	set<int>mtw;
	for(int i=1;i<=k;i++){
		cin >> p[i] >> d[i];
		queue<PII>q;
		memset(vi,0,sizeof vi);
		q.push({p[i],0});
		vi[p[i]]=1;
		while(!q.empty()){
			auto [x,y]=q.front(); q.pop();
			if(y==d[i]) continue;
			mtw.insert(x);
			for(auto j:ve[x])
				if(!vi[j]) vi[j]=1,q.push({j,y+1});
		}
	}
	if(!k){cout << "Yes\n"; for(int i=1;i<=n;i++) cout << 1; return ;}
	for(int i=1;i<=n;i++) s.append("0");
	for(int i=1;i<=n;i++) if(mtw.find(i)==mtw.end()) s[i-1]='1';
	for(int i=1;i<=k;i++){
		memset(vi,0,sizeof vi);
		ifd=1;
		if(!bfs(p[i],d[i])){cout << "No"; return ;}
	}
	cout << "Yes\n" << s;
}

星期三:

补 hdu1421 smu传送门

很普通的dp,赛时没出是我的问题

思路:dp【i】【j】表示考虑到前 i个物品,选了 j对的最小疲劳

有一个比较显然的结论(但我没想到)的是,物品配对的选择一定是排序后两两相邻着拿

转移式:dp【i】【j】= min( dp【i-2】【j-1】+ ( a【i】- a【i-1】)^2,dp【i-1】【j】)

代码如下:

cpp 复制代码
ll n;
int a[2020];
int dp[2020][1010];
void solve(){
	int k;
	while(cin >> n >> k){
		for(int i=1;i<=n;i++) cin >> a[i];
		sort(a+1,a+n+1);
		for(int i=0;i<=n;i++)
			for(int j=1;j<=k;j++) dp[i][j]=1e9;
		for(int i=2;i<=n;i++){
			for(int j=1;j*2<=i;j++){
				dp[i][j]=min(dp[i-2][j-1]+(a[i]-a[i-1])*(a[i]-a[i-1]),dp[i-1][j]);
			}
		}
		cout << dp[n][k] << "\n";
	}
}

补ABC 200 D atc传送门

之前没接触过鸽巢原理,现在遇到了一道与之相关的题,真是学到了

思路:n为200,看似很小,但如果要纯暴力又肯定不行。根据鸽巢原理,根据对200的模数把序列分类,最多需要多少序列就能确定一定有模数相同的序列呢?201个序列足矣,长为 n的序列存在 2^n-1个非空子序列,所以若n>8,也只需要对前8个进行暴力分类,就一定有解

代码如下:

cpp 复制代码
ll n;
ll a[220];
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++) cin >> a[i];
	map<int,vector<int>>mp;
	int cnt=0;
	vector<int>ve[220];
	int len=min(8ll,n);
	for(int mask=1;mask<1<<len;mask++){
		ll sum=0;
		vector<int>tp;
		for(int j=0;j<len;j++) if(mask&1<<j) sum+=a[j+1],tp.push_back(j+1);
		mp[++cnt]=tp;
		ve[sum%200].push_back(cnt);                    //序列的编号
	}
	for(int i=0;i<200;i++)
		if(ve[i].size()>1){
			cout << "Yes\n";
			cout << mp[ve[i][0]].size() << " ";
			for(auto j:mp[ve[i][0]]) cout << j << " ";
			cout << "\n";
			cout << mp[ve[i][1]].size() << " ";
			for(auto j:mp[ve[i][1]]) cout << j << " ";
			return ;
		}
	cout << "No";
}

补ABC 204 E atc传送门

思路:dp【i】表示到达 i点的最短时间,dfs不能实现转移,需要迪杰斯特拉

dp【v】= min( t + C + D/( t+1),dp【v】),t >= dp【u】

把 t+1用 x来表示,dp【v】= x + D/x + C - 1,x >= dp【u】+1

如何求 x+D/x的最小值呢,试过三分,但整形的三分会出错,结论是 x=sqrt(D)四舍五入时最小

代码如下:

cpp 复制代码
const int N=3e5+10;
ll n;
struct nod{
	int v;
	ll c,d;
};
vector<nod>ve[N];
ll dp[N];
bool vi[N];
void dij(){
	memset(dp,0x3f,sizeof dp);
	priority_queue<PII,vector<PII>,greater<PII>>pq;
	pq.push({0,1});
	dp[1]=0;
	while(!pq.empty()){
		auto [t,u]=pq.top(); pq.pop();
		if(vi[u]) continue;
		vi[u]=1;
		for(auto nd:ve[u]){
			ll v=nd.v,c=nd.c,d=nd.d;
			ll x=round(sqrt(d));
			x=max(t+1,x);
			ll w=x+d/x+c-1;
			if(w<dp[v]){
				dp[v]=w;
				pq.push({dp[v],v});
			}
		}
	}
}
void solve(){
	int m; cin >> n >> m;
	for(int i=1;i<=m;i++){
		int a,b,c,d; cin >> a >> b >> c >> d;
		ve[a].push_back({b,c,d});
		ve[b].push_back({a,c,d});
	}
	dij();
	if(dp[n]>1e18) cout << -1;
	else cout << dp[n];
}

补ABC 202 E atc传送门

题意:给一棵树,q次询问,每次求 u子树上有多少深度为 d的点

思路:用时间戳解决,若 v是 u的子树上的点,则满足 in【u】<= in【v】< out【v】<= out【u】,按深度来存点,在深度数组里二分查找满足条件的点集区间(只存 in【x】值

以上关于时间戳的结论在 atc此题的题解中有较详细的证明:

代码如下:

cpp 复制代码
const int N=3e5+10;
ll n;
vector<int>ve[N],dep[N];
int in[N],out[N],timer;
void dfs(int x,int d){
	in[x]=++timer;
	dep[d].push_back(in[x]);
	for(auto v:ve[x]) dfs(v,d+1);
	out[x]=++timer;
}
void solve(){
	cin >> n;
	for(int i=2;i<=n;i++){
		int p; cin >> p;
		ve[p].push_back(i);
	}
	dfs(1,0);
	int q; cin >> q;
	while(q--){
		int u,d; cin >> u >> d;
		auto r=lower_bound(dep[d].begin(),dep[d].end(),out[u]);
		auto l=lower_bound(dep[d].begin(),dep[d].end(),in[u]);
		cout << r-l << "\n";
	}
}

板刷数据结构 题四 数据结构 牛客传送门

思路:之前做过维护乘和加的线段树,这次多了一个平方和,需要推下式子,懒标记注意先乘再加

代码如下:

cpp 复制代码
const int N=3e5+10,M=210;
ll n;
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
	struct nod{
		int l,r;
		ll sum,sum2;
		ll add,mul;
	}t[N<<2];
	int ql,qr,qop;
	ll qv,a[N];
	nod merge(nod a,nod b){
		nod res;
		res.l=a.l,res.r=b.r;
		res.sum=a.sum+b.sum;
		res.sum2=a.sum2+b.sum2;
		res.add=0;
		res.mul=1;
		return res;
	}
	void pushup(int p){t[p]=merge(t[lc],t[rc]);}
	void pushdn(int p){
		if(t[p].mul!=1){
			t[lc].sum2*=t[p].mul*t[p].mul;
			t[rc].sum2*=t[p].mul*t[p].mul;
			t[lc].sum*=t[p].mul;
			t[rc].sum*=t[p].mul;
			t[lc].mul*=t[p].mul;
			t[rc].mul*=t[p].mul;
			t[lc].add*=t[p].mul;
			t[rc].add*=t[p].mul;             //注意对add的影响不要遗漏
			t[p].mul=1;
		}
		if(t[p].add){
			t[lc].sum2+=2*t[lc].sum*t[p].add+(t[lc].r-t[lc].l+1)*t[p].add*t[p].add;
			t[rc].sum2+=2*t[rc].sum*t[p].add+(t[rc].r-t[rc].l+1)*t[p].add*t[p].add;
			t[lc].sum+=(t[lc].r-t[lc].l+1)*t[p].add;
			t[rc].sum+=(t[rc].r-t[rc].l+1)*t[p].add;
			t[lc].add+=t[p].add;
			t[rc].add+=t[p].add;
			t[p].add=0;
		}
	}
	void bd(int p,int l,int r){
		t[p]={l,r,0,0,0,1};
		if(l==r){
			t[p].sum=a[l];
			t[p].sum2=a[l]*a[l];
			return ;
		}
		int mid=l+r>>1;
		bd(lc,l,mid);
		bd(rc,mid+1,r);
		pushup(p);
	}
	void update(int p){
		if(ql<=t[p].l && qr>=t[p].r){
			if(qop==1){                        //区间加
				t[p].sum2+=2*t[p].sum*qv+(t[p].r-t[p].l+1)*qv*qv;
				t[p].sum+=(t[p].r-t[p].l+1)*qv;
				t[p].add+=qv;
				return ;
			}else{                             //区间乘
				t[p].sum2*=qv*qv;
				t[p].sum*=qv;
				t[p].mul*=qv;
				t[p].add*=qv;
				return ;
			}
		}
		int mid=t[p].l+t[p].r>>1;
		pushdn(p);
		if(ql<=mid) update(lc);
		if(qr>mid) update(rc);
		pushup(p);
	}
	nod query(int p){
		if(ql<=t[p].l && qr>=t[p].r) return t[p];
		int mid=t[p].l+t[p].r>>1;
		pushdn(p);
		if(ql>mid) return query(rc);
		if(qr<=mid) return query(lc);
		return merge(query(lc),query(rc));
	}
	void updt(int l,int r,int op,ll v){
		ql=l,qr=r;
		qop=op,qv=v;
		update(1);
	}
	ll ask_sum(int l,int r){
		ql=l,qr=r;
		return query(1).sum;
	}
	ll ask_sum2(int l,int r){
		ql=l,qr=r;
		return query(1).sum2;
	}
}tr;
void solve(){
	int q; cin >> n >> q;
	for(int i=1;i<=n;i++) cin >> tr.a[i];
	tr.bd(1,1,n);
	while(q--){
		int op; cin >> op;
		if(op==1 || op==2){
			int l,r; cin >> l >> r;
			if(op==1) cout << tr.ask_sum(l,r) << "\n";
			if(op==2) cout << tr.ask_sum2(l,r) << "\n";
		}
		if(op==3 || op==4){
			int l,r;ll x; cin >> l >> r >> x;
			if(op==3) tr.updt(l,r,2,x);
			if(op==4) tr.updt(l,r,1,x);
		}
	}
}

星期四:

板刷数据结构 题五 牛客传送门

思路:和上一题有点像,注意运算中时刻%P,不然会爆 ll

代码如下:

cpp 复制代码
const int N=3e5+10,M=210;
ll n;
int P;
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
	struct nod{
		int l,r;
		ll sum,sum2;
		ll add,mul;
	}t[N<<2];
	int ql,qr,qop;
	ll qv,a[N];
	nod merge(nod a,nod b){
		nod res;
		res.l=a.l,res.r=b.r;
		res.sum=(a.sum+b.sum)%P;
		res.sum2=(a.sum2+b.sum2+a.sum*b.sum%P)%P;
		res.add=0;
		res.mul=1;
		return res;
	}
	void pushup(int p){t[p]=merge(t[lc],t[rc]);}
	void pushdn(int p){
		if(t[p].mul!=1){
			t[lc].sum2*=t[p].mul*t[p].mul%P,t[lc].sum2%=P;
			t[rc].sum2*=t[p].mul*t[p].mul%P,t[rc].sum2%=P;
			t[lc].sum*=t[p].mul,t[lc].sum%=P;
			t[rc].sum*=t[p].mul,t[rc].sum%=P;
			t[lc].mul*=t[p].mul,t[lc].mul%=P;
			t[rc].mul*=t[p].mul,t[rc].mul%=P;
			t[lc].add*=t[p].mul,t[lc].add%=P;
			t[rc].add*=t[p].mul,t[rc].add%=P;
			t[p].mul=1;
		}
		if(t[p].add){
			t[lc].sum2+=t[lc].sum*(t[lc].r-t[lc].l)%P*t[p].add%P+(t[lc].r-t[lc].l+1)*(t[lc].r-t[lc].l)/2*t[p].add%P*t[p].add,t[lc].sum2%=P;
			t[rc].sum2+=t[rc].sum*(t[rc].r-t[rc].l)%P*t[p].add%P+(t[rc].r-t[rc].l+1)*(t[rc].r-t[rc].l)/2*t[p].add%P*t[p].add,t[rc].sum2%=P;
			t[lc].sum+=(t[lc].r-t[lc].l+1)*t[p].add%P,t[lc].sum%=P;
			t[rc].sum+=(t[rc].r-t[rc].l+1)*t[p].add%P,t[rc].sum%=P;
			t[lc].add+=t[p].add,t[lc].add%=P;
			t[rc].add+=t[p].add,t[rc].add%=P;
			t[p].add=0;
		}
	}
	void bd(int p,int l,int r){
		t[p]={l,r,0,0,0,1};
		if(l==r){
			t[p].sum=a[l];
			return ;
		}
		int mid=l+r>>1;
		bd(lc,l,mid);
		bd(rc,mid+1,r);
		pushup(p);
	}
	void update(int p){
		if(ql<=t[p].l && qr>=t[p].r){
			if(qop==1){                          //区间加
				t[p].sum2+=t[p].sum*(t[p].r-t[p].l)%P*qv%P+(t[p].r-t[p].l+1)*(t[p].r-t[p].l)/2%P*qv%P*qv%P,t[p].sum2%=P;
				t[p].sum+=(t[p].r-t[p].l+1)*qv%P,t[p].sum%=P;
				t[p].add+=qv,t[p].add%=P;
			}else{                               //区间乘
				t[p].sum2*=qv*qv%P,t[p].sum2%=P;
				t[p].sum*=qv,t[p].sum%=P;
				t[p].mul*=qv,t[p].mul%=P;
				t[p].add*=qv,t[p].add%=P;
			}
			return ;
		}
		int mid=t[p].l+t[p].r>>1;
		pushdn(p);
		if(ql<=mid) update(lc);
		if(qr>mid) update(rc);
		pushup(p);
	}
	nod query(int p){
		if(ql<=t[p].l && qr>=t[p].r) return t[p];
		int mid=t[p].l+t[p].r>>1;
		pushdn(p);
		if(ql>mid) return query(rc);
		if(qr<=mid) return query(lc);
		return merge(query(lc),query(rc));
	}
	void updt(int l,int r,int op,ll v){
		ql=l,qr=r;
		qop=op,qv=v;
		update(1);
	}
	ll ask_sum(int l,int r){
		ql=l,qr=r;
		return query(1).sum;
	}
	ll ask_sum2(int l,int r){
		ql=l,qr=r;
		return query(1).sum2;
	}
}tr;
void solve(){
	int m; cin >> n >> m >> P;
	for(int i=1;i<=n;i++) cin >> tr.a[i];
	tr.bd(1,1,n);
	while(m--){
		int op; cin >> op;
		if(op==3){
			int l,r; cin >> l >> r;
			cout << tr.ask_sum2(l,r)%P << "\n";
		}else{
			int l,r,v; cin >> l >> r >> v;
			tr.updt(l,r,op,v);
		}
	}
}

下午暑假友谊赛热身1

贴个D atc传送门

题意:将 a数组切三刀分成四段,使四段中的最大sum值和最小sum值差值最小

思路:注意到切三刀这个操作,先考虑能不能枚举中间那刀,答案是可以的,两边的最佳切法为将值尽量对半开,二分lower_bound找到切点,因为lower_bound是大于等于值的一半,所以注意检查切点往左移一位是否更优

代码如下:

cpp 复制代码
const int N=3e5+10;
ll n;
ll a[N];
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		a[i]+=a[i-1];
	}
	ll ans=1e18;
	for(int mid=2;mid<=n-2;mid++){
		ll l=a[mid],r=a[n]-a[mid];
		int i=lower_bound(a+1,a+n+1,l/2)-a;
		if(i>1 && abs(a[mid]-a[i]*2)>abs(a[mid]-a[i-1]*2)) i--;
		int j=lower_bound(a+1,a+n+1,a[mid]+r/2)-a;
		if(j>mid+1 && abs(a[n]+a[mid]-a[j]*2)>abs(a[n]+a[mid]-a[j-1]*2)) j--;
		ll ma=max({a[i],a[mid]-a[i],a[j]-a[mid],a[n]-a[j]});
		ll mi=min({a[i],a[mid]-a[i],a[j]-a[mid],a[n]-a[j]});
		ans=min(ma-mi,ans);
	}
	cout << ans;
}

星期五:

补cf round731 div3 cf传送门

思路:注意到一部空调可以对两端都造成影响,遇到这种类型的题,优先考虑把左端和右端分开考虑。l【i】表示只考虑左边空调影响的最低温度,r【i】表示只考虑右边空调影响的最低温度

l【i】= min(l【i-1】+ 1,l【i】),r【i】= min(r【i+1】+1,r【i】),答案取更小值

代码如下:

cpp 复制代码
const int N=3e5+10,M=210;
ll n;
int a[N],t[N];
int l[N],r[N];
void solve(){
	int k; cin >> n >> k;
	for(int i=0;i<=n+1;i++) l[i]=r[i]=2e9;
	for(int i=1;i<=k;i++) cin >> a[i];
	for(int i=1;i<=k;i++){
		cin >> t[i];
		l[a[i]]=r[a[i]]=t[i];
	}
	for(int i=1;i<=n;i++)
		l[i]=min(l[i-1]+1,l[i]);
	for(int i=n;i;i--)
		r[i]=min(r[i+1]+1,r[i]);
	for(int i=1;i<=n;i++) cout << min(l[i],r[i]) << " \n"[i==n];
}

补cf round 957 div3 D cf传送门

很恶心一道模拟,但放上来的重点不是这个,而是提醒自己特判要等到输入完成之后!!!

cpp 复制代码
    int m,k; cin >> n >> m >> k;
	if(m>n){cout << "YES\n"; return ;}
	string s; cin >> s; s=" "+s;

此为错误代码,也是我赛时写的,将m>n的特判放到 cin >> s后,就能过了

!!!!!!!!!!!!!!!!!!警钟长鸣!!!!!!!!!!!!!!!!!!!!!

板刷数据结构 H 牛客传送门

思路:维护全局最大值,然后线段树上二分

需要注意两点,第一点是merge时,res不能嗯取 a,b的最大值,取 ma1的前提是子节点ma2>0,第二点是输出 -1后是 contnue而不是 return

代码如下:

cpp 复制代码
const int N=3e5+10;
ll n;
int m,k;
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
	struct nod{
		int l,r;
		ll ma1,ma2;
	}t[N<<2];
	int ql,qr,qv;
	nod merge(nod a,nod b){
		nod res;
		res.l=a.l,res.r=b.r;
		if(a.ma2 && b.ma2) res.ma1=max(a.ma1,b.ma1),res.ma2=1;
		else if(a.ma2) res.ma1=a.ma1,res.ma2=1;
		else if(b.ma2) res.ma1=b.ma1,res.ma2=1;
		else res.ma1=res.ma2=0;
		return res;
	}
	void pushup(int p){t[p]=merge(t[lc],t[rc]);}
	void bd(int p,int l,int r){
		t[p]={l,r,m,k};
		if(l==r) return ;
		int mid=l+r>>1;
		bd(lc,l,mid);
		bd(rc,mid+1,r);
	}
	void update(int p){
		if(ql<=t[p].l && qr>=t[p].r){
			t[p].ma1-=qv;
			t[p].ma2--;
			return ;
		}
		int mid=t[p].l+t[p].r>>1;
		if(ql<=mid) update(lc);
		if(qr>mid) update(rc);
		pushup(p);
	}
	void updt(int l,int r,int v){
		ql=l,qr=r;
		qv=v;
		update(1);
	}
	int fnd(int p,int a){
		if(t[p].l==t[p].r) return t[p].l;
		if(t[lc].ma1>=a && t[lc].ma2>0) return fnd(lc,a);
		return fnd(rc,a);
	}
}tr;
void solve(){
	cin >> n >> m >> k;
	tr.bd(1,1,n);
	for(int i=1;i<=n;i++){
		int a; cin >> a;
		if(tr.t[1].ma1<a || tr.t[1].ma2<=0){cout << "-1\n"; continue;}
		int res=tr.fnd(1,a);
		tr.updt(res,res,a);
		cout << res << "\n";
	}
}

星期六:

上午友谊赛

贴个ABC123 D atc传送门

和 hdoj的大雪球很像

思路:狠狠地二分求出第 k个的值,然后把大于此值的存入,排序输出,操作有点复杂,写了挺久

代码如下:

cpp 复制代码
ll n;
ll a[1010],b[1010],c[1010];
void solve(){
	ll x,y,z,pq; cin >> x >> y >> z >> pq;
	for(int i=1;i<=x;i++) cin >> a[i];
	for(int i=1;i<=y;i++) cin >> b[i];
	for(int i=1;i<=z;i++) cin >> c[i];
	sort(a+1,a+x+1);
	sort(b+1,b+y+1);
	sort(c+1,c+z+1);
	ll l=a[1]+b[1]+c[1],r=a[x]+b[y]+c[z],res=0;
	while(l<=r){                       //二分出第 k个的体积
		ll mid=l+r>>1;
		ll sum=0;
		for(int i=1;i<=x;i++){
			for(int j=1;j<=y;j++){
				int k=lower_bound(c+1,c+z+1,mid-a[i]-b[j])-c;
				sum+=z-k+1;                              //算出大于此体积的个数
			}
		}
		if(sum>=pq) res=mid,l=mid+1;
		else r=mid-1;
	}
	vector<ll>ans; ll cnt=0;
	for(int i=x;i;i--){
		if(a[i]+b[y]+c[z]<res) break;
		for(int j=y;j;j--){
			if(a[i]+b[j]+c[z]<res) break;
			for(int k=z;k;k--){
				if(a[i]+b[j]+c[k]<res) break;
				ans.push_back(a[i]+b[j]+c[k]);
				cnt++;
				if(cnt>pq*1000) break;        //塞少了会wa
			}
			if(cnt>pq*1000) break;
		}
		if(cnt>pq*1000) break;
	}
	sort(ans.begin(),ans.end(),greater<>());
	for(int i=0;i<pq;i++) cout << ans[i] << "\n";
}

贴个ABC204 D atc传送门

思路:典题,正解类似于背包,一维枚举物品,二维枚举体积,这里是dfs加个记忆化

代码如下:

cpp 复制代码
const int N=2e5+10,M=210;
ll n;
int t[1010];
ll ans;
ll dp[110][N];
ll dfs(int x,int sum1,int sum2){
	if(x>n) return max(sum1,sum2);
	if(max(sum1,sum2)>=ans) return dp[x][sum1]=INT_MAX;
	if(dp[x][sum1]!=-1) return dp[x][sum1];
	if(dp[x][sum2]!=-1) return dp[x][sum2];
	ll res=0;
	res=min(dfs(x+1,sum1+t[x],sum2),dfs(x+1,sum1,sum2+t[x]));
	return dp[x][sum1]=res;
}
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++) cin >> t[i];
	sort(t+1,t+n+1,greater<>());
	int sum1=0,sum2=0;
	for(int i=1;i<=n;i++){
		if(sum1<sum2) sum1+=t[i];
		else sum2+=t[i];
	}
	ans=max(sum1,sum2);
	memset(dp,-1,sizeof dp);
	ans=min(dfs(1,0,0),ans);
	cout << ans;
}

补cf round734 D1 cf传送门

题意:在n*m的格子里放k个1*2的骨牌,剩下的放 2*1的,能否放满

思路:分类讨论,n和m的奇偶性有三种情况

n和m都为偶,k奇数NO,偶数YES

n为奇,考虑将其转化为第一种情况,先用 m/2个横牌填满一行,若填不了也肯定NO

m为奇,也考虑如何转化为第一种情况,用 n/2个竖牌填一列 (判断 k的大小),再判断 k奇偶

代码如下:

cpp 复制代码
ll n;
void solve(){
	ll m,k; cin >> n >> m >> k;
	if(n!=2 && k==n*m/2-1){cout << "NO\n"; return ;}
	if(!(n&1) && !(m&1)){
		if(k&1) cout << "NO\n";
		else cout << "YES\n";
	}else if(!(n&1)){
		if(k>n*m/2-n/2) cout << "NO\n";
		else if(k&1) cout << "NO\n";
		else cout << "YES\n";
	}else{
		if(k>=m/2){
			k-=m/2;
			if(k&1) cout << "NO\n";
			else cout << "YES\n";
		}else cout << "NO\n";
	}
}

补ABC 067 D atc传送门

思路:容易想到两人应该先抢占 1-n的简单路径,抢完后谁能涂的点多谁就赢

dis【i】表示 i到俩人的距离,disb【i】<= disw【i】,此点就是F的,否则S的

代码如下:

cpp 复制代码
const int N=2e5+10,M=210;
ll n;
vector<int>ve[N];
int disb[N],disw[N];
int sumb,sumw;
void dfsb(int x,int f){
	for(int v:ve[x]){
		if(v==f) continue;
		disb[v]=disb[x]+1;
		dfsb(v,x);
	}
}
void dfsw(int x,int f){
	for(int v:ve[x]){
		if(v==f) continue;
		disw[v]=disw[x]+1;
		dfsw(v,x);
	}
}
void painb(int x,int f){
	if(disb[x]>disw[x]) return ;
	sumb++;
	for(int v:ve[x]){
		if(v==f) continue;
		painb(v,x);
	}
}
void painw(int x,int f){
	if(disw[x]>=disb[x]) return ;
	sumw++;
	for(int v:ve[x]){
		if(v==f) continue;
		painw(v,x);
	}
}
void solve(){
	cin >> n;
	for(int i=1;i<n;i++){
		int a,b; cin >> a >> b;
		ve[a].push_back(b);
		ve[b].push_back(a);
	}
	dfsb(1,0);
	dfsw(n,0);
	painb(1,0);
	painw(n,0);
	if(sumb<=sumw) cout << "Snuke";
	else cout << "Fennec";
}

补cf round760 div3 F cf传送门

思路:第一眼可能会让人摸不着头脑,但手操下操作会发现,x能变为的数是十分有限的

若加0翻转,实际等于去除末尾的0再翻转,连续的有效操作次数最多为2

若加1翻转,等于翻转后加一个高位的1,这样操作在60次后就一定会超过1e18

所以可直接暴力模拟

代码如下:

cpp 复制代码
string to_s(ll x){
	string s;
	while(x){
		if(x&1) s.append("1");
		else s.append("0");
		x>>=1;
	}
	reverse(s.begin(),s.end());
	return s;
}
void solve(){
	ll x,y; cin >> x >> y;
	string sx=to_s(x),sy=to_s(y);
	set<string>st;
	st.insert(sx);
	queue<string>qu;
	qu.push(sx);
	while(!qu.empty()){
		string t=qu.front(); qu.pop();
		if(t.size()>60) continue;
		string xs=t;
		while(xs.back()=='0') xs.pop_back();
		reverse(xs.begin(),xs.end());
		if(st.find(xs)==st.end()){
			st.insert(xs);
			qu.push(xs);
		}
		t.append("1");
		reverse(t.begin(),t.end());
		if(st.find(t)==st.end()){
			st.insert(t);
			qu.push(t);
		}
	}
	if(st.find(sy)!=st.end()) cout << "YES";
	else cout << "NO";
}

周日:

补 ABC207 E atc传送门

思路:dp【i】【j】表示考虑到第 i个元素,分成 j段的方案数,朴素转移复杂度为 O(n^3)

dp【i】【j】从 dp【k】【j-1】转移的条件是 ( a【i】- a【k】)% j == 0,也就是 a【i】与 a【k】同模 j,那么可以用 sum提前存下分成 j-1段及其对 j模数为 a【i】%j的总方案数(有点抽象

代码如下:

cpp 复制代码
const int mod=1e9+7;
ll n;
ll a[3030];
ll dp[3030][3030];
ll sum[3030][3030];
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		a[i]+=a[i-1];
	}
	for(int i=1;i<=n;i++){
		dp[i][1]=1;
		for(int j=2;j<=i;j++)
			dp[i][j]=sum[j][a[i]%j];
		for(int j=1;j<=n;j++)
			(sum[j][a[i]%j]+=dp[i][j-1])%=mod;
	}
	ll ans=0;
	for(int i=1;i<=n;i++) (ans+=dp[n][i])%=mod;
	cout << ans;
}
相关推荐
yuanbenshidiaos43 分钟前
C++----------函数的调用机制
java·c++·算法
唐叔在学习1 小时前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
ALISHENGYA1 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法
chengooooooo1 小时前
代码随想录训练营第二十七天| 贪心理论基础 455.分发饼干 376. 摆动序列 53. 最大子序和
算法·leetcode·职场和发展
jackiendsc1 小时前
Java的垃圾回收机制介绍、工作原理、算法及分析调优
java·开发语言·算法
游是水里的游2 小时前
【算法day20】回溯:子集与全排列问题
算法
yoyobravery2 小时前
c语言大一期末复习
c语言·开发语言·算法
Jiude2 小时前
算法题题解记录——双变量问题的 “枚举右,维护左”
python·算法·面试
被AI抢饭碗的人3 小时前
算法题(13):异或变换
算法
nuyoah♂4 小时前
DAY36|动态规划Part04|LeetCode:1049. 最后一块石头的重量 II、494. 目标和、474.一和零
算法·leetcode·动态规划