24.10.7(线段树合并,分裂,扫描线,字符串哈希)

news2024/10/8 11:31:26

星期一:

昨晚熬夜场的div2总算是不负有心人,到C都比较简单,出C后我也没有run的想法,一直在看D,最后5min的时间ac,小小上了波分

贴cf round976 div2 D                                    cf传送门

题意:给m次连接点的操作,问最后的连通块数

思路:暴力的复杂度是 n*m,但我们注意到一个很关键的信息 -- d<=10,马上就想到可以按d给操作分个类,对于不同的d,可以按不同的起点分类,例如d==3,有三个不同的起点,d==10,就有10种不同的起点

计算下共有385种不同的操作,若能对于每种操作,处理出其操作的区间,那么就能以 O(n*10)的复杂度处理完毕。在想到这后简单想了下如何处理区间之间的交,合并等操作,感觉并不太好实现(现在能实现了,先将区间排序),于是,上线段树!(区改,单查

对于每种操作,先找出操作的类型,然后对其操作的区间赋值,这就是线段树的目的。但会发现385种操作类型过于庞大,节点并不太好维护此信息(可以开个长度为10数组,每个值都是压缩的状态),我这里是分10次用线段树,对于起点为st的操作,给区间l,r赋上 1<<st的值

re了两发,wa了两发扣了有点多分

RE:没注意到 k==0时,我的l会大于r,给线段树操作就会寄

WA:太久没写并查集,合并写唐了

代码如下:

const int N=2e6+10,M=1e4+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
	struct nod{
		int l,r; //385
		ll opt,tag;
	}t[N];
	int opd,ql,qr,qv;
	nod merge(nod a,nod b){
		nod res;
		res.l=a.l,res.r=b.r;
		res.opt=a.opt|b.opt;
		res.tag=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,0,0};
		if(l==r) return ;
		int mid=l+r>>1;
		bd(lc,l,mid);
		bd(rc,mid+1,r);
	}
	void pushdn(int p){
		if(!t[p].tag) return ;
		t[lc].opt|=t[p].tag;
		t[lc].tag|=t[p].tag;
		t[rc].opt|=t[p].tag;
		t[rc].tag|=t[p].tag;
		t[p].tag=0;
	}
	void update(int p){
		if(ql<=t[p].l && qr>=t[p].r){
			t[p].opt|=(1<<qv);
			t[p].tag|=(1<<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);
	}
	void updt(int l,int r,int v){
		ql=l,qr=r;
		qv=v;
		update(1);
	}
	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(qr<=mid) return query(lc);
		if(ql>mid) return query(rc);
	}
	ll ask(int l,int r){
		ql=l,qr=r;
		return query(1).opt;
	}
}tr;
vector<PII>op[11];
int fa[N];
int fnd(int x){
	return fa[x]==x?x:fa[x]=fnd(fa[x]);
}
void solve(){
	int m; cin >> n >> m;
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=10;i++) op[i].clear();
	for(int i=1;i<=m;i++){
		int a,d,k; cin >> a >> d >> k;
		if(k) op[d].push_back({a,k});
	}
	for(int d=1;d<=10;d++){
		tr.bd(1,1,n);
		for(auto [a,k]:op[d]){
			int l=a,r=a+(k-1)*d;
			int st=(a-1)%d+1;
			tr.updt(l,r,st);    //若!k, r==l-d !!!会RE!!!
		}
		for(int st=1;st<=d;st++){
			for(int i=st;i<=n;i+=d){
				if(tr.ask(i,i)&1<<st){
					int u=fnd(i),v=fnd(i+d);
//					if(u!=v) fa[i]=fa[i+d]; //并查集合并不是这样写的!!!
					if(u!=v) fa[u]=v;
				}
			}
		}
	}
	ll ans=0;
	for(int i=1;i<=n;i++) ans+=(fnd(i)==i);
	cout << ans << "\n";
}

补cf round975 div2 D                                   cf传送门

思路:猜个结论,答案一定是一段连续区间。然后想个贪心策略,每次前往还没征服的ai最小城市,由此可得到个解法,以一最小ai为起点,向左右二分check得到区间的左右端点,可惜没想到这个解法

官方的解法是,对于 i,找出最小的涵盖所有ai<=i的区间【l,r】,若此区间长度>i,则可直接得到答案为0,否则可得到对于这段区间,合法的起点范围为【r-i+1,l+i-1】,将所有起点区间求交集即可,这里用的差分,注意差分时 l不能小于1

代码如下:

const int N=2e6+10,M=1e4+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
int a[N],pre[N],suf[N];
PII inv[N];
int sum[N];
void solve(){
	cin >> n;
	pre[0]=1e9;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		pre[i]=min(a[i],pre[i-1]);
		sum[i]=0;
	}
	suf[n+1]=1e9;
	for(int i=n;i;i--) suf[i]=min(a[i],suf[i+1]);
	int l=0,r=0;
	for(int i=1;i<=n;i++) if(a[i]==pre[n]){
		if(!l) l=i;
		r=i;
	}
	for(int i=pre[n];i<=n;i++){
		while(pre[l-1]<=i) l--;
		while(suf[r+1]<=i) r++;
		inv[i]={l,r};
	}
//	for(int i=pre[n];i<=n;i++) cout << inv[i].first << " " << inv[i].second << "\n";
//	cout << "\n";
	for(int i=pre[n];i<=n;i++) if(inv[i].second-inv[i].first+1<=i){
		int l=max(inv[i].second-i+1,1ll),r=inv[i].first+i-1;
		sum[l]++,sum[r+1]--;
//		cout << l << " " << r << "\n";
	}
	ll ans=0;
	for(int i=1;i<=n;i++){
		sum[i]+=sum[i-1];
		ans+=(sum[i]==n-pre[n]+1);
//		cout << sum[i] << " \n"[i==n];
	}
	cout << ans << "\n";
}

