海亮集训

Day 1-整体二分

当我发现我用这个键盘打字打一个错一个的时候,我红温了
当我又双叒叕把int达成ubt时,我又红温了

我不李姐

1.1 放个板子

就是分治nie

普通二分是每次在某个值域区间上确定一个 mid ,然后跑遍 check 处理一个问题

但要是多个询问,每次都跑一遍复杂度就爆炸了

所以,整体二分就是把所有询问一起二分找答案了(然后就能跑所有询问了!

首先有个前提,当前询问的答案就在正在处理的值域 [l,r]

假设目前正在处理的值域区间是 [L,R] ,一个询问的子问题求在当前值域 [L,R] 上,下标区间 [l,r] 中的第 k 小的值(注意注意这里的第 k 小是在当前值域上的,不是在整体值域上的排名)

好的,我们知道待会儿就要把值域再掰成两半二分,现在就是要确定真正的答案究竟是在目前值域的左半拉还是在目前值域的右半拉

aaa./

如果目前下标区间在左值域上的个数 ea 大于 k ,就相当于要在右边值域上找答案,且是要找到第 kea 个数作为它的答案;反之,就是要在左区间上找答案

好的,那么怎么找到 ea 呢?用万能的树状数组就行了!(注意是在左值域上找,所以插入时不要把右值域也插入了)

那么,每次先将当前左值域上的该插入的都插入,然后处理查询并将其分给两边。当值域是 [x,x] 时,就说明 x 就是这些个询问的答案了

建议结合代码食用

#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
const int inf=1e9;
int n,m,a[N];
int tr[N],ans[N];
int cnt;
struct node{
	int l,r,k;//值域区间 
	int id,type;//1是操作,2是询问 
}q[N<<1],q1[N<<1],q2[N<<1];
//利用q1,q2存储q两半,最后再把q1,q2映射到q上就好了

int lowbit(int x) { return x&-x; }
void add(int d,int x) { while (d<N) { tr[d]+=x; d+=lowbit(d); } }
int sum(int x)
{
	int res=0;
	while (x) { res+=tr[x]; x-=lowbit(x); }
	return res;
}
void solve(int ql,int qr,int l,int r)//下标区间,值域区间 
{
	if (ql>qr) return ;
	if (l==r)
	{
		for (int i=ql;i<=qr;i++) if (q[i].type==2) ans[q[i].id]=l;
		return ;
	}
	
	int id1=0,id2=0;
	int mid=(l+r)>>1;
	for (int i=ql;i<=qr;i++)
	{
		if (q[i].type==1)//先插入
		{
			if (q[i].l<=mid) { add(q[i].id,1); q1[++id1]=q[i]; }
			else q2[++id2]=q[i];
		}
		else//再询问
		{
			int res=sum(q[i].r)-sum(q[i].l-1);
			if (res>=q[i].k) q1[++id1]=q[i];
			else
			{
				q2[++id2]=q[i];
				q2[id2].k-=res;
			}
		}
	}
    //清空树状数组
	for (int i=1;i<=id1;i++) if (q1[i].type==1) add(q1[i].id,-1);
    //for (int i=1;i<=id1;i++) q[i+ql-1]=q1[i];
	for (int i=1;i<=id2;i++) q[i+ql+id1-1]=q2[i];

	solve(ql,ql+id1-1,l,mid);
	solve(ql+id1,qr,mid+1,r); 
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>m;
	for (int i=1;i<=n;i++)
	{
		cin>>a[i];
		q[i]={a[i],1,inf,i,1};
	}
	for (int i=1;i<=m;i++)
	{
		cin>>q[n+i].l>>q[n+i].r>>q[n+i].k;
		q[n+i].id=i,q[n+i].type=2;
	}
	
	solve(1,n+m,-inf,inf);
	for (int i=1;i<=m;i++) cout<<ans[i]<<"\n";
	return 0;
}

1.2 Dynamic Rankings 精力丰沛的排名

怎么做怎么做怎么做怎么做

把一次“修改”分成两个操作:“删除”“插入”,然后操作顺序不能变,貌似就没了

#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
const int inf=1e9;
int n,m,a[N];
int tr[N],ans[N];
int cnt,tol;
struct node{
	int l,r,k;
	int id,type;
}q[N*3],q1[N*2],q2[N*2];

int lowbit(int x) { return x&-x; }
void add(int d,int x) { while (d<N) { tr[d]+=x; d+=lowbit(d); } }
int sum(int x)
{
	int res=0;
	while (x) { res+=tr[x]; x-=lowbit(x); }
	return res;
}
void solve(int ql,int qr,int l,int r)
{
	if (ql>qr) return ;
	if (l==r)
	{
		for (int i=ql;i<=qr;i++) if (!q[i].type) ans[q[i].id]=l;
		return ;
	}
	
	int id1=0,id2=0;
	int mid=(l+r)>>1;
	for (int i=ql;i<=qr;i++)
	{
		if (q[i].type!=0)
		{
			if (q[i].l<=mid) { add(q[i].id,q[i].type); q1[++id1]=q[i]; }
			else q2[++id2]=q[i];
		}
		else 
		{
			int res=sum(q[i].r)-sum(q[i].l-1);
			if (res>=q[i].k) q1[++id1]=q[i];
			else
			{
				q2[++id2]=q[i];
				q2[id2].k-=res;
			}
		}
	}
	for (int i=1;i<=id1;i++) if (q1[i].type!=0) add(q1[i].id,-q1[i].type);
	for (int i=1;i<=id1;i++) q[i+ql-1]=q1[i];
	for (int i=1;i<=id2;i++) q[i+ql+id1-1]=q2[i];

	solve(ql,ql+id1-1,l,mid);
	solve(ql+id1,qr,mid+1,r); 
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	memset(ans,-1,sizeof ans);
	cin>>n>>m;
	for (int i=1;i<=n;i++)
	{
		cin>>a[i];
		q[++tol]={a[i],1,inf,i,1};
	}
	for (int i=1,x,y,k;i<=m;i++)
	{
		char op;
		cin>>op;
		if (op=='Q') { cin>>x>>y>>k; q[++tol]={x,y,k,i,0}; }
		else { cin>>x>>y; q[++tol]={a[x],1,inf,x,-1}; q[++tol]={y,1,inf,x,1}; a[x]=y; }
	}
	
	solve(1,tol,0,inf);
	for (int i=1;i<=m;i++) if (ans[i]!=-1) cout<<ans[i]<<"\n";
	return 0;
}

1.3 K大数查询

呃啊呃啊

这里要区修区查,所以不用树状数组,用线段树

这里查的是第 k 大,所以和上面反过来

然后剩下的就一样了

警示后人:注意值域

点击查看代码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e4+5;
const int inf=0x7f7f7f7f7f7f7f7f;
int n,m,a[N];
int sum[N];
struct node {
	int l,r,k;
	int id,type;//1插入,2查询 
}p[N<<1],p1[N<<1],p2[N<<1];
int cnt,tol,ans[N];
struct NODE{
	struct Node{
		int l,r;
		int sum,tag;
	}tr[N<<2];
	void push_up(int id) { tr[id].sum=tr[id<<1].sum+tr[(id<<1)|1].sum; }
	void push_down(int id)
	{
		if (!tr[id].tag) return ;
		int ls=id<<1,rs=(id<<1)|1;
		tr[ls].sum+=(tr[ls].r-tr[ls].l+1)*tr[id].tag;
		tr[rs].sum+=(tr[rs].r-tr[rs].l+1)*tr[id].tag;
		tr[ls].tag+=tr[id].tag,tr[rs].tag+=tr[id].tag,tr[id].tag=0;			
	}
	void build(int id,int l,int r)
	{
		tr[id].l=l,tr[id].r=r;
		if (l==r) return ;
		
		int mid=l+r>>1;
		build(id<<1,l,mid);
		build((id<<1)|1,mid+1,r);
	}
	void update(int id,int l,int r,int x)
	{
		if (tr[id].l>=l&&tr[id].r<=r) { tr[id].sum+=x*(tr[id].r-tr[id].l+1); tr[id].tag+=x; return ; }
		
		push_down(id);
		int mid=(tr[id].l+tr[id].r)>>1;
		if (mid>=l) update(id<<1,l,r,x);
		if (mid+1<=r) update((id<<1)|1,l,r,x);
		push_up(id);
	}
	int query(int id,int l,int r)
	{
		if (tr[id].l>=l&&tr[id].r<=r) return tr[id].sum;
		
		push_down(id);
		int mid=(tr[id].l+tr[id].r)>>1,res=0;
		if (mid>=l) res+=query(id<<1,l,r);
		if (mid+1<=r) res+=query((id<<1)|1,l,r);
		return res; 
	}
}Tr;

void solve(int ql,int qr,int l,int r)
{
	if (ql>qr) return ;
	if (l==r)
	{
		for (int i=ql;i<=qr;i++) if (p[i].type==2) ans[p[i].id]=l;
		return ;
	}
	
	int mid=(l+r)>>1,id1=0,id2=0;
	for (int i=ql;i<=qr;i++)
	{
		if (p[i].type==1)
		{
			if (p[i].k>mid) { Tr.update(1,p[i].l,p[i].r,1); p2[++id2]=p[i]; }
			else p1[++id1]=p[i];
		}
		else
		{
			int res=Tr.query(1,p[i].l,p[i].r);
			if (res<p[i].k)
			{
				p1[++id1]=p[i];
				p1[id1].k-=res;
			}
			else p2[++id2]=p[i];
		}
	}
	
	for (int i=1;i<=id2;i++) if (p2[i].type==1) Tr.update(1,p2[i].l,p2[i].r,-1);
	for (int i=1;i<=id1;i++) p[ql+i-1]=p1[i];
	for (int i=1;i<=id2;i++) p[ql+id1+i-1]=p2[i];
	
	solve(ql,ql+id1-1,l,mid);
	solve(ql+id1,qr,mid+1,r);
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>m;
	for (int i=1,op,l,r,k;i<=m;i++)
	{
		cin>>op>>l>>r>>k;
		p[++cnt]={l,r,k,0,op};
		if (op==2) p[cnt].id=++tol;
	}
	
	Tr.build(1,1,n);
	solve(1,cnt,-N,N);
	for (int i=1;i<=tol;i++) cout<<ans[i]<<"\n";
	return 0;
}

1.4 矩阵乘法

不多说了, 调了老子一个下午,老子要毁灭世界

点击查看代码
#incIude <bits/stdc++.h>
#define int long long
#define y1 y_1
using namespace std;
const int N=502;
const int Q=6e4+2;
const int inf=1e9;
int n,q;
struct node{
	int x1,y1,x2,y2,k;
	int id,type;
}p[N*N+Q],p1[N*N+Q],p2[N*N+Q];
int cnt,tr[N][N],ans[Q];

int lowbit(int x) { return x&-x; }
void add(int x,int y,int k)
{
	for (int i=x;i<=n;i+=lowbit(i))
	for (int j=y;j<=n;j+=lowbit(j)) tr[i][j]+=k;
}
int sum(int x,int y)
{
	int res=0;
	for (int i=x;i;i-=lowbit(i))
	for (int j=y;j;j-=lowbit(j)) res+=tr[i][j];
	return res;
}
int query(int x1,int y1,int x2,int y2) { return sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1); }
void solve(int ql,int qr,int l,int r) 
{
	if (ql>qr) return ;
	if (l==r)
	{
		for (int i=ql;i<=qr;i++) if (p[i].type) ans[p[i].id]=l;
		return ;
	}
	
	int mid=(l+r)>>1,id1=0,id2=0;
	for (int i=ql;i<=qr;i++)
	{
		if (p[i].type)
		{
			int res=query(p[i].x1,p[i].y1,p[i].x2,p[i].y2);
			if (res<p[i].k) 
			{
				p2[++id2]=p[i];
				p2[id2].k-=res;
			}
			else p1[++id1]=p[i];
		}
		else
		{
			if (p[i].k<=mid) { add(p[i].x1,p[i].y1,1); p1[++id1]=p[i]; }
			else p2[++id2]=p[i];
		}
	}
	
	for (int i=1;i<=id1;i++) if (!p1[i].type) add(p1[i].x1,p1[i].y1,-1);
	for (int i=1;i<=id1;i++) p[ql+i-1]=p1[i];
	for (int i=1;i<=id2;i++) p[ql+id1+i-1]=p2[i];
	
	solve(ql,ql+id1-1,l,mid),solve(ql+id1,qr,mid+1,r); 
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>q;
	for (int i=1;i<=n;i++)
	for (int j=1,a;j<=n;j++)
	{
		cin>>a;
		p[++cnt]={i,j,i,j,a,0,0};
	}
	for (int i=1,x1,y1,x2,y2,k;i<=q;i++)
	{
		cin>>x1>>y1>>x2>>y2>>k;
		p[++cnt]={x1,y1,x2,y2,k,i,1};
	}
	
	solve(1,cnt,-inf,inf);
	for (int i=1;i<=q;i++) cout<<ans[i]<<"\n";
	return 0;
}

