神秘做题记录3(含追忆)

P11831

rerererererecall

题解

发现图的形态一点都不会变,所以可达性很好做,直接用bitset做一个传递闭包。
然后考虑加上A的限制,你需要维护一个新的bitset同时满足可达性和A的区间。
区间?可以分块了,每块开bitset,散点暴力即可,然后可以修改一下意义,块维护后缀块的和,然后查询l,r+1异或起来。
这样你就再一个合理的时间内得到了合法的点,但是这些合法的点B最大值为多少呢?设想一下可以二分,做一个后缀和,但是无法修改。
那就对B也分块,维护根号个bitset,和A一模一样的一点区别都没有,然后也不用二分了直接扫做完了。
一个细节,你怎么扫呢?可以先枚举bitset的位,但是不是bool位,是unsigned long long位,这就相当于每一个点更新一下,最后就是n/w+sqrt(n)的,否则两者要乘起来,过不了。
而且你要访问ull,就得手写bitset,好在写这个比写树状数组还简单,随便做。
多测记得清空。

代码

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int N = 100010,M = 1570,len = 320;
int C,T;
int n,m,q,cnt;
int a[N],b[N];
int ia[N],ib[N];//a,b的反映射 
int ans;
vector<int> e[N];
struct bitset_{//reset set flip
	ull num[M];
	void reset(){
		memset(num,0,sizeof(num));
	}
	void set(int p){
		num[p>>6]|=(1ull<<(p&63));
	}
	void flip(int p){
		num[p>>6]^=(1ull<<(p&63)); 
	}
	void operator &=(const bitset_ &tmp){
		for(int i = 0;i<M;i++)num[i]&=tmp.num[i];
	}
	void operator |=(const bitset_ &tmp){
		for(int i = 0;i<M;i++)num[i]|=tmp.num[i];
	}
	void operator ^=(const bitset_ &tmp){
		for(int i = 0;i<M;i++)num[i]^=tmp.num[i];
	}
	int get(int p){
		return (1ull&(num[p>>6]>>(p&63)));
	}
};
bitset_ g[N];//传递闭包
bitset_ bl_a[len+10];//对A_i值域进行分块,大于等于idx*len的i,存到bitset里 
bitset_ bl_b[len+10];//B_i也分块,可行性bitset和b值域的bitset与一下,sqrt(n)查询 
bitset_ c,res1,res2;
void solve(){
	cin>>n>>m>>q;
	for(int i = 1;i<=n;i++)e[i].clear(); 
	for(int i = 1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
	}
	for(int i = n;i>=1;i--){
		g[i].reset();
		g[i].set(i);
		for(int j = 0;j<e[i].size();j++){
			g[i]|=g[e[i][j]];
		}
	}
	cnt = 0;
	while(cnt*len<=n)cnt++;
	for(int i = 0;i<=cnt;i++){//根据分块的意义下标从0开始 
		bl_a[i].reset();
		bl_b[i].reset();
	}
	for(int i = 1;i<=n;i++){
		cin>>a[i];
		ia[a[i]] = i;
	}
	for(int i = 1;i<=n;i++){
		cin>>b[i];
		ib[b[i]] = i;
	}
	for(int i = 1;i<=n;i++){
		bl_a[a[i]/len].set(i);
		bl_b[b[i]/len].set(i);
	}
	for(int i = cnt-1;i>=0;i--){
		bl_a[i]|=bl_a[i+1];
		bl_b[i]|=bl_b[i+1];
	}
	for(int iq = 1;iq<=q;iq++){
		int t,x,y,l,r;
		cin>>t;
		if(t==1){//a_x a_y
			cin>>x>>y;
			int ll = a[x]/len,rr = a[y]/len;
			if(ll>rr)swap(ll,rr);
			for(int i = ll+1;i<=rr;i++){//这里很重要,改的是整块的! 
				bl_a[i].flip(x);
				bl_a[i].flip(y);
			}
			swap(ia[a[x]],ia[a[y]]);
			swap(a[x],a[y]);
		}else if(t==2){//b_x b_y
			cin>>x>>y;
			int ll = b[x]/len,rr = b[y]/len;
			if(ll>rr)swap(ll,rr);
			for(int i = ll+1;i<=rr;i++){
				bl_b[i].flip(x);
				bl_b[i].flip(y);
			}
			swap(ib[b[x]],ib[b[y]]);
			swap(b[x],b[y]);
		}else{//query
			cin>>x>>l>>r;
			r++;
			ans = 0;
			c = g[x];
			res1.reset(),res2.reset();
			int h = (l+len-1)/len;//右边的块 
			res1 = bl_a[h];
			for(int i = l;i<min(n+1,h*len);i++){//
				res1.set(ia[i]);
			}
			h = (r+len-1)/len;
			res2 = bl_a[h];
			for(int i = r;i<min(n+1,h*len);i++){
				res2.set(ia[i]);
			}
			res1^=res2;
			c&=res1;
			int now = 0;
			for(int i = 0;i<M&&now<cnt-1;i++){
    			while(now<cnt-1&&(c.num[i]&bl_b[now+1].num[i])){
					now++;
				}
			}
			int ll = now*len;
			if(ll==0)ll = 1;
			int rr = min(n,(now+1)*len-1);
			for(int i = rr;i>=ll;i--){
				if(c.get(ib[i])){
					ans = i;
					break;
				}
			}
			cout<<ans<<"\n";
		}
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>C>>T;
	while(T--){
		solve();
	}
	return 0;
}

反思

感觉追忆不咋难啊,不降紫我还不敢做呢,但是我本来就不擅长数据结构,还是写了挺长时间的。
希望到省选的时候我能有场切追忆的水平。
其实这个目标不算很难实现?

P5419

思考过程

看到了题里给的提示,然后想那个转化,然后我就想模拟这个过程,每次操作n/2条边,使得它们没有交。
感觉比较乱,瞎做了一下就得6分。
其实这个构出来这题就切了。

题解

写出邻接矩阵。
对角线表示自环,我们没有自环,设为0。
然后发现邻接矩阵必然对称,对于每条边,我们只关注它是哪个集合的(这里的划分方式不变,n/2),编号1 to n-1。
对于一行,是一个点到所有点的连边,不能有相同的数,否则有交,列同理。
所以这是个拉丁方,并且满足对称且主对角线相等。
这个也太难构了!先不考虑对角线,对称就行,你可以形如
1 3 2
3 2 1
2 1 3
这么斜着放。
然后发现直接把对角线填上,因为是123,所以数的个数没变
0 3 2
3 0 1
2 1 0
数多了一种,扩大看看。
0 3 2 x
3 0 1 x
2 1 0 x
x x x 0
因为我们已经满足了拉丁方的性质,每行缺了对角线上那个,直接补。
0 3 2 1
3 0 1 2
2 1 0 3
1 2 3 0
做完了!
把每组的边填进来输出即可。

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 510;
int n;
int e[N][N];
int get(int x,int mx){
	if(x>mx)x-=mx;
	if(x<1)x+=mx;
	return x; 
}
int cnt = 0;
int cur[N];
int main(){
	cin>>n;
	int now = 1;
	for(int i = 1;i<=n;i++){
		cur[i] = 1;
	}
	for(int p = 1;p<n;p++){
		for(int i = now,j = 1;j<n;i--,j++){
			i = get(i,n-1);
			e[i][j] = p;
		}
		now = get(now+2,n-1);
	}
	for(int i = 1;i<n;i++){
		e[i][n] = i;
	}
	for(int i = 1;i<=n;i++){
		for(int j = i+1;j<=n;j++){
			cout<<(e[i][j]-1)*(n/2)+cur[e[i][j]]<<" ";
			cur[e[i][j]]++;
		}
		cout<<endl;
	}
	return 0;
}

反思

构造太神秘了。
总之把这个记住吧可能有用。

P5441

思考过程

完全图,看成一个圆。
我试着左手边连正边,右手边连反边,以为任取四个点都可以。
然后发现四个点在优弧上就死了,考虑双向边放哪里。
肯定放在优弧的最后最有了,这样C的上指标比较大,覆盖的情况多,推个式子证明了在有向边这样取的情况下这么做最优。
那么有向边这么取最优吗?我感性理解一下,边没有浪费的,所以这样取可以。
写了,获得99pts,发现n = 1需要特判,判完通过了。
是的,又切紫了。

题解

wwh给出了证明,先取一个点,假设它对取的另外三个点都向外连或向里连,就无了。
那么怎么使得这个最少呢,这个个数也是组合数,我们让两个组合数上指标接近均值,就最优了。

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int n;
int ans = 0;
int c[N][N];
int e[N][N];
inline int get(int x){
	if(x>n)x-=n;
	if(x<1)x+=n;
	return x;
}
int main(){
	cin>>n;
	if(n==1){
		cout<<"0\n0\n";
		return 0;
	} 
	c[0][0] = 1;
	for(int i = 1;i<=n;i++){
		c[i][0] = 1;
		for(int j = 1;j<=min(i,4);j++){
			c[i][j] = c[i-1][j]+c[i-1][j-1];
		}
	}
	ans = c[n][4];
	int res = 0;
	for(int i = 2;i<=n/2-2;i++){
		res+=n*c[i][2];
	}
	ans-=res;
	for(int i = 1;i<=n;i++){
		for(int j = 1,k = i+1;j<=n/2+1;j++,k++){
			k = get(k);
			e[i][k] = 1;
		}
		for(int j = n/2+1,k = i+n/2+1;j<n;j++,k++){
			k = get(k);
			e[k][i] = 1;
		}
	}
	cout<<ans<<"\n";
	for(int i = 1;i<=n;i++){
		for(int j = 1;j<=n;j++){
			cout<<e[i][j]<<" ";
		}
		cout<<"\n";
	}
	return 0;
}

反思

不太会证但是切了。
构造题是这样的。

P5811

思考过程

树会做了,发现考虑每条边就行。
这是40分,然后就想图能不能归约成树。
然后就是想点双,边双,园方树,最后想了一个看起来很真的边双算法,但是还是假的。

题解

图转化为树是对的,用dfs树即可。
然后,发现我们满足a,b即可(设a<b<c)
把重心搜出来,如果出了一个n/2左右的子树就通关了。
而且对于重心,如果任何一个子树都不能满足a,那么两个加起来不会超三分之二n,可以满足b。
子树能加吗?考虑到根是给b的,不好加,但是原图中还有边,我们凑一凑,凑出来大于a的就通关了。
一定要先判单个子树能不能符合a的要求!

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n,m;
int a,b,c;
pair<int,int> v[5];
vector<int> e[N];
bool intr[N];
vector<int> g[N];//dfs树
void get_dfs_tree(int x){
	for(int i = 0;i<e[x].size();i++){
		int h = e[x][i];
		if(!intr[h]){
			g[h].push_back(x);
			g[x].push_back(h);
			intr[h] = 1;
			get_dfs_tree(h);
		}
	}
}
int siz[N],G;
void get_G(int x,int fa){
	siz[x] = 1;
	int mx = 1;
	for(int i = 0;i<g[x].size();i++){
		int h = g[x][i];
		if(h!=fa){
			get_G(h,x);
			siz[x]+=siz[h];
			mx = max(mx,siz[h]);
		}
	}
	mx = max(mx,n-siz[x]);
	if(mx<=n/2)G = x;
}
int bel[N],rt[N],idx = 0;
int g_siz[N];
void find_sons(int x,int fa){
	g_siz[x] = 1;
	if(fa==G){
		bel[x] = ++idx;
		rt[idx] = x; 
	}else if(x!=G){
		bel[x] = bel[fa];
	}
	for(int i = 0;i<g[x].size();i++){
		int h = g[x][i];
		if(h!=fa){
			find_sons(h,x);
			g_siz[x]+=g_siz[h];
		}
	}
}
int cnt[N];
int in[N];
bool vis[N];
int k = 0,scc_cnt = 0;
void build(int x){
	if(!in[bel[x]]){
		in[bel[x]] = scc_cnt;
		cnt[scc_cnt]+=g_siz[rt[bel[x]]];
	}
	if(cnt[scc_cnt]>=v[1].first)return;
	for(int i = 0;i<e[x].size();i++){
		int h = e[x][i];
		if(!vis[h]){
			vis[h] = 1;
			build(h);
			if(cnt[scc_cnt]>=v[1].first)return;
		}
	}
}
int ans[N];
int num = 0;
void color(int x,int fa,int typ,int col,int aim){
	if(num==aim)return;
	ans[x] = col;
	num++;
	for(int i = 0;i<e[x].size();i++){
		int h = e[x][i];
		if(h!=fa&&(in[bel[h]]^typ)&&ans[h]==0){//
			color(h,x,typ,col,aim);
		}
	}
}
void recolor(){
	for(int i = 1;i<=n;i++){
		if(ans[i]==1)ans[i] = v[1].second;
		else if(ans[i]==2)ans[i] = v[2].second;
		else ans[i] = v[3].second;
	}
}
int main(){
	cin>>n>>m;
	cin>>a>>b>>c;
	v[1] = {a,1},v[2] = {b,2},v[3] = {c,3};
	sort(v+1,v+4);
	for(int i = 1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		u++,v++;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	intr[1] = 1;
	get_dfs_tree(1);
	get_G(1,0); 
	find_sons(G,0);
	for(int i = 1;i<=idx;i++){
		if(g_siz[rt[i]]>=v[1].first){
			scc_cnt = 1;
			in[i] = 1;
			cnt[scc_cnt] = g_siz[rt[i]];
			k = i;
			break;
		}
	}
	vis[G] = 1;
	if(k==0){
		for(int i = 1;i<=idx;i++){
			if(!in[i]){
				scc_cnt++;
				build(rt[i]);
				if(cnt[scc_cnt]>=v[1].first){
					k = i;
					break;
				}
			}
		}
	}
	if(k==0){
		for(int i = 1;i<=n;i++)cout<<"0 ";
		return 0;
	}
	for(int i = 1;i<=idx;i++){
		if(in[i]==scc_cnt){
			in[i] = 1;
		}else{
			in[i] = 0;
		}
	}
	color(rt[k],G,0,1,v[1].first);
	num = 0;
	color(G,0,1,2,v[2].first);
	recolor();
	for(int i = 1;i<=n;i++){
		cout<<ans[i]<<" ";
	}
	return 0;
}

反思

所以说黑题都这么神秘吗?
没敢想dfs树,挂着很多边感觉不好做,重心的性质更是不敢想。
代码也不怎么好写,但是也不算太难,和追忆难度差不多吧。

P11993

思考过程

看到是数据结构。
好吧我知道标签了,线段树合并,没想到怎么合。
然后发现给树分层很重要,于是想能不能分块,不能,然后想nq/w,追忆做多了是这样的。
大概糊了一下,发现应该可以,但是时间复杂度不行。

题解

看书一下看懂了,每层开一棵线段树,对dfn序开,然后合并直接合。
查询怎么查?查子树就完事了,这个点的权值要么是本身的要么是子树上来的。
所以说这么水我为什么想不出来?

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 2000010,M = 64000010;
int n,q;
vector<int> e[N];
int a[N];
struct segment_tree{//change merge query
	int num[M],ls[M],rs[M];
	int rt[N],idx;
	void push_up(int p){
		num[p] = num[ls[p]]+num[rs[p]];
	}
	void change(int &p,int l,int r,int x,int y){
		if(!p)p = ++idx;
		if(l==r){
			num[p]+=y;
			return;
		}
		int mid = (l+r)>>1;
		if(mid>=x)change(ls[p],l,mid,x,y);
		else change(rs[p],mid+1,r,x,y);
		push_up(p);
	}
	void merge(int &p,int q,int l,int r){
		if(!q)return;
		if(!p){
			p = q;
			return;
		}
		if(l==r){
			num[p]+=num[q];
			return;
		}
		int mid = (l+r)>>1;
		merge(ls[p],ls[q],l,mid);
		merge(rs[p],rs[q],mid+1,r);
		push_up(p);
	}
	int query(int p,int l,int r,int ll,int rr){
		if(p==0)return 0;
		if(l>=ll&&r<=rr){
			return num[p];
		}
		int mid = (l+r)>>1,res = 0;
		if(mid>=ll)res+=query(ls[p],l,mid,ll,rr);
		if(mid+1<=rr)res+=query(rs[p],mid+1,r,ll,rr);
		return res;
	}
}tr;
int siz[N],dfn[N],dep[N],cnt = 0,max_dep = 1;
void dfs(int x,int fa){
	dep[x] = dep[fa]+1;
	max_dep = max(max_dep,dep[x]);
	dfn[x] = ++cnt;
	siz[x] = 1;
	for(int i = 0;i<e[x].size();i++){
		int h = e[x][i];
		if(h!=fa){
			dfs(h,x);
			siz[x]+=siz[h];
		}
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0); 
	cin>>n;
	for(int i = 2;i<=n;i++){
		int t;
		cin>>t;
		e[i].push_back(t);
		e[t].push_back(i);
	}
	for(int i = 1;i<=n;i++){
		cin>>a[i];
	}
	dfs(1,0);
	tr.idx = max_dep;
	for(int i = 1;i<=max_dep;i++){
		tr.rt[i] = i;
	}
	for(int i = 1;i<=n;i++){
		tr.change(tr.rt[dep[i]],1,n,dfn[i],a[i]);
	}
	cin>>q;
	for(int i = 1;i<=q;i++){
		int t,x,y;
		cin>>t;
		if(t==1){
			cin>>x>>y;
			if(x+1>max_dep)continue; 
			tr.merge(tr.rt[y+1],tr.rt[x+1],1,n);
			tr.rt[x+1] = ++tr.idx;
		}else if(t==2){
			cin>>x>>y;
			tr.change(tr.rt[dep[x]],1,n,dfn[x],y);
		}else{
			cin>>x;
			cout<<tr.query(tr.rt[dep[x]],1,n,dfn[x],dfn[x]+siz[x]-1)<<'\n';
		}
	}
	return 0;
}

反思

加训数据结构,发现了性质不会维护就完蛋了。

posted @ 2026-06-30 11:10  wzc6666  阅读(5)  评论(0)    收藏  举报