补补队友a的签到构造                                      cf传送门

思路:首先每行每列起码得放俩,放什么位置比较方便呢,易想到 (1,1), (1,2), (2,2), (2,3), (3,3)...的方法,最后一个放(n,1),观察一下发现这些数刚好是俩俩挨着的,再想到 num和num+1互质,即可得到按上述位置放从1到2*n的构造方法,此时所有行列gcd都为1

多的数随便放就行,注意判别放重了,特别注意(n,1)这个点,在这wa了一发

代码如下:

const int N=2e6+10,M=1e4+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;

void solve(){
	ll k; cin >> n >> k;
	int x=1,y=1;
	while(x<=n && y<=n){
		cout << x << " " << y << "\n";
		if(x==y) y++;
		else x++;
	}
	cout << n << " " << 1 << "\n";
	ll num=n*2+1;
	for(int x=1;x<=n && num<=k;x++){
		for(int y=1;y<=n && num<=k;y++){
			if(y==x || y==x+1 || (x==n && y==1)) continue;
			cout << x << " " << y << "\n";
			num++;
		}
	}
}

星期二:

国庆长假,好好把握😽😽💪

24牛客国庆集训1 J                                     牛客传送门

题意:给一完全图,边分黑白两色,问有多少三角形满足三条边颜色相同

思路:暴力枚举复杂度为 n^3。观察下也许能发现一个不合法的三角形的特点,即边一定为两黑一白或两白一黑,再看看点,发现三个点中只有一个点两条边颜色相同,其余两点连接的边均为一白一黑。那么对于点 a,包含它的不合法三角形的个数即为 (a的黑边数)*(a的白边数),所以可以用一变量ms遍历一遍点集计算所有不合法三角形的个数再用C n 3减去,但注意一个不合法三角形有两个这样的点,也就是会被减去两次,所以ms需要除2

代码如下:

bool edge[8005][8005];
int cnt0[N],cnt1[N];
void solve(){
	int seed; cin >> n >> seed;
	srand(seed);
	for (int i = 1; i <= n; i++)
		for (int j = i + 1; j <= n; j++){
			edge[j][i] = edge[i][j] = read();
			if(edge[i][j]) cnt0[i]++,cnt0[j]++;
			else cnt1[i]++,cnt1[j]++;
		}
	ll ans=n*(n-1)*(n-2)/6,ms=0;
	for(int i=1;i<=n;i++) ms+=1ll*cnt0[i]*cnt1[i];
	cout << ans-ms/2;
}

补 24百度之星决赛 豆腐店                           mtj传送门

那道树上差分,毁了我的国三梦

思路:很典的树上差分(但当时不会,这里是对边差分,和对点有一丢区别,修路用优先队列跑就行,因为每次都是除2,复杂度最多 n*logw

代码如下:

const int N=3e5+10,M=1e4+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
vector<PII>ve[N];
int fa[N][20],dep[N],cf[N];
void dfs(int x,int f){
	dep[x]=dep[f]+1;
	fa[x][0]=f;
	for(int j=1;j<20;j++) fa[x][j]=fa[fa[x][j-1]][j-1];
	for(auto [w,v]:ve[x]) if(v!=f) dfs(v,x);
}
int lca(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=19;~i;i--)
		if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
	if(u==v) return v;
	for(int i=19;~i;i--)
		if(fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}
struct nod{
	ll w,t;
	bool operator <(const nod &b)const{
		return (w-w/2)*t<(b.w-b.w/2)*b.t;
	}
};
priority_queue<nod>pq;
void dfs2(int x,int f){
	for(auto [w,v]:ve[x]) if(v!=f){
		dfs2(v,x);
		pq.push({w,cf[v]});
		cf[x]+=cf[v];
	}
}
void solve(){
	ll m,k; cin >> n >> m >> k;
	for(int i=1;i<n;i++){
		ll u,v,w; cin >> u >> v >> w;
		ve[u].push_back({w,v});
		ve[v].push_back({w,u});
	}
	dfs(1,0);
	int lst=1;
	while(m--){
		int a; cin >> a;
		int an=lca(lst,a);
//		cout << lst << " " << a << " " << an << "\n";
		cf[lst]++,cf[a]++,cf[an]-=2;
		lst=a;
	}
	dfs2(1,0);
	while(k-- && !pq.empty()){
		auto [w,t]=pq.top(); pq.pop();
		w/=2;
		if(w) pq.push({w,t});
	}
	ll ans=0;
	while(!pq.empty()){
		auto [w,t]=pq.top(); pq.pop();
		ans+=w*t;
	}
	cout << ans << "\n";
}

cf edu round2 E 线段树合并                           cf传送门

线段树合并的复杂度:对于插入m个点,时间复杂度与空间复杂度都为 mlogm

思路:可称得上是线段树合并板子题,对每个点建一颗线段树,dfs时把所有子节点合并到父节点上,再插入父节点颜色,即可查询答案

因为ans没开ll又wa了两发,真是无语了

代码如下:

const int N=2e5+10,M=1e4+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
struct seg_Tree{
#define lc(p) t[p].ch[0]
#define rc(p) t[p].ch[1]
	struct nod{
		int ch[2];
		ll sum,an;
	}t[N*22];
	int tot,root[N];
	void pushup(int p){
		if(t[lc(p)].sum>t[rc(p)].sum)
			t[p].sum=t[lc(p)].sum,t[p].an=t[lc(p)].an;
		else if(t[lc(p)].sum<t[rc(p)].sum)
			t[p].sum=t[rc(p)].sum,t[p].an=t[rc(p)].an;
		else t[p].sum=t[lc(p)].sum,t[p].an=t[lc(p)].an+t[rc(p)].an;
	}
	int merge(int x,int y,int l,int r){
		if(!x || !y) return x+y;
		if(l==r){
			t[y].sum+=t[x].sum;
			t[y].an=l;
			return y;
		}
		int mid=l+r>>1;
		lc(y)=merge(lc(x),lc(y),l,mid);
		rc(y)=merge(rc(x),rc(y),mid+1,r);
		pushup(y);
		return y;
	}
	void update(int &y,int l,int r,int v){
		if(!y) y=++tot;
//		t[y].sum++;     //不是主席树写法
		if(l==r){
			t[y].sum++;
			t[y].an=l;
			return ;
		}
		int mid=l+r>>1;
		if(v<=mid) update(lc(y),l,mid,v);
		if(v>mid) update(rc(y),mid+1,r,v);
		pushup(y);
	}
}tr;
vector<int>ve[N];
ll c[N],ans[N];            怎么又没开ll啊啊啊啊啊啊
void dfs(int x,int f){
	for(int v:ve[x]) if(v!=f){
		dfs(v,x);
		tr.root[x]=tr.merge(tr.root[v],tr.root[x],1,n);  //子节点直接和父节点合并
	}
	tr.update(tr.root[x],1,n,c[x]);  //插入父节点新值
	ans[x]=tr.t[tr.root[x]].an;
}
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++) cin >> c[i];
	for(int i=1;i<n;i++){
		int u,v; cin >> u >> v;
		ve[u].push_back(v);
		ve[v].push_back(u);
	}
	dfs(1,0);
	for(int i=1;i<=n;i++) cout << ans[i] << " \n"[i==n];
}