1.5 不是哥们怎么又是你啊

转变题意:给出 m 个数和 n 个区间,求每个区间内的第 s 小的数(桶数组记录

然后因为树状数组的值域问题重构了三次

我真服了

#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
int n,m;
struct node{
	int l,r,k;
	int id,op;
}p[N<<1],p1[N<<1],p2[N<<1];
int tr[N],ans[N];

int lowbit(int x) { return x&-x; }
void add(int x,int y)
{
	while (x<N)
	{
		tr[x]+=y;
		x+=lowbit(x);
	}
}
int sum(int x)
{
	int res=0;
	while (x)
	{
		res+=tr[x];
		x-=lowbit(x);
	}
	return res;
}
void solve(int ql,int qr,int l,int r)
{
	if (ql>qr) return ;
	if (l==r)
	{
		for (int i=ql;i<=qr;i++) if (p[i].op) ans[l]++;
		return ;
	}
	
	int mid=(l+r)>>1,id1=0,id2=0;
	for (int i=ql;i<=qr;i++)
	{
		if (p[i].op)
		{
			int res=sum(p[i].r)-sum(p[i].l-1);
			if (res<p[i].k)
			{
				p[i].k-=res;
				p2[++id2]=p[i];
			}
			else p1[++id1]=p[i];
		}
		else
		{
			if (p[i].id<=mid) { add(p[i].l,1); p1[++id1]=p[i]; }
			else p2[++id2]=p[i];
		}
	}
	
	for (int i=1;i<=id1;i++) if (!p1[i].op) add(p1[i].l,-1);
	for (int i=1;i<=id1;i++) p[ql+i-1]=p1[i];
	for (int i=1;i<=id2;i++) p[ql+id1+i-1]=p2[i];
	
	solve(ql,ql+id1-1,l,mid),solve(ql+id1,qr,mid+1,r);
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>m;
	for (int i=1,l,r,x;i<=n;i++)
	{
		cin>>l>>r>>x;
		p[i+m]={l,r,x,i,1};
	}
	for (int i=1,x;i<=m;i++)
	{
		cin>>x;
		p[i]={x,x,i,i,0};
	}
	
	solve(1,n+m,1,N);
	for (int i=1;i<=m;i++) cout<<ans[i]<<"\n";
	return 0;
}

1.6 biu

aaa,这道题居然不是求第 k 小,根本不会整体二分www

个蛋

联想普通二分过程:对于当前二分区间 [l,r] 的中点 mid ,若加上这 lmid 场陨石雨的需求量 res 也不满足需求 ned,那么就转移至区间 [mid+1,r] 中(此时该国家在这个区间的需求量应是 nedres),反之则转移至 [l,mid]

(突然发现这是一个区修单查

但是一个需求量是按国家给的,下雨区间是按地带给的,这不是一个东西,怎么判断呀怎么判

……暴力循环累加计算

[微笑]

#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=4e6+5;
const int inf=1e9;
int n,m,k;
vector <int> nt[N];
int nd[N];
struct node {
	int l,r,k;
	int id,op;
}p[N],p1[N],p2[N];
int tr[N],ans[N];

int lowbit(int x) { return x&-x; }
void add(int x,int y)
{
	while (x<=m)
	{
		tr[x]+=y;
		x+=lowbit(x);
	}
}
int sum(int x)
{
	int res=0;
	while (x)
	{
		res+=tr[x];
		x-=lowbit(x); 
	}
	return res;
}
void solve(int ql,int qr,int l,int r)
{
	if (ql>qr) return ;
	if (l==r)
	{
		for (int i=ql;i<=qr;i++) if (p[i].op==1) ans[p[i].id]=l;
		return ;
	}
	
	int mid=(l+r)>>1,id1=0,id2=0;
	for (int i=ql;i<=qr;i++)
	{
		if (p[i].op==1)
		{
			int tmp=0,_size=nt[p[i].id].size();
			for (int j=0;j<_size;j++)
			{
				tmp+=sum(nt[p[i].id][j]);
				if (tmp>=p[i].k) break;
			}
			if (tmp<p[i].k)
			{
				p2[++id2]=p[i];
				p2[id2].k-=tmp;
			}
			else p1[++id1]=p[i];
		}
		else
		{
			if (p[i].id>mid) p2[++id2]=p[i];
			else
			{
				if (p[i].op==2) add(p[i].l,p[i].k),add(p[i].r+1,-p[i].k);
				else add(p[i].l,p[i].k),add(1,p[i].k),add(p[i].r+1,-p[i].k);
				p1[++id1]=p[i];
			}
		}
	}
	
	for (int i=1;i<=id1;i++)
	{
		if (p1[i].op==2) add(p1[i].l,-p1[i].k),add(p1[i].r+1,p1[i].k);
		else if (p1[i].op==3) add(p1[i].l,-p1[i].k),add(1,-p1[i].k),add(p1[i].r+1,p1[i].k); 
	}
	for (int i=1;i<=id1;i++) p[ql+i-1]=p1[i];
	for (int i=1;i<=id2;i++) p[ql+id1+i-1]=p2[i];
	
	solve(ql,ql+id1-1,l,mid),solve(ql+id1,qr,mid+1,r);
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>m;
	for (int i=1,a;i<=m;i++)
	{
		cin>>a;
		nt[a].push_back(i);
	}
	for (int i=1;i<=n;i++) cin>>nd[i];
	cin>>k;
	for (int i=1,l,r,a;i<=k;i++)
	{
		cin>>l>>r>>a;
		p[i]={l,r,a,i,2};
		if (l>r) p[i].op=3;
	}
	for (int i=1;i<=n;i++) p[k+i]={0,0,nd[i],i,1};
	
	solve(1,n+k,1,k+1);
	for (int i=1;i<=n;i++)
	{
		if (ans[i]==k+1) cout<<"NIE"<<"\n";
		else cout<<ans[i]<<"\n";
	} 
	return 0;
}

Day 2-模拟赛

打了场神奇模拟赛,怒得倒数

作业链接

总结:

  • T1感觉可以不高精,但没感觉出来怎么不高精,遂高精爆炸(1h30min)
  • T2一眼联想到弦图吧,然后敲一遍后样例不过。画了下图,又调了点若至小错误后爆炸(40min)
  • T3感觉像是爆搜。妈妈我不会爆搜了!怒得30分
  • T4一眼数位dp,然后方向错。因为没推出性质,一顿优化后无果,怒得80分 这题可以打表解决 悲
  • T5大概手推了一下,发现容斥后放弃
    刚开始推出来了 (2n1)m 列合法的情况(此 n 指需要放东西的行数),然后不知道怎么容斥行合法的情况
    fjj大佬讲了两遍。让我回顾一下……回顾不起来
    这样列合法了,但行可能不合法。所以要容斥。若有 i 行不合法,那么就一共有 Cni×(2ni1)m 种情况被多算,需减去。然后一顿操作猛如虎,补多减的同时还会再多减,所以还要再加回一些;加的时候又会多加,所以还要减去一些,最后的方程式就是 Σi=0(1)ni×Cni×(2ni1)m
    很完美的答案,但我不会逆元。是时候补我的数论了!!!
  • T6 根据最短路径性质做。神奇小结论。
    对于一棵树,若满足点 ki,j 的最短路上,一定满足 disi,k+disk,j=disi,j 。对于 K 棵树,若仍满足所有 k 都在路径上,那么一定满足 Σdisi,k+Σdisk,j=Σdisi,j ,其中所有树的 disi,j 可以通过每棵树跑 ndfs 实现
    然后就做完了
  • T7 想的线段树加反悔贪心,没写。看到题解时,我炸了
    首先,经过手模发现一个点最多被两个区间同时覆盖
    这题又不是贪心,所以就是dp。而这个区间覆盖和覆盖层数紧密相关,所以这要在状态中体现
    将所有区间按右端点升序排序后,设状态 fi,j 表示覆盖到第 i 个区间,其中 [j,ai.r] 只被覆盖了一次所需要的最小代价。
    考虑第 i 个区间从第 j 个区间转移而来。若 aj.r+1=ai.l ,即两个区间无重叠,那么代价就是 max(ai.w,min{fj,[1,aj.r]}) 。若aj.r[ai.l,ai.r] ,即两个区间有重叠,那么代价就是 max(ai.w+aj.w,min{fj,[1,ai.l]}) ,其中取最小值的部分可以用前缀最小值数组提前求出,复杂度 O(n2+nm) (也不知道为啥我T了半天)

我好菜我好菜www “当有人比你小还比你巨的时候,你就可以退役了”


Day 3-数据结构1

起晚了,没早饭吃了wwwww我想回家。

线段树维护差分数组

然后全WA,去看讨论区,被hack了 不嘻嘻

然后改了一下,结果RE40分,至今不知问题所在,不嘻嘻

还得是fjj,查询没特判导致的RE

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+2;
int n,q;
int aa[N],dif[N];
char op;
int s,t,a,b;

struct NODE
{
	struct node{
		int l,r;	
		int ld,rd,tag;
		int s[2][2];
		node() { tag=0; }
		node operator +(const node &y) const{
			node res;
			res.ld=ld,res.rd=y.rd;
			res.l=l,res.r=y.r,res.tag=0;
			res.s[0][0]=min(s[0][1]+y.s[1][0]-(rd==y.ld),min(s[0][0]+y.s[1][0],s[0][1]+y.s[0][0]));
			res.s[1][1]=min(s[1][1]+y.s[1][1]-(rd==y.ld),min(s[1][0]+y.s[1][1],s[1][1]+y.s[0][1]));
			res.s[0][1]=min(s[0][1]+y.s[1][1]-(rd==y.ld),min(s[0][1]+y.s[0][1],s[0][0]+y.s[1][1]));
			res.s[1][0]=min(s[1][1]+y.s[1][0]-(rd==y.ld),min(s[1][1]+y.s[0][0],s[1][0]+y.s[1][0]));
			return res;
		}
	}tr[N<<2];
	void push_down(int id)
	{
		if (!tr[id].tag) return ;
		int ls=id<<1,rs=id<<1|1;
		tr[ls].ld+=tr[id].tag,tr[ls].rd+=tr[id].tag,tr[ls].tag+=tr[id].tag;
		tr[rs].ld+=tr[id].tag,tr[rs].rd+=tr[id].tag,tr[rs].tag+=tr[id].tag;
		tr[id].tag=0;
	} 
	void build(int id,int l,int r)
	{
		tr[id].l=l,tr[id].r=r;
		if (l==r) { tr[id].ld=tr[id].rd=dif[l]; tr[id].s[1][1]=tr[id].s[1][0]=tr[id].s[0][1]=1; return ; }
		
		int mid=(l+r)>>1;
		build(id<<1,l,mid),build(id<<1|1,mid+1,r);
		tr[id]=tr[id<<1]+tr[id<<1|1];
	}
	void update(int id,int l,int r,int k)
	{
		if (tr[id].l>=l&&tr[id].r<=r) { tr[id].ld+=k; tr[id].rd+=k; tr[id].tag+=k; return ; }

		push_down(id);
		int mid=(tr[id].l+tr[id].r)>>1;
		if (mid>=l) update(id<<1,l,r,k);
		if (mid+1<=r) update(id<<1|1,l,r,k);
		tr[id]=tr[id<<1]+tr[id<<1|1];
	}
	node query(int id,int l,int r)
	{
		if (tr[id].l>=l&&tr[id].r<=r) return tr[id];
		
		push_down(id);
		int mid=(tr[id].l+tr[id].r)>>1;
		if (mid>=r) return query(id<<1,l,r);
		else if (mid+1<=l) return query(id<<1|1,l,r);
		else return query(id<<1,l,r)+query(id<<1|1,l,r);
	}
}Tr;
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n;
	for (int i=1;i<=n;i++) cin>>aa[i];
	for (int i=1;i<=n;i++) dif[i]=aa[i]-aa[i-1];
	Tr.build(1,1,n);
	
	cin>>q;
	while (q--)
	{
		cin>>op;
		if (op=='A')
		{
			cin>>s>>t>>a>>b;
			Tr.update(1,s,s,a);
			if (s!=t) Tr.update(1,s+1,t,b);	
			if (t!=n) Tr.update(1,t+1,t+1,-a-b*(t-s));
		}
		else
		{
			cin>>s>>t;
			if (s==t) 
			{
				cout<<1<<"\n";
				continue;
			}
			cout<<Tr.query(1,s+1,t).s[1][1]<<"\n";
		}
	}
	return 0;
}

随机抽取一道线段树优化最短路?

主要是第二第三道题是真的不会做了

边的代价相同,跑个 bfs 就行了,关键是确定路径

首先把限制中的绝对值拆掉

  1. i>j ,那么式子就是 ijpiijpj ,再捣鼓一下就是找 j[ipi,i1],pj+ji 的数。于是乎查找这个范围内的最大值,若是最大值小于 i 了,说明就找完了
  2. i<j ,那么式子就是 jipijipj ,再捣鼓一下就是找 j[i+1,i+pi],jiji 的数。于是乎查找这个范围内的最小值,若是最小值大于 i 了,说明就找完了

标记一个点是否走过只需要将相应的值改为正无穷/负无穷就行了

然后跑一些普通的 bfs 就可以得到答案了

关于调试

突然发现我这个多测不用手动清空哎

其实没什么太多的细节,但是要注意不要忘记标记起点,以及因为存在 jpjj+pj 可能会越界,所以区间查询的时候不要取到负数和大于 n 的数

再次感谢&膜拜fjj大佬

#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define mkp make_pair
#define fst first
#define scd second
using namespace std;
const int N=2e5+5;
int inf=9e18;
int T;
int n,a,b;
int p[N];
struct SegmentTree
{
	struct node 
	{
		int l,r;
		int mn,mx;
		int u1,u2;
	}tr[N<<2];
	void push_up(int id)
	{
		int ls=id<<1,rs=id<<1|1;
		tr[id].mn=min(tr[ls].mn,tr[rs].mn);
		if (tr[ls].mn<tr[rs].mn) tr[id].u1=tr[ls].u1;
		else tr[id].u1=tr[rs].u1;
		tr[id].mx=max(tr[ls].mx,tr[rs].mx); 
		if (tr[ls].mx>tr[rs].mx) tr[id].u2=tr[ls].u2;
		else tr[id].u2=tr[rs].u2;
	}
	void build(int id,int l,int r)
	{
		tr[id].l=l,tr[id].r=r;
		if (l==r) 
		{
			tr[id].mn=max(l-p[l],1ll),tr[id].mx=min(l+p[l],n); tr[id].u1=tr[id].u2=l; 
			if (l==a) tr[id].mn=inf,tr[id].mx=-inf; 
			return ;
		}
		
		int mid=(l+r)>>1;
		build(id<<1,l,mid),build(id<<1|1,mid+1,r);
		push_up(id);
	}
	pii query1(int id,int l,int r)//查询最小值 //fst节点,scd值 
	{
		if (tr[id].l>=l&&tr[id].r<=r) return mkp(tr[id].u1,tr[id].mn);
		
		pii res=mkp(inf,inf);
		int mid=(tr[id].l+tr[id].r)>>1;
		if (mid>=l) res=query1(id<<1,l,r);
		if (mid+1<=r) { pii res2=query1(id<<1|1,l,r); if (res.scd>res2.scd) res=res2; }
		return res; 
	}
	pii query2(int id,int l,int r)
	{
		if (tr[id].l>=l&&tr[id].r<=r) return mkp(tr[id].u2,tr[id].mx);
		
		pii res=mkp(-inf,-inf);
		int mid=(tr[id].l+tr[id].r)>>1;
		if (mid>=l) res=query2(id<<1,l,r);
		if (mid+1<=r) { pii res2=query2(id<<1|1,l,r); if (res.scd<res2.scd) res=res2; }
		return res;
	}
	void update1(int id,int x)
	{
		if (tr[id].l==tr[id].r&&tr[id].l==x) { tr[id].mn=inf; return ; }
		
		int mid=(tr[id].l+tr[id].r)>>1;
		if (x<=mid) update1(id<<1,x);
		else update1(id<<1|1,x);
		push_up(id);
	}
	void update2(int id,int x)
	{
		if (tr[id].l==tr[id].r&&tr[id].l==x) { tr[id].mx=-inf; return ; }
		
		int mid=(tr[id].l+tr[id].r)>>1;
		if (x<=mid) update2(id<<1,x);
		else update2(id<<1|1,x);
		push_up(id);
	}
}Tr;
int bfs()
{
	queue <pii> q;
	q.push(mkp(a,0));
	
	while (!q.empty())
	{
		int u=q.front().fst,val=q.front().scd;
		q.pop();
		if (u==b) return val;
		
		while (1)
		{
			pii v=Tr.query1(1,u+1,min(n,u+p[u]));
			if (v.scd>u) break;
			q.push(mkp(v.fst,val+1));
			Tr.update1(1,v.fst);
		}
		while (1)
		{
			pii v=Tr.query2(1,max(u-p[u],1ll),u-1);
			if (v.scd<u) break;
			q.push(mkp(v.fst,val+1));
			Tr.update2(1,v.fst);
		}
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>T;
	while (T--)
	{
		cin>>n>>a>>b;
		for (int i=1;i<=n;i++) cin>>p[i];
		
		Tr.build(1,1,n);
		cout<<bfs()<<"\n";
	}
	return 0;
}

学下主席树

做完第一题感觉剩下题都不可做 于是来做这道唯一的蓝题

不严谨的理解:

主席树是权值线段树,所以肯定是处理权值有关的东西 这不废话 然后……

主席树一般处理的是查询区间 [l,r]k 小的数,那么还是先把 [l,r] 掰成 [1,r][1,l1] 来搞一棵下标在 [l,r] 间的权值线段树,用二分那样的流程找到第 k 大的值。这样看来岂不是建 n 棵区间 [1,i] 的权值线段树不就行了!

但是空间复杂度就会爆炸了。怎么办捏。容易发现 [1,i] 的权值线段树和 [1,i+1] 的权值线段树差别很小,所以每次只需要记录和上一次线段树的不同就行(也就是可持久化),以及使用动态开点

理论搞定,程序咋写呢 我不知道呢


知识补充完毕,那么这道题……呃啊呃啊

若左子树的元素个数超过一半,那么所求一定就在左子树中,反之就在右子树。所以可以用主席树

然后在主席树板子上改一下应该就行了 但是我不会啊

程序实现相关

因为是可持久化,所以每次更新只需要把不同的地方新开个点就行,相同的地方直接沿用之前的点

然后在查询的时候不用像整体二分那样在到右子树的时候下传 klres ,因为这是权值线段树啊权值线段树!!!

敲错一个字母爆改一小时
#incIude <bits/stdc++.h>
using namespace std;
const int N=5e4+5;
int n,m;
int rt[N],s,t;
int ans;
struct node 
{
	struct NODE{
		int ls,rs;
		int sum;
	}tr[N<<5];
	int cnt=0;
	int newnode(int ls,int rs,int sum) { tr[++cnt]={ls,rs,sum}; return cnt; }
	int update(int id,int l,int r,int k)
	{
		if (l==r) return newnode(0,0,tr[id].sum+1);
		
		int mid=(l+r)>>1;
		if (k<=mid) return newnode(update(tr[id].ls,l,mid,k),tr[id].rs,tr[id].sum+1);
		else return newnode(tr[id].ls,update(tr[id].rs,mid+1,r,k),tr[id].sum+1);
	}
	void query(int id1,int id2,int l,int r,int k)//id2>id1
	{
		if (l==r) { ans=l; return ; }
		
		int mid=(l+r)>>1;
		int lsum=tr[tr[id2].ls].sum-tr[tr[id1].ls].sum,rsum=tr[tr[id2].rs].sum-tr[tr[id1].rs].sum;
		if (lsum>k) query(tr[id1].ls,tr[id2].ls,l,mid,k);
		if (rsum>k) query(tr[id1].rs,tr[id2].rs,mid+1,r,k);
	}
}Tr;

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>m;
	for (int i=1,c;i<=n;i++)
	{
		cin>>c;
		rt[i]=Tr.update(rt[i-1],1,50000,c);
	}
	while (m--)
	{
		cin>>s>>t;
		ans=0,Tr.query(rt[s-1],rt[t],1,50000,(t-s+1)>>1);
		cout<<ans<<"\n";
	}
	return 0;
}

还是主席树……吗?

神奇的小东西,但我不想写

放个大佬的题解(为数不多我看一遍基本上看懂的)

线段树优化建图

一个单源最短路,问题是如何建边

看到区间,联想到线段树。将 [l,r] 掰成 log 个区间,每次操作就将节点 u 向那若干个区间连边(或反向连边)。因为有的是从大到小,有的是从小到大,所以建俩线段树,一个是可以从父节点走到子节点,一个是可以从子节点走到父节点

那么两颗线段树间该如何连接呢

首先,不是所有的区间都可以互相连接,不然就会喜提全部输出 0 。其次,两颗线段树必须有连接,那么肯定就是相同的叶子节点之间连接了

然后,要注意各种数组大小,不然会喜提 RE

调试问题

建边建边建边建边

#incIude <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define mkp make_pair
using namespace std;
const int N=1e6+5;
const int ea=5e5;
const int inf=0x3f3f3f3f3f3f3f3f;
int n,m,s;
struct node { int nxt,val; };
vector <node> e[N<<1];
int idx[N];
int op,v,l,r,w;
struct Segment_Tree
{
	struct Node{
		int l,r;
		int w;
	}tr[N<<2];
	void build(int id,int l,int r)
	{
		tr[id].l=l,tr[id].r=r;
		if (l==r) { idx[l]=id;  e[id].push_back({id+ea,0}); e[id+ea].push_back({id,0}); return ; }
		
		e[id].push_back({id<<1,0}),e[id].push_back({id<<1|1,0});
		e[ea+(id<<1)].push_back({id+ea,0}),e[ea+(id<<1|1)].push_back({id+ea,0});
		int mid=(l+r)>>1;
		build(id<<1,l,mid),build(id<<1|1,mid+1,r);
	}
	void update1(int id,int u,int l,int r,int w)
	{
		if (tr[id].l>=l&&tr[id].r<=r) { e[u].push_back({id,w}); return ; }
		
		int mid=(tr[id].l+tr[id].r)>>1;
		if (mid>=l) update1(id<<1,u,l,r,w);
		if (mid+1<=r) update1(id<<1|1,u,l,r,w);
	}
	void update2(int id,int u,int l,int r,int w)
	{
		if (tr[id].l>=l&&tr[id].r<=r) { e[id+ea].push_back({u,w}); return ; }
		
		int mid=(tr[id].l+tr[id].r)>>1;
		if (mid>=l) update2(id<<1,u,l,r,w);
		if (mid+1<=r) update2(id<<1|1,u,l,r,w);
	}
}Tr;

priority_queue < pii,vector <pii>,greater<pii> > q;
int dis[N],vis[N];
void dijkstra()
{
	memset(dis,0x3f,sizeof dis);
	q.push(mkp(0,idx[s]));
	dis[idx[s]]=0;
	while (!q.empty())
	{
		int u=q.top().second;
		q.pop();
		if (vis[u]) continue;
		vis[u]=1;
		
		int _size=e[u].size();
		for (int i=0;i<_size;i++)
		{
			int v=e[u][i].nxt,w=e[u][i].val;
			if (dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				q.push(mkp(dis[v],v));
			}
		}
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>m>>s;
	Tr.build(1,1,n);
	for (int i=1;i<=m;i++)
	{
		cin>>op;
		if (op==1)
		{
			cin>>v>>l>>w;
			e[idx[v]].push_back({idx[l],w});
		}
		else
		{
			cin>>v>>l>>r>>w;
			if (op==2) Tr.update1(1,idx[v],l,r,w);
			else Tr.update2(1,idx[v],l,r,w);
		}
	}
	dijkstra();
	for (int i=1;i<=n;i++) if (dis[idx[i]]!=inf) cout<<dis[idx[i]]<<" "; else cout<<-1<<" "; 
	return 0;
}

线段树优化建图2?

难绷。区间连区间很扯,所以建一个中间点e,f,前区间向 e 连有向边, e 向后区间连有向边,f 相反。

然后跑一个 01BFS就行了(貌似相当于边权只有0和1的 dij)

后话:虽然似乎很简单,但我一个晚上还是没做出来。晚上“两觉”都做了梦,都梦到莫名其妙把这题过了……结果全是梦QAQ

花了一个早上终于调出来了

点击查看代码
#incIude <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define mkp make_pair
#define fst first
#define scd second
using namespace std;
const int N=5e5+2;
const int MAXN=6e6+5;
const int ea=2e6;
const int inf=0x3f3f3f3f3f3f3f3f;
int n,m,s;
int a,b,c,d;
int idx[N],nw;
struct Node { int nxt,val; };
vector <Node> e[MAXN];
struct SegmentTree
{
	struct node { int l,r; }tr[N<<2];
	void build(int id,int l,int r)
	{
		tr[id].l=l,tr[id].r=r;
		if (l==r)
		{
			e[id].push_back({id+ea,0}),e[id+ea].push_back({id,0});
			idx[l]=id; return ; 
		}
		
		e[id].push_back({id<<1,0}),e[id].push_back({id<<1|1,0});
		e[(id<<1)+ea].push_back({id+ea,0}),e[(id<<1|1)+ea].push_back({id+ea,0});
		int mid=(l+r)>>1;
		build(id<<1,l,mid),build(id<<1|1,mid+1,r);
	}
	void update1(int id,int u,int l,int r,int w)//u->l,r
	{
		if (tr[id].l>=l&&tr[id].r<=r) { e[u].push_back({id,w}); return ; }
		
		int mid=(tr[id].l+tr[id].r)>>1;
		if (mid>=l) update1(id<<1,u,l,r,w);
		if (mid+1<=r) update1(id<<1|1,u,l,r,w);
	}
	void update2(int id,int u,int l,int r,int w)
	{
		if (tr[id].l>=l&&tr[id].r<=r) { e[id+ea].push_back({u,w}); return ; }
		
		int mid=(tr[id].l+tr[id].r)>>1;
		if (mid>=l) update2(id<<1,u,l,r,w);
		if (mid+1<=r) update2(id<<1|1,u,l,r,w);
	}
}Tr;

int dis[MAXN],vis[MAXN];
deque <int> q;
void bfs01()
{
	q.push_front(idx[s]);
	vis[idx[s]]=1;
	while (!q.empty())
	{
		int u=q.front();
		q.pop_front();
		
		int _size=e[u].size();
		for (int i=0;i<_size;i++)
		{
			int v=e[u][i].nxt,w=e[u][i].val;
			if (vis[v]) continue;
			dis[v]=dis[u]+w;
			vis[v]=1;
			if (w) q.push_back(v);
			else q.push_front(v);
		}
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>m>>s;
	Tr.build(1,1,n);
	nw=ea+ea;
	
	while (m--)
	{
		cin>>a>>b>>c>>d;
		Tr.update1(1,++nw,a,b,1),Tr.update2(1,nw,c,d,1);
		Tr.update2(1,++nw,a,b,1),Tr.update1(1,nw,c,d,1);
	}
	
	bfs01();
	for (int i=1;i<=n;i++) cout<<dis[idx[i]]/2<<"\n";
	return 0;
}

然后是动态DP,不会不听了

似乎是用数据结构优化矩阵乘法


Day 4-模拟赛+发烧

头晕头晕,好虚好虚,嗓子好疼

妈妈我想回家


Day 5-数据结构2

作业链接

?我来机房干什么,想不了题……

不知道和数据结构有啥关系

看到每次降雨形似等差数列,肯定是用差分数组维护降雨量,再用较大值判断 m 的限制就行;而这些较大值就是每个 xi (降雨中心),于是先用差分数组求出每个 xi 的降雨量 ai

关于差分

第一眼想的就是用树状数组区间修改维护形如 aiai1 的差分数组,最后前缀和求出 ai 。打开题解,发现了另一种维护差分数组的方法(比树状数组还少一个 log ……)

因为每一次降雨量都可以用一次函数解析式的方式表达,那么也就是说每一个点的降雨量就可以用一堆一次函数解析式的和表示出……换言之,降雨量可以用斜率和and截距和表达的一次函数表示出来

那么就可以直接在斜率和截距上差分了,就不用树状数组还带个 log

对于那些 au>m 的降雨量,若没有第 i 场雨后就满足限制,那么就满足 aupi+|xixu|m ,变形移项后得auxupixi+m,au+xupi+xi+m。所以对于第 i 场雨,只要满足上式答案就是 1 反之则为 0。用 mx1,mx2 分别维护 auxu,au+xu 的最大值判断即可

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
const int inf=0x3f3f3f3f3f3f3f3f;
int T;
int n,m;
struct node { int x,p,id; }op[N];
int k[N],b[N],a[N];
int mx1,mx2,ans[N];

bool cmp(node x,node y) { return x.x<y.x; }
int find_l(int x)
{
	int l=1,r=n;
	while (l<r)
	{
		int mid=(l+r)>>1;
		if (op[mid].x>=x) r=mid;
		else l=mid+1;
	}
	return r;
}
int find_r(int x)
{
	int l=1,r=n;
	while (l<r)
	{
		int mid=(l+r+1)>>1;
		if (op[mid].x<=x) l=mid;
		else r=mid-1;
	}
	return l;
}
void init()
{
	sort(op+1,op+1+n,cmp);
	for (int i=1;i<=n;i++)
	{
		int l=find_l(op[i].x-op[i].p),r=find_r(op[i].x+op[i].p);
		k[l]++,k[i+1]-=2,k[r+1]++;
		b[l]+=op[i].p-op[i].x,b[i+1]-=op[i].p-op[i].x,b[i+1]+=op[i].p+op[i].x,b[r+1]-=op[i].x+op[i].p;
	}
	for (int i=1;i<=n;i++)
	{
		k[i]+=k[i-1],b[i]+=b[i-1];
		a[i]=k[i]*op[i].x+b[i];
		if (a[i]>m) mx1=max(mx1,a[i]-op[i].x),mx2=max(mx2,a[i]+op[i].x);
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>T;
	while (T--)
	{
		memset(k,0,sizeof k);
		memset(b,0,sizeof b);
		mx1=-inf,mx2=-inf;
		cin>>n>>m;
		for (int i=1;i<=n;i++) 
		{
			cin>>op[i].x>>op[i].p;
			op[i].id=i;
		}
		init();
		for (int i=1;i<=n;i++)
		{
			if (op[i].p-op[i].x+m>=mx1&&op[i].p+op[i].x+m>=mx2) ans[op[i].id]=1;
			else ans[op[i].id]=0;
		}
		for (int i=1;i<=n;i++) cout<<ans[i];
		cout<<"\n";
	}
	return 0;
}

小结论+树状数组

气人,我还不会离散化
难得我推出了结论啊 hia hia hia

查询操作的瓶颈肯定是那些小于 s 的数。若大于等于 s 的数的个数 cnt 不够 c 个,那么剩下的 d=ccnt 个数就要在小于 s 的数里选了

那么,如果这些数的和满足 sumd×s ,那就肯定能选出来,反之就一定选不出来。证明略

然后离线下来,用树状数组离散化就行了

后续:卡常两年半无果

老子再也不用map离散化了!!!
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+3;
int n,m,a[N];
int bt1[N];
ll bt2[N];
int num[N];
struct node { char op; int k,a,id; }q[N];
int idx[N];

int read()
{
	int x=0;char c=getchar();
	while (c<'0'||c>'9') c=getchar();
	while (c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c-'0'),c=getchar();
	return x;
}
inline int lowbit(int x) { return x&-x; }
inline void add1(int x,int y){ while (x<=m) { bt1[x]+=y; x+=lowbit(x); }}
inline void add2(int x,int y){ while (x<=m) { bt2[x]+=y; x+=lowbit(x); }}
inline int sum1(int x){	int res=0; while (x) { res+=bt1[x]; x-=lowbit(x); } return res; }
inline ll sum2(int x){ ll res=0; while (x) { res+=bt2[x]; x-=lowbit(x); } return res; }
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	n=read(),m=read();
	for (int i=1;i<=m;i++) 
	{
		do { q[i].op=getchar(); } while(q[i].op!='U'&&q[i].op!='Z');
		q[i].k=read(),q[i].a=read(),q[i].id=i;
		num[i]=q[i].a;
	}
	
	sort(num+1,num+1+m);
	int p=unique(num+1,num+m+1)-num-1;
	for (int i=1,pre;i<=m;i++)
	{
		pre=q[i].a;
		q[i].a=lower_bound(num+1,num+p+1,q[i].a)-num;
		idx[q[i].a]=pre;
	} 

	int tol=0;
	for (int ea=1;ea<=m;ea++)
	{
		char op=q[ea].op;
		int k=q[ea].k,aa=q[ea].a;
		if (op=='U') 
		{
			if (a[k]) add1(a[k],-1),add2(a[k],-idx[a[k]]),tol--;
			add1(aa,1),add2(aa,idx[aa]),tol++;
			a[k]=aa;
		}
		else
		{
			int cnt=sum1(aa-1);
			if (tol-cnt>=k) { cout<<"TAK"<<"\n"; continue; }
			int delta=k-tol+cnt;
			if (sum2(aa-1)>=1ll*delta*idx[aa]) cout<<"TAK"<<"\n";
			else cout<<"NIE"<<"\n"; 
		}
	}
	return 0;
}

树状数组维护三阶前缀和

首先,题解大佬们想到了计算序列中每一个数 ai 对答案的贡献(不知是怎样看到 ai 范围就想到的)

设目前这个数 num 在位置 i 前一共出现了 Si 次,若满足在区间 [l,r]num是众数且出现次数严格大于区间长度,则可以列出方程式 SrSl1>(rl+1)(SrSl1) ,移项后得 Sr×2r>Sl1×2(l1) ,观察式子,发现对 Sii 操作就可以了

pi=Si×2i ,对于每个 pi ,要求满足 pj<pi,j[1,i1]j 个数,容易想到权值树状数组维护。手摸一下,可以发现 pi 会形成若干个公差为 1 的等差数列,每个等差数列的首项在数字 num 在序列中出现的位置,且首项大小是上一等差数列的末项 +1 。因为所有 num 的等差数列的个数一定不超过 n 个,所以只要每次对一个等差数列操作就可以保证复杂度了

对于每个等差数列,每次修改是对于其中 r,r1,,l 的每个元素 [l,r] 的值 +1 ,区修想到树状数组维护差分,以及自带的第一层前缀和;对于每个 pi ,它要查询 pj[,pi],j[1,i1] 的个数,这是第二层前缀和,记它为 cnti ;为了保证复杂度,我们每次是要在一整个等差数列上操作,也就是要求的是 Σi=lrcnti ,这是第三层前缀和。也就是说,在这道题中需要用树状数组维护三阶前缀和(当然也可以用线段树维护,但常数嘛……)

式子不会推,直接拿的题解区的,大概是

sumi=(x+1)(x+2)2j=1iaj2x+32j=1iaj×j+12j=1iaj×j2

其中 ai 是刚开始的差分数组

呃啊
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+5;
int n,xixixi;
vector <int> pos[N];
int bt1[N<<1],bt2[N<<1],bt3[N<<1],ans;

int lowbit(int x) { return x&-x; }
void add(int x,int y)
{
	for (int i=x;i<=(n*2+1);i+=lowbit(i))
	{
		bt1[i]+=y;
		bt2[i]+=y*x;
		bt3[i]+=y*x*x;
	}
}
int sum(int x)
{
	if (x<=0) return 0;
	int res=0;
	for (int i=x;i>0;i-=lowbit(i)) res+=bt1[i]*(x+2)*(x+1)-bt2[i]*(x*2+3)+bt3[i];
	return res>>1;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>xixixi;
	for (int i=1,a;i<=n;i++)
	{
		cin>>a;
		pos[a].push_back(i);
	}
	for (int i=0;i<n;i++) pos[i].push_back(n+1);

	const int ea=n+1;
	for (int i=0;i<n;i++)
	{
		int _size=pos[i].size(),lst=0;
		for (int j=0;j<_size;j++)
		{
			int y=j*2-lst+ea,x=j*2-(pos[i][j]-1)+ea;
			ans+=sum(y-1)-sum(x-2);
			add(x,1),add(y+1,-1);
			lst=pos[i][j];
		}
		lst=0;
		for (int j=0;j<_size;j++)
		{
			int y=j*2-lst+ea,x=j*2-(pos[i][j]-1)+ea;
			add(x,-1),add(y+1,1);
			lst=pos[i][j];
		}
	}
	cout<<ans;
	return 0;
}

Day 6-模拟赛+CF赛

哈哈哈哈哈哈哈哈哈哈哈哈马上就能回家啦哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈

但今天要打模拟赛(悲 希望今天不会那么爆炸吧

吗的八嘎 我将毁灭世界

于是~来总结一下今天的神奇模拟赛~

作业链接

T1唐唐二分。赛时没想出来,打了个40分暴力,未挂分。

正解:二分。二分平均值,每次 check 求有多少数小于当前二分值 x 。将原序列所有数都减去 x ,balababla

T2唐唐模拟题,看完题就下一题了

T3一眼 整体二分 呸二维数点。但是带修。怎么办呢。

注意到每次只修改一个数,所以只需要计算这一个数对答案的贡献就行。设将原值 ai=num 修改为 p ,那么这次修改会对 lir,x(num,p] 的查询造成贡献(正负都有可能)。也就是说,还得对那 m 个查询二维数点一下

但是它强制在线,所以只能用主席树。然后直接做做完了。然后赛时主席树没调出来,怒挂60分

啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊正确的写法我在赛时想到了,但我没往上写,就差一点就可以了啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊

T3
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n,m,q,a[N];
struct node { int lim,val,id; };
vector <node> qr[N];
vector <int> add[N];
vector <node> add2[N];
int bt1[N];
int ans[N],sum;
int rt[N];

int lowbit(int x) { return x&-x; }
void add1(int x,int y) { while (x<=n) { bt1[x]+=y; x+=lowbit(x); } }
int sum1(int x)
{
	int res=0;
	while (x)
	{
		res+=bt1[x];
		x-=lowbit(x);
	}
	return res;
}
int solve1()
{
	for (int i=1;i<=n;i++)
	{
		int _size=add[i].size();
		for (int j=0;j<_size;j++) add1(add[i][j],1);
		
		_size=qr[i].size();
		for (int j=0;j<_size;j++) ans[qr[i][j].id]+=qr[i][j].val*(sum1(n)-sum1(qr[i][j].lim-1));
	}
	for (int i=1;i<=n;i++) sum+=ans[i];
	return sum;
}
struct zhuxishu
{
	int cnt=0;
	struct Node{
		int ls,rs,sum;
	}tr[N<<8];
	int newnode(int ls,int rs,int sum) { cnt++; tr[cnt]={ls,rs,sum}; return cnt; }
	int update(int id,int l,int r,int x,int w)
	{
		if (l==r) return newnode(0,0,tr[id].sum+w);
		
		int mid=(l+r)>>1;
		if (mid>=x) return newnode(update(tr[id].ls,l,mid,x,w),tr[id].rs,tr[id].sum+w);
		else return newnode(tr[id].ls,update(tr[id].rs,mid+1,r,x,w),tr[id].sum+w);
	}
	int query(int id,int l,int r,int ql,int qr)//求<=x的个数 
	{
		if (l>=ql&&r<=qr) return tr[id].sum;
		
		int mid=(l+r)>>1,res=0;
		if (mid>=ql) res+=query(tr[id].ls,l,mid,ql,qr);
		if (mid+1<=qr) res+=query(tr[id].rs,mid+1,r,ql,qr);
		
		return res;
	}
}Tr;
void init()
{
	int pre=0;
	for (int i=1;i<=n;i++)
	{
		rt[i]=rt[i-1];
		int _size=add2[i].size();
		for (int j=0;j<_size;j++) rt[i]=Tr.update(rt[i],1,n,add2[i][j].lim,add2[i][j].val);
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>m>>q;
	for (int i=1;i<=n;i++) { cin>>a[i]; add[i].push_back(a[i]); }
	for (int i=1,l,r,x;i<=m;i++)
	{
		cin>>l>>r>>x;
		qr[r].push_back({x,1,i}),qr[l-1].push_back({x,-1,i});
		add2[l].push_back({x,1,i}),add2[r+1].push_back({x,-1,i});
	}
	cout<<solve1()<<"\n";
	init();
	
	int p,v;
	while (q--)
	{
		cin>>p>>v;
		int d=sum^p,num=sum^v;
		if (num>a[d]) sum+=Tr.query(rt[d],1,n,a[d]+1,num);
		else if (num<a[d]) sum-=Tr.query(rt[d],1,n,num+1,a[d]);
		a[d]=num;
		cout<<sum<<"\n";
	}
	return 0;
}

T4不知道正解是啥。部分分是用背包做。一切的一切都很好,但是

空间限制32MB是什么鬼啊

下次一定滚动数组


开摸

补:晚上CF赛,前两题小调一下过了,C题没特判一直没过(急急急 于是乎,直接去补题了 嘻嘻嘻


Day 7-欧拉回路

哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈马上就能回家辣哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈

作业链接&wiki链接

有一些奇奇怪怪的东西在这里贴一下

无向图的欧拉路径:两个度数为奇数的点分别是起点和终点,其他点的度数都是偶数

无向图的欧拉回路:所有点的度数都是偶数

有向图的欧拉路径:出度比入度多1 的是起点,入度比出度多 1 的是终点,其他点入度出度相同

有向图的欧拉回路:所有点入度出度相同

喵.板子

先判存不存在欧拉回路(要么所有点入度出度都相同,要么只有起点的出度比入度多1、终点的入度比出度多1),然后直接 dfs 找就找完了

注意每条边只能走一次哦

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n,m;
int ind[N],oud[N];
int s=-1,t=-1,id[N];
vector <int> e[N];

stack <int> st;
void dfs(int x)
{
	int _size=e[x].size();
	for (int i=id[x];i<_size;i=id[x]) { id[x]=i+1; dfs(e[x][i]); }
	st.push(x);
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>m;
	for (int i=1,u,v;i<=m;i++)
	{
		cin>>u>>v;
		e[u].push_back(v);
		ind[v]++,oud[u]++;
	}
	for (int i=1;i<=n;i++) sort(e[i].begin(),e[i].end());
	
	int cnt=0;
	for (int i=1;i<=n;i++)
	{
		if (ind[i]==oud[i]) continue;
		if (ind[i]==oud[i]-1) s=i;
		else if (ind[i]==oud[i]+1) t=i;
		cnt++;
	}
	if (cnt!=0&&cnt!=2) { cout<<"No"; return 0; }
	if (!cnt) s=t=1;
	if (s==-1||t==-1) { cout<<"No"; return 0; }
	
	dfs(s);
	while (!st.empty()) { cout<<st.top()<<" "; st.pop(); }
	return 0;
}

喵喵.你还重边无向图

和板子一毛一样,除了这是无向图 with 重边。

注意要走所有重边,然后这里源点可能不是 1 因为 1 可能就不在图中

then全剧终
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=505;
int m,mn=510;
vector <int> e[N];
int vis[N][N];
int ind[N],s=510;
int idx[N];

stack <int> st;
void dfs(int x,int _fa)
{
	int _size=e[x].size();
	for (int i=idx[x];i<_size;i=idx[x]) 
	{
		idx[x]=i+1;
		int v=e[x][i];
		if (vis[x][v]) { vis[x][v]--,vis[v][x]--; dfs(e[x][i],x); }
	}
	st.push(x);
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>m;
	for (int i=1,u,v;i<=m;i++)
	{
		cin>>u>>v;
		e[u].push_back(v),e[v].push_back(u);
		ind[u]++,ind[v]++,vis[u][v]++,vis[v][u]++;
		mn=min(mn,(min(u,v)));
	}
	for (int i=1;i<=500;i++) sort(e[i].begin(),e[i].end());
	for (int i=1;i<=500;i++) if (ind[i]%2) { s=i; break; }
	if (s==510) s=mn;
	
	dfs(s,0);
	while (!st.empty()) { cout<<st.top()<<"\n"; st.pop(); }
	return 0;
}

喵喵喵.小转变的欧拉路径

根据字符建图跑欧拉路径就行了

貌似无向图就得开个 vis 数组才够呢 [微笑]

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=55;
int id[150],n;
vector <int> e[N];
int ind[N],idx[N];
int mn=150,s=150;
map <int,char> mp;
int vis[N][N];

stack <int> st;
void init()
{
	for (int i=1;i<=26;i++) id[(int)(i+'A'-1)]=i,mp[i]=(char)('A'+i-1);
	for (int i=1;i<=26;i++) id[(int)(i+'a'-1)]=i+26,mp[i+26]=(char)('a'+i-1); 
}
void dfs(int x)
{
	int _size=e[x].size();
	for (int i=idx[x];i<_size;i=idx[x])
	{
		idx[x]=i+1; 
		int v=e[x][i];
		if (vis[x][v]) 
		{
			vis[x][v]=vis[v][x]=0;
			dfs(e[x][i]); 
		}
	}
	st.push(x);
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	init();
	cin>>n;
	for (int i=1;i<=n;i++)
	{
		char u,v;
		cin>>u>>v;
		e[id[(int)(u)]].push_back(id[(int)(v)]);
		e[id[(int)(v)]].push_back(id[(int)(u)]);
		ind[id[(int)(u)]]++,ind[id[(int)(v)]]++;
		mn=min(mn,min(id[(int)(u)],id[(int)(v)]));
		vis[id[(int)(u)]][id[(int)(v)]]=1,vis[id[(int)(v)]][id[(int)(u)]]=1;
	}

	for (int i=1;i<=52;i++) sort(e[i].begin(),e[i].end());
	
	int cnt=0;
	for (int i=1;i<=52;i++) if (ind[i]%2) cnt++,s=min(s,i);
	if (cnt!=0&&cnt!=2) { cout<<"No Solution"; return 0; }
	if (s==150) s=mn;
	
	dfs(s);
	while (!st.empty()) { cout<<mp[st.top()]; st.pop(); }
	return 0;
} 

喵喵喵喵.我们充分发扬人类智慧

然后开始打表

复制得下吗?

人类群星闪耀时

喵喵喵喵喵.利用欧拉回路小性质

首先显然,要翻转色的话白边需跑偶数次,黑边需跑奇数次

其次不显然,若需要跑白边,只可能是跑两次白边、一次黑边,这是可以放在一个欧拉回路上的(可以想到这是两个黑边联通块通过一条白边相连的情况);若不跑白边,那么还是要跑一次黑边,还是一个欧拉回路(可以想到这是一整个黑边联通块)。所以,欧拉回路的数量只和黑边有关(换句话说,只和带黑边的可跑欧拉回路的联通块有关)。

用度数判断一个联通块是否可跑欧拉回路,再记下这种联通块的个数就是答案了。
(注意判断连通性是在原图上进行,统计度数时不算白边的)

综上,只需维护度数为奇数的点的个数以及带黑边的联通块的个数。带不带黑边?用并查集维护集合中白点个数和总点数一比较就好咯

嘻嘻嘻嘻嘻
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,m,q;
int op,x;
int fa[N],siz[N],sum[N],ind[N];
int ea[N];
struct node { int u,v,c; }edge[N];
int cnt,ans;

int find_fa(int x)
{
	if (fa[x]==x) return x;
	return fa[x]=find_fa(fa[x]);
}
void merge(int x,int y)
{
	int fa1=find_fa(x),fa2=find_fa(y);
	if (fa1==fa2) return ;
	fa[fa1]=fa2;
	siz[fa2]+=siz[fa1],siz[fa1]=0;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>m;
	for (int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
	for (int i=1,u,v,c;i<=m;i++) 
	{
		cin>>u>>v>>c;
		edge[i]={u,v,c};
		if (c) ind[u]++,ind[v]++;
		merge(u,v);
	}
	for (int i=1;i<=n;i++)
	{
		if (ind[i]%2) cnt++;//无法欧拉回路的联通块个数 
		if (!ind[i]) sum[find_fa(i)]++;
		if (find_fa(i)==i) ea[i]=1;
	}
	for (int i=1;i<=n;i++) if (ea[i]&&siz[i]>sum[i]) ans++;
	
	cin>>q;
	while (q--)
	{
		cin>>op;
		if (op==1)
		{
			cin>>x;
			x++;
			int u=edge[x].u,v=edge[x].v,fa=find_fa(u);
			if (edge[x].c) 
			{
				ind[u]--,ind[v]--;
				if (!ind[u]) sum[fa]++;
				if (!ind[v]) sum[fa]++;
				ans-=sum[fa]==siz[fa];
			}
			else
			{
				ind[u]++,ind[v]++;
				int flag=0;
				if (ind[u]==1) sum[fa]--,flag++;
				if (ind[v]==1) sum[fa]--,flag++;
				ans+=(sum[fa]+flag==siz[fa])&&flag;
			}
			
			edge[x].c^=1;
			if (ind[u]%2) cnt++; else cnt--;
			if (ind[v]%2) cnt++; else cnt--;
		}
		else
		{
			if (cnt) { cout<<"-1\n"; continue; }
			else cout<<ans<<"\n";
		}
	}
	return 0;
}

喵喵喵喵喵喵.利用欧拉回路小性质的史

和上一题有异曲同工之妙nie 但是不带修 好评!

我撤回我的好评。因为这题要求输出简单环,所以要把欧拉回路掰成简单环输出。然后就很像 Tarjan 了

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
const int M=1e6+6;
int n,m;
vector <int> e[N];
vector <int> f[N];
int flag[M];
int ind[N],idx[N];
int sum;
vector <int> ans[M];

stack <int> st;
int vis[N];
void dfs(int x)
{
	int _size=e[x].size();
	for (int i=idx[x];i<_size;i=idx[x])
	{
		int v=e[x][i];
		idx[x]=i+1;
		if (flag[f[x][i]]) continue;//无向图的vis数组
		flag[f[x][i]]=1,dfs(v);
	}
	if (vis[x])
	{
		sum++;
		ans[sum].push_back(x);
		while (st.top()!=x)
		{
			vis[st.top()]=0;
			ans[sum].push_back(st.top());
			st.pop();
		}
		ans[sum].push_back(x),st.pop(),vis[x]=0;
	}
	st.push(x),vis[x]=1;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>m;
	for (int i=1,u,v,c1,c2;i<=m;i++)
	{
		cin>>u>>v>>c1>>c2;
		if (c1==c2) continue;
		ind[u]++,ind[v]++;
		e[u].push_back(v),e[v].push_back(u);
		f[u].push_back(i),f[v].push_back(i);
	}
	
	for (int i=1;i<=n;i++) if (ind[i]&1) { cout<<"NIE"; return 0; }
	for (int i=1;i<=n;i++) if (idx[i]<e[i].size()) dfs(i);
	
	cout<<sum<<"\n";
	for (int i=1;i<=sum;i++)
	{
		int _size=ans[i].size();
		cout<<_size-1<<" ";
		for (int j=0;j<_size;j++) cout<<ans[i][j]<<" ";
		cout<<"\n";
	}
	return 0;
}

好困……好。。。。。。困。。。。。。

感觉完全没力气思考了。想睡觉 www

www

嗓子像是在刮痧

www

再写两题就摆烂……


呜呜呜呜呜呜呜.加边构造欧拉路径

把题面翻译成人话,就是根据输入建图,跑欧拉路径,最后输出欧拉路径上经过的点的数量。

那么首先,每条边肯定至少被经过一次,有多少欧拉回路/欧拉路径,就会再多几个端点。原图会分成若干个不交 的子图,它可能在子图上跑欧拉路径,也有可能跑欧拉回路。若是一子图中存在欧拉路径,对于出度入度差 d 为奇数的点来说,它会作为起点或终点 d 次,换言之,统计出的 |indioudi|2 就是欧拉路径的数。

那么,剩下的没有欧拉路径的联通块就是跑的欧拉回路了,统计一下就好咯

#include <bits/stdc++.h>
#define pii pair<int,int>
#define mkp make_pair
using namespace std;
const int N=1e6+5;
const int M=1005;
int n,mx,ans,sum;
int ind[M],oud[M];
int fa[M],f[M],yes[M];
map <pii,int> mp;

int find_fa(int x)
{
	if (fa[x]==x) return x;
	return fa[x]=find_fa(fa[x]);
}
void merge(int x,int y)
{
	int fa1=find_fa(x),fa2=find_fa(y);
	fa[fa1]=fa2;
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	for (int i=1;i<=1000;i++) fa[i]=i;
	cin>>n;
	for (int i=1,l,r;i<=n;i++)
	{
		cin>>l>>r;
		if (mp[mkp(l,r)]) continue;
		ans++,mp[mkp(l,r)]=1;
		ind[r]++,oud[l]++;
		yes[l]=yes[r]=1;
		merge(l,r);
		mx=max(mx,max(l,r));
	}
	
	for (int i=1;i<=mx;i++)
	{
		if (ind[i]==oud[i]) continue;
		sum+=abs(ind[i]-oud[i]);
		f[find_fa(i)]=1;
	}
	for (int i=1;i<=mx;i++) if (!f[i]&&fa[i]==i&&yes[i]) ans++;
	cout<<ans+(sum>>1);
	return 0;
}

Day 8-周赛

好消息:明天就可以回家了 坏消息:今天开始有鼻涕了www

模拟赛水过,反正不是倒数 嘻嘻

全是之前模拟赛的原题,就不总结了

posted @   还是沄沄沄  阅读(47)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示