星期三:

国庆长假第二天😸😸😸

学而时习之,不亦乐乎                                    cf传送门

这题最少需开3e5*152个节点,但复杂度应是q*2logk,就是3e5*121才对,为什么至少得开3e5*152呢???

复杂度算错了,区间修改每次最多开两条链也就是2*logv,再算上pushdn,每次最多开近4条链,也就是q*4*logv的复杂度,这样算来最多需3e5*240个节点,那120过不了也合理了

洛谷 P4556 线段树合并板子题                   洛谷传送门

思路:树上点差分+线段树合并的板子

注意差分过程的时间/空间复杂度为 m*logMAXN*4,因为一次差分要动4个点

dfs2处理差分的函数,内部的dfs2给写成dfs了,真的是耽误了好久。。可能是没睡够脑子不太清醒

代码如下:

const int N=3e5+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=998244353;
ll n;
const int MAXN=1e5+10;
struct seg_Tree{
#define lc(p) t[p].ch[0]
#define rc(p) t[p].ch[1]
	struct nod{
		int ch[2];
		ll an,sum;
	}t[N*22];
	int tot,root[N];
	void pushup(int p){
		if(t[lc(p)].sum>=t[rc(p)].sum) t[p].sum=t[lc(p)].sum,t[p].an=t[lc(p)].an;
		else t[p].sum=t[rc(p)].sum,t[p].an=t[rc(p)].an;
	}
	int merge(int x,int y,int l,int r){
		if(!x || !y) return x+y;
		if(l==r){
			t[y].sum+=t[x].sum;
			return y;
		}
		int mid=l+r>>1;
		lc(y)=merge(lc(x),lc(y),l,mid);
		rc(y)=merge(rc(x),rc(y),mid+1,r);
		pushup(y);
		return y;
	}
	void update(int &y,int l,int r,int z,int v){
		if(!y) y=++tot;
		if(l==r){
			t[y].sum+=v;
			t[y].an=z;
			return ;
		}
		int mid=l+r>>1;
		if(z<=mid) update(lc(y),l,mid,z,v);
		if(z>mid) update(rc(y),mid+1,r,z,v);
		pushup(y);
	}
}tr;
vector<int>ve[N];
int fa[N][20],dep[N];
void dfs(int x,int f){
	dep[x]=dep[f]+1;
	fa[x][0]=f;
	for(int j=1;j<20;j++) fa[x][j]=fa[fa[x][j-1]][j-1];
	for(int v:ve[x]) if(v!=f) dfs(v,x);
}
int lca(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	for(int j=19;~j;j--)
		if(dep[fa[u][j]]>=dep[v]) u=fa[u][j];
	if(u==v) return v;
	for(int j=19;~j;j--)
		if(fa[u][j]!=fa[v][j]) u=fa[u][j],v=fa[v][j];
	return fa[u][0];
}
ll ans[N];
void dfs2(int x,int f){
	for(int v:ve[x]) if(v!=f){
		dfs2(v,x);            //一开始写成dfs(v,x),de了半天啊啊啊啊啊
		tr.root[x]=tr.merge(tr.root[v],tr.root[x],1,MAXN);
	}
	ans[x]=tr.t[tr.root[x]].sum>0?tr.t[tr.root[x]].an:0;
}
void solve(){
	int m; cin >> n >> m;
	for(int i=1;i<n;i++){
		int u,v; cin >> u >> v;
		ve[u].push_back(v);
		ve[v].push_back(u);
	}
	dfs(1,0);
	while(m--){
		int x,y,z; cin >> x >> y >> z;
		int an=lca(x,y);
		tr.update(tr.root[x],1,MAXN,z,1);
		tr.update(tr.root[y],1,MAXN,z,1);
		tr.update(tr.root[an],1,MAXN,z,-1);
		tr.update(tr.root[fa[an][0]],1,MAXN,z,-1);
	}
	dfs2(1,0);
	for(int i=1;i<=n;i++) cout << ans[i] << "\n";
}

23年秦皇岛的 I怎么这么难补,学完线段树合并又要去看分裂,我要成为数据结构领域大神!!!

洛谷 P5494                                                 洛谷传送门

思路:线段树分裂板子题

注意函数是将前k个之后的数分裂出去,不是将前k个数分裂出去

代码如下:

const int N=3e5+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=998244353;
ll n;
struct seg_Tree{
#define lc(p) t[p].ch[0]
#define rc(p) t[p].ch[1]
	struct nod{
		int ch[2];
		ll sum;
	}t[N*22];
	int tot,root[N];
	void pushup(int p){
		t[p].sum=t[lc(p)].sum+t[rc(p)].sum;
	}
	int merge(int x,int y,int l,int r){
		if(!x || !y) return x+y;
		if(l==r){
			t[y].sum+=t[x].sum;
			return y;
		}
		int mid=l+r>>1;
		lc(y)=merge(lc(x),lc(y),l,mid);
		rc(y)=merge(rc(x),rc(y),mid+1,r);
		pushup(y);
		return y;
	}
	void split(int x,int &y,ll k){    //将x的前k个数之后的数分裂给y
		if(!x) return ;
		y=++tot;
		ll s=t[lc(x)].sum;
		if(k<=s) split(lc(x),lc(y),k),swap(rc(x),rc(y));
		else split(rc(x),rc(y),k-s);
		t[y].sum=t[x].sum-k,t[x].sum=k;
	}
	void update(int &p,int l,int r,int v,int k){
		if(!p) p=++tot;
		if(l==r){
			t[p].sum+=k;
			return ;
		}
		int mid=l+r>>1;
		if(v<=mid) update(lc(p),l,mid,v,k);
		else update(rc(p),mid+1,r,v,k);
		pushup(p);
	}
	ll ask(int p,int l,int r,int ql,int qr){
		if(!p) return 0;
		if(ql<=l && qr>=r) return t[p].sum;
		int mid=l+r>>1;
		ll res=0;
		if(ql<=mid) res+=ask(lc(p),l,mid,ql,qr);
		if(qr>mid) res+=ask(rc(p),mid+1,r,ql,qr);
		return res;
	}
	int fnd(int p,int l,int r,int k){
		if(l==r) return l;
		ll s=t[lc(p)].sum;
		if(s+t[rc(p)].sum<k) return -1;
		int mid=l+r>>1;
		if(k<=s) return fnd(lc(p),l,mid,k);
		else return fnd(rc(p),mid+1,r,k-s);
	}
}tr;
void solve(){
	int m; cin >> n >> m;
	int cnt=1;
	for(int i=1;i<=n;i++){
		int k; cin >> k;
		tr.update(tr.root[1],1,n,i,k);
	}
	while(m--){
		int op,p; cin >> op >> p;
		if(!op){
			int x,y; cin >> x >> y;
			ll sum1=tr.ask(tr.root[p],1,n,1,x-1);
			ll sum2=tr.ask(tr.root[p],1,n,x,y);
			tr.split(tr.root[p],tr.root[++cnt],sum1);
			int tmp=0;
			tr.split(tr.root[cnt],tmp,sum2);         //cnt只保留前sum2个,剩余的还回去
			tr.root[p]=tr.merge(tmp,tr.root[p],1,n);
		}else if(op==1){
			int t; cin >> t;
			tr.root[p]=tr.merge(tr.root[t],tr.root[p],1,n);
		}else if(op==2){
			int x,q; cin >> x >> q;
			tr.update(tr.root[p],1,n,q,x);
		}else if(op==3){
			int x,y; cin >> x >> y;
			cout << tr.ask(tr.root[p],1,n,x,y) << "\n";
		}else{
			int k; cin >> k;
			cout << tr.fnd(tr.root[p],1,n,k) << "\n";
		}
	}
}

cf round312 div2 E                                         cf传送门

思路:嗯开26棵线段树,给每个字母都开一棵,对于叶子节点意义是 s【l】是否是此字母

需要支持区修区查,这里用的动态开点

代码如下:

const int N=1e5+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=998244353;
ll n;
struct seg_Tree{
#define lc(p) t[p].ch[0]
#define rc(p) t[p].ch[1]
	struct nod{
		int ch[2];
		int sum,tag;
	}t[N*4*26];
	int tot,root[27];
	void pushup(int p){
		t[p].sum=t[lc(p)].sum+t[rc(p)].sum;
		t[p].tag=0;
	}
	void pushdn(int p,int l,int r){
		if(!t[p].tag) return ;
		if(!lc(p)) lc(p)=++tot;
		if(!rc(p)) rc(p)=++tot;
		int mid=l+r>>1;
		if(t[p].tag==1){
			t[lc(p)].sum=mid-l+1;
			t[lc(p)].tag=1;
			t[rc(p)].sum=r-mid;
			t[rc(p)].tag=1;
		}else{
			t[lc(p)].sum=0;
			t[lc(p)].tag=-1;
			t[rc(p)].sum=0;
			t[rc(p)].tag=-1;
		}
		t[p].tag=0;
	}
	void update(int &p,int l,int r,int ql,int qr,int qop){
		if(!p) p=++tot;
		if(ql<=l && qr>=r){
			if(qop==1) t[p].sum=r-l+1,t[p].tag=1;
			else t[p].sum=0,t[p].tag=-1;
			return ;
		}
		int mid=l+r>>1;
		pushdn(p,l,r);
		if(ql<=mid) update(lc(p),l,mid,ql,qr,qop);
		if(qr>mid) update(rc(p),mid+1,r,ql,qr,qop);
		pushup(p);
	}
	int ask(int p,int l,int r,int ql,int qr){
		if(!p) return 0;
		if(ql<=l && qr>=r) return t[p].sum;
		int mid=l+r>>1;
		int res=0;
		pushdn(p,l,r);
		if(ql<=mid) res+=ask(lc(p),l,mid,ql,qr);
		if(qr>mid) res+=ask(rc(p),mid+1,r,ql,qr);
		return res;
	}
}tr;
void solve(){
	int q; cin >> n >> q;
	string s; cin >> s;s=" "+s;
	for(int i=1;i<=n;i++){
		int j=s[i]-'a'+1;
		tr.update(tr.root[j],1,n,i,i,1);
	}
	while(q--){
		int l,r,op; cin >> l >> r >> op;
		int c[27]={0};          //统计区间内26个字母的数量
		for(int i=1;i<=26;i++){
			c[i]=tr.ask(tr.root[i],1,n,l,r);
			tr.update(tr.root[i],1,n,l,r,-1);
		}
		int from=l;
		for(int i=op?1:26;op?i<=26:i;op?i++:i--) if(c[i]){
			int to=from+c[i]-1;
			tr.update(tr.root[i],1,n,from,to,1);   //升序或降序放
			from=to+1;
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=26;j++)
			if(tr.ask(tr.root[j],1,n,i,i)==1){cout << char('a'+j-1); break;}
	}
}

星期四:

国庆长假第三天🤨 转眼已过半

23 CCPC秦皇岛 I,目前补不动😭,以后再看

23 ICPC济南 队友出的A                                  cf传送门

思路:刚开始以为只能有一个嵌套,如oxo xx oxo形式(ox代表方圆类型,有点莽撞地交了发wa2,确实是有点莽了,只要稍稍再想想,很容易想到反例,如ooxx,oxxoxx。

实际上是最多俩嵌套,而且俩嵌套最外括号得不同,若最外括号相同,如([()]) (),可变为([()] () ),一眼假。若存在仨嵌套,那么由前面可知,第一坨和第三坨最外括号一定相同,还是一眼假

代码如下:

const int N=1e5+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=998244353;
ll n;

void solve(){
	string s; cin >> s;
	n=s.size(); s=" "+s;
	for(int i=1;i<=n;i++){
		if(s[i]=='(' || s[i]==')') s[i]='o';
		else s[i]='x';
	}
	int cnt=0;
	for(int i=1;i<n;i++) cnt+=(s[i]==s[i+1]);
	if(cnt>2) cout << "No\n";
	else cout << "Yes\n";
}

星期五:

和队友打了24牛客国庆集训4,被扫描线拿下了,赶紧学了补

补 23 ICPC济南 G                                            cf传送门

据说这种两两冲突求方案数是典题,好好体会下。

思路:虽说我用的并查集,但这种题还是转成图论比较好,把每行分为 i表示翻转和 i+n不翻转,将有冲突的选择连边,一条边的两点选其一,跑个二分图染色。

先将 i和 i+n连边,然后遍历 i和 c-i+1列,若sum1>2,则无解(注意若奇数列特判中间那列),  遍历列和其镜像列,若1的个数<2,则每行翻不翻无所谓。若==2,若俩1在同一列,则两行翻转状态必须相反,否则必须相同。连完边跑个二分图染色,若没有任何翻转的限制答案为 2^r,建图后每个连通块的选项绑定在一起,答案为 2^连通块个数

代码如下:

const int N=2e6+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
string b[N];
int fa[N];
int fnd(int x){
	return fa[x]==x?x:fa[x]=fnd(fa[x]);
}
void merge(int u,int v){
	u=fnd(u),v=fnd(v);
	if(u!=v) fa[u]=v;
}
ll qpow(ll a,ll n){
	ll res=1;
	while(n){
		if(n&1) (res*=a)%=mod;
		(a*=a)%=mod;
		n>>=1;
	}
	return res;
}
void solve(){
	ll c; cin >> n >> c;
	for(int i=1;i<=2*n;i++) fa[i]=i;
	for(int i=1;i<=n;i++) cin >> b[i],b[i]=" "+b[i];
	if(c&1){
		int mid=0;
		for(int i=1;i<=n;i++) mid+=(b[i][c/2+1]=='1');
		if(mid>1){cout << "0\n"; return ;}
	}
	for(int l=1,r=c;l<r;l++,r--){
		int sum=0;
		for(int i=1;i<=n;i++) sum+=(b[i][l]=='1')+(b[i][r]=='1');
		if(sum>2){cout << "0\n"; return ;}
	}
	for(int l=1,r=c;l<r;l++,r--){
		vector<int>vel,ver;
		for(int i=1;i<=n;i++){
			if(b[i][l]=='1') vel.push_back(i);
			if(b[i][r]=='1') ver.push_back(i);
		}
		if(vel.size()==2) merge(vel[0],vel[1]+n),merge(vel[0]+n,vel[1]);
		else if(ver.size()==2) merge(ver[0],ver[1]+n),merge(ver[0]+n,ver[1]);
		else if(vel.size()==1 && ver.size()==1)
            merge(vel[0],ver[0]),merge(vel[0]+n,ver[0]+n);
	}
	for(int i=1;i<=n;i++) if(fnd(i)==fnd(i+n)){cout << "0\n"; return ;}
	int cnt=0;
	for(int i=1;i<=n;i++) cnt+=(fnd(i)==i);
	cout << qpow(2,cnt) << "\n";
}

星期六:

补 cf round806 div4 G                                     cf传送门

思路:比较典的线性dp,关键在于除2这个操作,见到这个字眼应该格外警惕,因为除2很快,任何值的log2都很小,所以1e9的值拿30次bad key后就会变成0,由此可以对每个 i枚举拿bk的次数

dp【i】【j】表示考虑到第 i个箱子用了 j次bk的最大值,注意这题需要时刻取max

赛时很快猜到了个结论,即如用bk那么存在一个idx,在<=idx时都用gk,在idx后至n都用bk,可惜当时以为是三分,后来发现用bk的时机并非二次函数。实际上有了这个结论后可以直接暴力枚举时机,而且最多往后枚举30个,可惜当时还是没有想到log21e9在30以内,误以为是 n^2写法 : <

代码如下:

const int N=2e5+10,M=4e3+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
inline ll rng(ll l,ll r){return rnd()%(r-l+1)+l;}
ll n;
ll a[N],k;
ll dp[N][33];
void solve(){
	cin >> n >> k;
	for(int i=1;i<=n;i++) cin >> a[i];
	ll ans=0;
	for(int i=1;i<=n;i++){
		ll ta=a[i];
		for(int j=0;j<=min(i,31);j++){
			if(!j) dp[i][j]=dp[i-1][j]+ta-k;
			else dp[i][j]=max(dp[i-1][j]+ta-k,dp[i-1][j-1]+ta);
			ta>>=1;
			ans=max(dp[i][j],ans);
		}
	}
	cout << ans << "\n";
}

补 cf round926 E                                           cf传送门

思路:一眼树上差分+状压,但是对于状压有些许问题。

最开始是边处理差分边跑状压,只跑出现过的有限的状态,但这样有时会达不到答案,也想过先把边提出来再跑一个状压,但由于不清楚不同状态的边的数量,感觉会T,没敢跑(现在想来,赛时那么多wa2,就应该换个写法莽一发),赛后发现实际上不同状态的边的数量是 k级别的,这样就可以很轻松跑个状压,但是对于为何这么少还没想明白,说是虚树,现在不是很想看

代码如下:

const int N=1e5+10,M=1.1e6+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
inline ll rng(ll l,ll r){return rnd()%(r-l+1)+l;}
ll n;
vector<int>ve[N];
int dep[N],fa[N][20];
int cf[N];
set<int>ma;
int dp[M];
void dfs(int x,int f){
	dep[x]=dep[f]+1;
	fa[x][0]=f;
	for(int j=1;j<20;j++) fa[x][j]=fa[fa[x][j-1]][j-1];
	for(int v:ve[x]) if(v!=f) dfs(v,x);
}
int lca(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	for(int j=19;~j;j--)
		if(dep[fa[u][j]]>=dep[v]) u=fa[u][j];
	if(u==v) return v;
	for(int j=19;~j;j--)
		if(fa[u][j]!=fa[v][j]) u=fa[u][j],v=fa[v][j];
	return fa[u][0];
}
void dfs2(int x,int f){
	for(int v:ve[x]) if(v!=f){
		dfs2(v,x);
		cf[x]+=cf[v];
	}
	ma.insert(cf[x]);
}
void solve(){
	cin >> n;
	ma.clear();
	for(int i=1;i<=n;i++){
		ve[i].clear();
		cf[i]=0;
	}
	int rt=0;
	for(int i=1;i<n;i++){
		int u,v; cin >> u >> v;
		ve[u].push_back(v);
		ve[v].push_back(u);
	}
	for(int i=1;i<=n;i++) if(ve[i].size()==1){rt=i; break;}
	dfs(rt,0);
	int k; cin >> k;
	for(int i=1;i<1<<k;i++) dp[i]=1e9;
	for(int i=0;i<k;i++){
		int a,b; cin >> a >> b;
		int an=lca(a,b);
		cf[a]+=1<<i,cf[b]+=1<<i;
		cf[an]-=(1<<i+1);
	}
	dfs2(rt,0);
	for(int mask=0;mask<1<<k;mask++){
		for(int m:ma){
			int nmask=mask|m;
			dp[nmask]=min(dp[mask]+1,dp[nmask]);
		}
	}
	cout << dp[(1<<k)-1] << "\n";
}

周日:

下午vp 21 CCPC威海,中途想双线程开div2,被A吓跑了

补cf edu round164 div2 D                                cf传送门

参考题解:D. Colored Balls - onlyblues - 博客园 (cnblogs.com)

思路:先考虑如何计算每个颜色集的对答案贡献,假设颜色集总球数为sum,若球数最多的颜色(记max)不严格大于sum/2,则答案为 sum/2向上取整,否则为max。

将 a从小到大排序,dp【i】【j】表示考虑到第 i个拿了 j个球的方案数(不是必拿 i

dp的转移很好写,考虑答案怎么计算

分两种情况:( i从1到n遍历,因为已经排了序,此时 ai即最大球数,此时讨论的是dp【i-1】【j】

1.若 ai > j,即 ai > (j+ai)/2,则贡献为 ai,方案数为 dp【i-1】【j】

2.若 ai <= j,则贡献为 (ai+j)/2向上取整,方案数同上

代码如下:

const int N=5e5+10,M=1.1e6+10;
const int INF=0x3f3f3f3f;
const int mod=998244353;
inline ll rng(ll l,ll r){return rnd()%(r-l+1)+l;}
ll n;
ll a[5050];
ll dp[5050][5050];
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++) cin >> a[i];
	sort(a+1,a+n+1);
	ll ans=0;
	dp[0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=5000;j++){
			if(j>=a[i]) (ans+=(j+a[i]+1)/2*dp[i-1][j])%=mod;
			else (ans+=dp[i-1][j]*a[i])%=mod;
			dp[i][j]=dp[i-1][j];
			if(j>=a[i]) (dp[i][j]+=dp[i-1][j-a[i]])%=mod;
		}
	}
	cout << ans;
}

星期一:

洛谷P5490 扫描线                                          洛谷传送门

思路:纯板子,看的董晓算法

有很多细节需要注意,主要是计算节点长度时,节点右端点应向右偏移一位,因为若不这么做,那么叶子节点的l==r,难道叶子节点代表的长度即x【r】-x【l】即为0吗?显然不能这样,所以需要偏移。离散量共有s个,因为偏移了,建树时也只需建1 - s-1即可。且更新时也要注意,因为计算时向右偏了一位,所以传参时 r要-1

代码如下:

const int N=2e5+10,M=1.1e6+10;
const int INF=0x3f3f3f3f;
const int mod=998244353;
inline ll rng(ll l,ll r){return rnd()%(r-l+1)+l;}
ll n;
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
	struct line{    //扫描线
		ll x1,x2,y;
		int tag;
		bool operator <(const line &b)const{return y<b.y;}
	}l[N];
	struct nod{
		int l,r;
		ll cnt,len;
	}t[N<<3];
	ll x[N],ql,qr,qv;
	void pushup(int p){
		int l=t[p].l,r=t[p].r;
		if(t[p].cnt) t[p].len=x[r+1]-x[l];
		else t[p].len=t[lc].len+t[rc].len;
	}
	void bd(int p,int l,int r){
		t[p]={l,r,0,0};
		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].cnt+=qv;
			pushup(p);
			return ;
		}
		int mid=t[p].l+t[p].r>>1;
		if(ql<=mid) update(lc);
		if(qr>mid) update(rc);
		pushup(p);
	}
	void upd(int l,int r,int v){
		ql=l,qr=r;
		qv=v;
		update(1);
	}
}tr;
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		int x1,y1,x2,y2; cin >> x1 >> y1 >> x2 >> y2;
		tr.l[i]={x1,x2,y1,1};
		tr.l[i+n]={x1,x2,y2,-1};
		tr.x[i]=x1,tr.x[i+n]=x2;
	}
	n<<=1;
	sort(tr.l+1,tr.l+n+1);
	sort(tr.x+1,tr.x+n+1);
	ll s=unique(tr.x+1,tr.x+n+1)-tr.x-1;
	tr.bd(1,1,s-1);   //注意-1
	ll ans=0;
	for(int i=1;i<n;i++){
		int l=lower_bound(tr.x+1,tr.x+s+1,tr.l[i].x1)-tr.x;
		int r=lower_bound(tr.x+1,tr.x+s+1,tr.l[i].x2)-tr.x;
		tr.upd(l,r-1,tr.l[i].tag);            //注意-1
		ans+=tr.t[1].len*(tr.l[i+1].y-tr.l[i].y);
	}
	cout << ans;
}

在这放个很顶的哈希方法,参考自:一种比较科学的字符串哈希实现方法 - 知乎 (zhihu.com)

洛谷U461211                                              洛谷传送门

代码如下:

const int N=2e6+10,M=3e3+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
inline ll rng(ll l,ll r){return rnd()%(r-l+1)+l;}
ll n;
const int base=131;
const ull MOD=(1ull<<61)-1;
inline ull add(ull a,ull b){
	a+=b;
	if(a>=MOD) a-=MOD;
	return a;
}
inline ull mul(ull a,ull b){
	__uint128_t c=__uint128_t(a)*b;
	return add(c>>61,c&MOD);
}
void solve(){
	cin >> n;
	set<ull>st;
	while(n--){
		string s; cin >> s;
		ull hsh=0;
		for(auto i:s) hsh=(add(mul(hsh,base),i));
		st.insert(hsh);
	}
	cout << st.size();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2196245.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从学习Java到学习AI大模型,我为什么选择的后者???

我为什么从Java转到AI大模型 在编程的海洋里&#xff0c;Java一直是我信赖的“小船”&#xff0c;载着我航行在代码的世界中。然而&#xff0c;随着行业的不断发展和变化&#xff0c;我开始感受到了一丝的迷茫和不安。我开始担心&#xff0c;随着技术的不断更新&#xff0c;Ja…

银河麒麟桌面操作系统V10 SP1:取消安装应用的安全授权认证

银河麒麟桌面操作系统V10 SP1&#xff1a;取消安装应用的安全授权认证 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 使用银河麒麟V10 SP1安装应用时&#xff0c;若频繁遇到安全授权认证提示&#xff0c;可按以下步骤设置&#xff1a; 打开…

操作系统 | 学习笔记 | 王道 | 4.3 文件系统

4.3 文件系统 4.3.1 文件系统结构 文件系统(File system)提供高效和便捷的磁盘访问&#xff0c;以便允许存储、定位、提取数据。 用一个例子来辅助记忆文件系统的层次结构&#xff1a; 假设某用户请求删除文件"D:/工作目录/学生信息.xIsx"的最后100条记录。 用户需…

Linux——磁盘分区、挂载

Linux 分区 原理介绍 原理图如下 当我们在/home目录下新建一个文件a.txt时&#xff0c;该文件实际上是存放在硬盘B的分区1中的&#xff0c;这就是图里说的&#xff0c;当进入某个目录&#xff0c;可以进入到该目录下挂载的分区里的意思 硬盘说明 应用实例&#xff1a;挂载一个…

镁稀土中间合金的耐腐蚀性

镁稀土中间合金&#xff0c;也称作镁稀土合金&#xff0c;是一种重要的合金材料&#xff0c;由镁、稀土元素(如镧、铈、镨、钕、钷、钐、铕、钆、铽、镝、钬、铒、镱、镥等)以及其他可能的金属元素(如铝、锶、锆、钙等)组成。以下是对镁稀土中间合金的详细介绍&#xff1a; 一、…

六、索引的数据结构

文章目录 1. 为什么使用索引2. 索引及其优缺点2.1 索引概述2.2 优点2.3 缺点3. InnoDB中索引的推演3.1 索引之前的查找3.1.1 在一个页中的查找3.1.2 在很多页中查找3.2 设计索引3.2.1 一个简单的索引设计方案3.2.2 InnoDB中的索引方案3.3 常见索引概念3.3.1 聚簇索引3.3.2 二级…

CPU Study - Recovery when Prediction Fails

参考来源&#xff1a;《超标量处理器设计》—— 姚永斌 分支预测失败时&#xff0c;这条分支指令之后的所有指令都处在了错误的路径上&#xff08;mis-prediction&#xff09;。 这些指令都会被抹除掉 &#xff0c;从而造成很多bubble&#xff0c;降低处理器性能&#xff0c;称…

数学概念算法-打印100以内的素/质数

素数&#xff1a;只能被1和自己整除的数 暴力破解 埃氏筛选 找到第一个数字&#xff0c;如果它是素数&#xff0c;则把它的倍数全部划掉 比如数字2是素数&#xff0c;那么 4,6,8,10,12。这些数字肯定不是素数&#xff0c;所以不用再考虑&#xff0c;直接划掉即可 第二步&#…

ROS理论与实践学习笔记——3 ROS运行管理之ROS话题名称设置

名称重映射是为名称起别名&#xff0c;为名称添加前缀&#xff0c;该实现比节点重名更复杂些&#xff0c;不单是使用命名空间作为前缀、还可以使用节点名称最为前缀。两种策略的实现途径有多种: &#xff08;1&#xff09;rosrun 命令 &#xff08;2&#xff09;launch 文件 …

camody卡魔迪-准备新一年双十一推出iPhone快充充电宝

随着双十一购物节的临近&#xff0c;各大品牌纷纷推出创新产品以吸引消费者的目光。近日&#xff0c;知名科技品牌Camody卡魔迪宣布将在今年双十一期间推出一款全新的iPhone快充充电宝。这款充电宝不仅具备快速充电功能&#xff0c;还兼具时尚设计和便携性&#xff0c;预计将成…

ArcGIS中分区统计栅格值前需要进行投影吗(在投影坐标系下进行吗),为什么?

最近&#xff0c;我接到了一个分区统计栅格数值前需要进行投影&#xff0c;或者说是必须需要在投影坐标系下进行吗的咨询。 答案是不需要刻意去变。 但是他又说他把地理坐标系下分区统计结果与投影坐标系下的分区统计结果分别做了一遍&#xff0c;并进行了对比&#xff0c;两个…

通过docker安装thingsboard需要的postgresql数据库

1、下载docker的包 docker run --name postgresql -e POSTGRES_PASSWORDXXX123 -d registry.openanolis.cn/openanolis/postgres:10.21-8.6 进入postgresql&#xff0c;创建数据库 [rootlocalhost ~]# docker exec -it postgresql bash [root0940f42b2263 /]# su - postgre…

9.10Mean-Shift分割算法

基本概念 Mean-Shift 分割算法是一种非参数的特征空间点集的迭代查找算法&#xff0c;主要用于估计概率密度函数的模式。在计算机视觉中&#xff0c;它常用于颜色图像分割和目标跟踪。它通过迭代地移动每个数据点到其邻域内密度最大的地方&#xff0c;从而找到数据点的模式&am…

基于单片机的非接触智能测温系统设计

本设计主要由单片机STC8A8K64S4A12、OLED显示屏、非接触式测温模块MLX9061、无线通讯模块ESP8266以及声光报警模块等构成。系统通过非接触式测温模块MLX9061测量当前人员温度&#xff0c;温度通过OLED显示屏进行实时显示&#xff0c;当被测温度高于阈值&#xff0c;声光报警模块…

力扣16~20题

题16&#xff08;中等&#xff09;&#xff1a; 思路&#xff1a; 双指针法&#xff0c;和15题差不多&#xff0c;就是要排除了&#xff0c;如果total<target则排除了更小的&#xff08;left右移&#xff09;&#xff0c;如果total>target则排除了更大的&#xff08;rig…

java语言基础案例-cnblog

java语言基础案例 象棋口诀 输出 package nb;public class XiangQi {public static void main(String[] args) {char a 马;char b 象;char c 卒;System.out.println(a"走日"b"走田""小"c"一去不复还");} }输出汇款单 package nb…

在java中使用redis

Redis Java使⽤ 引入依赖 Java 操作redis的客⼾端有很多.其中最知名的是jedis. 创建maven项⽬,把jedis的依赖拷⻉到pom.xml中 <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency><groupId>redis.clients</groupId><…

机房空调远程控制-Thingsboard MQTT 接口说明

一、 概述 为实现节能环保需求&#xff0c;拓扑未来公司推出轻量级&#xff0c;使用简单的远程空调控制解决方案。该方案集成了现场环境温湿度采集功能&#xff0c;设备管理功能&#xff0c;远程控制功能。支持本地自动控制模式和远程控制模式&#xff0c;自动模式下&#xff…

Chromium 如何查找已经定义好的mojom函数实现c++

进程通信定义通常都是用.mojom文件或者idl文件格式 以content\common\frame.mojom里面的BeginNavigation函数为例。 一、如何查找BeginNavigation函数定义&#xff0c;在vscode里面直接搜索BeginNavigation&#xff0c;过滤条件 *.idl,*.mojom,*.cc 效果&#xff1a; 这样…

YoloV9改进策略:BackBone改进|CAFormer在YoloV9中的创新应用,显著提升目标检测性能

摘要 在目标检测领域,模型性能的提升一直是研究者和开发者们关注的重点。近期,我们尝试将CAFormer模块引入YoloV9模型中,以替换其原有的主干网络,这一创新性的改进带来了显著的性能提升。 CAFormer,作为MetaFormer框架下的一个变体,结合了深度可分离卷积和普通自注意力…