专栏文章

题解:CF811E Vladik and Entertaining Flags

CF811E题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@minn4lv6
此快照首次捕获于
2025/12/02 05:07
3 个月前
此快照最后确认于
2025/12/02 05:07
3 个月前
查看原文
近期的 CF 补累了回来做杂题!
这道题我走了不少弯路,尝试了很多错误的解法,最终才找到正解的。

看到题目之后首先想到的是连通块数等于点数减边数,于是写了一个这样的代码:
CPP
#include<bits/stdc++.h>
#define mp(a,b) make_pair(a,b)
using namespace std;

inline int read(){
	int a=0,b=1;
	char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	b=-1;
		c=getchar();
	}
	while(isdigit(c)){
		a=(a<<1)+(a<<3)+(c-'0');
		c=getchar();
	}
	return a*b;
}
inline void write(int x){
	if(x<0)	putchar('-'),x=-x;
	if(x>=10)	write(x/10);
	putchar(x%10+48);
}
inline void write1(int x){
	write(x),putchar(' ');
}
inline void write2(int x){
	write(x),putchar('\n');
}
int n,m,q;
int a[12][114514];

int qzh1[114514];	//代表列的相同 
int qzh2[114514];	//跨行  
int main(){
	n=read(),m=read(),q=read();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			a[i][j]=read();			
		}
	}
//	for(int i=1;i<=n;i++){
//		for(int j=1;j<=m;j++){
//			cout<<a[i][j]<<' ';
//		}
//		cout<<endl;
//	}
//	cout<<endl;
	for(int i=1;i<=m;i++){
		//以 m 作为宽转移 
		qzh1[i]+=qzh1[i-1],qzh2[i]+=qzh2[i-1];
		for(int j=2;j<=n;j++){
			qzh1[i]+=(a[j][i]==a[j-1][i]);
//			if(a[j][i]==a[j-1][i]){
//				cout<<'!'<<'('<<j<<','<<i<<')'<<' '<<'('<<j-1<<','<<i<<')'<<endl;
//			}
//			else{
//				cout<<'@'<<'('<<j<<','<<i<<')'<<' '<<'('<<j-1<<','<<i<<')'<<endl;
//			}
		} 
		for(int j=1;j<=n;j++){
			qzh2[i]+=(a[j][i]==a[j][i-1]); 
		} 
//		cout<<'#'<<qzh1[i]<<' '<<qzh2[i]<<endl;
	} 
	while(q--){
		int l=read(),r=read();
//		cout<<n*(r-l+1)<<' '<<qzh1[r]-qzh1[l-1]<<' '<<qzh2[r]-qzh2[l]<<endl;
		write2(n*(r-l+1)-(qzh1[r]-qzh1[l-1])-(qzh2[r]-qzh2[l]));
	}
	putchar('\n');
	return 0;
}//2017.5.27~2025.10.10 
发现它会 WA on #2,因为可能出现以下情况:(xx 代表矩阵内某一个相同的数)
x & x\\ x & x \end{bmatrix}$$ 此时点数为 $4$,边数(只在相同且相邻的元素之间连边)也为 $4$。算下来答案为 $0$,但实际答案为 $1$。 考虑平面图的欧拉公式:$C=V-E+F-1$。($V$ 代表点数,$E$ 代表边数,$F$ 代表边分割之后形成的区域数)我想的是统计有多少个 $$\begin{bmatrix} x & x\\ x & x \end{bmatrix}$$ 代表多少个平面。 ```cpp #include<bits/stdc++.h> #define mp(a,b) make_pair(a,b) using namespace std; inline int read(){ int a=0,b=1; char c=getchar(); while(!isdigit(c)){ if(c=='-') b=-1; c=getchar(); } while(isdigit(c)){ a=(a<<1)+(a<<3)+(c-'0'); c=getchar(); } return a*b; } inline void write(int x){ if(x<0) putchar('-'),x=-x; if(x>=10) write(x/10); putchar(x%10+48); } inline void write1(int x){ write(x),putchar(' '); } inline void write2(int x){ write(x),putchar('\n'); } int n,m,q; int a[12][114514]; int qzh1[114514]; //代表列的相同 int qzh2[114514]; //跨行 int qzh3[114514]; //面数 int main(){ n=read(),m=read(),q=read(); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ a[i][j]=read(); } } // for(int i=1;i<=n;i++){ // for(int j=1;j<=m;j++){ // cout<<a[i][j]<<' '; // } // cout<<endl; // } // cout<<endl; for(int i=1;i<=m;i++){ //以 m 作为宽转移 qzh1[i]+=qzh1[i-1],qzh2[i]+=qzh2[i-1]; qzh3[i]+=qzh3[i-1]; for(int j=2;j<=n;j++){ qzh1[i]+=(a[j][i]==a[j-1][i]); qzh3[i]+=(a[j][i]==a[j-1][i]&&a[j][i]==a[j][i-1]&&a[j][i]==a[j-1][i-1]); // if(a[j][i]==a[j-1][i]){ // cout<<'!'<<'('<<j<<','<<i<<')'<<' '<<'('<<j-1<<','<<i<<')'<<endl; // } // else{ // cout<<'@'<<'('<<j<<','<<i<<')'<<' '<<'('<<j-1<<','<<i<<')'<<endl; // } } for(int j=1;j<=n;j++){ qzh2[i]+=(a[j][i]==a[j][i-1]); } // cout<<'#'<<qzh1[i]<<' '<<qzh2[i]<<endl; } while(q--){ int l=read(),r=read(); // cout<<n*(r-l+1)<<' '<<qzh1[r]-qzh1[l-1]<<' '<<qzh2[r]-qzh2[l]<<' '<<qzh3[r]-qzh3[l-1]<<endl; write2(n*(r-l+1)-(qzh1[r]-qzh1[l-1])-(qzh2[r]-qzh2[l])+(qzh3[r]-qzh3[l])); } putchar('\n'); return 0; }//2017.5.27~2025.10.10 ``` 但是有一种例外需要注意: $$\begin{bmatrix} x & x & x\\ x & y & x\\ x & x & x \end{bmatrix}$$ 于是原先的方法彻底失效了! 因为 $n$ 很小,$m$ 也只有 $10^5$,又是无修改的区间查询,考虑并查集+线段树的神奇做法! 线段树的每一个节点维护一段区间 $[l,r]$,维护 $l$ 列和 $r$ 列的所有位置所在的极大连通块。两块合并时判断两边是否同色,若同色,直接合并。 于是写出了以下代码: ```cpp #include<bits/stdc++.h> #define mp(a,b) make_pair(a,b) using namespace std; inline int read(){ int a=0,b=1; char c=getchar(); while(!isdigit(c)){ if(c=='-') b=-1; c=getchar(); } while(isdigit(c)){ a=(a<<1)+(a<<3)+(c-'0'); c=getchar(); } return a*b; } inline void write(int x){ if(x<0) putchar('-'),x=-x; if(x>=10) write(x/10); putchar(x%10+48); } inline void write1(int x){ write(x),putchar(' '); } inline void write2(int x){ write(x),putchar('\n'); } const int MAXN=114514; struct SGT{ int l,r; //代表这里的左右区间情况 int lcol[15],rcol[15]; //代表左右两边的颜色 其实是并查集的参数 int col; //颜色数目 }tree[MAXN*5],tree1[MAXN*5]; //tree1 代表永久保存版本的 tree //vector<int> cal; //在计算结束之后要还原的位置 queue<int> cal; int n,m,q; int id_[15][MAXN],a[15][MAXN]; int idx=0; int fa[15*MAXN]; //代表所有的父亲 并查集 不要求其可撤销 int col_in_column[MAXN]; //代表一列的情况 inline int find(int u){ if(fa[u]==u) return u; return fa[u]=find(fa[u]); } void pushup(int id,int mid){ int L=mid,R=mid+1; //由这两边的颜色决定 //将两边合并 tree[id].col=tree[id*2].col+tree[id*2+1].col; for(int i=1;i<=n;i++){ tree[id].lcol[i]=find(tree[id*2].lcol[i]); tree[id].rcol[i]=find(tree[id*2+1].rcol[i]); // if(a[L][i]==a[R][i]){ // tree[id].lcol[i]=find(tree[id].lcol[i]); // tree[id].rcol[i]=find(tree[id].rcol[i]); // if(tree[id].lcol[i]!=tree[id].rcol[i]){ // fa[tree[id].lcol[i]]=tree[id].rcol[i]; // } // } //仍然是从两边往上上传 tree[id*2].rcol[i]=find(tree[id*2].rcol[i]),tree[id*2+1].lcol[i]=find(tree[id*2+1].lcol[i]); if(a[i][L]==a[i][R]){ // cout<<'#'<<'('<<i<<','<<L<<')'<<' '<<'('<<i<<','<<R<<')'<<endl; if(tree[id*2].rcol[i]!=tree[id*2+1].lcol[i]){ // cout<<'@'<<'('<<i<<','<<L<<')'<<' '<<'('<<i<<','<<R<<')'<<endl; // 说明两侧颜色不一 需要调整 fa[tree[id*2].rcol[i]]=tree[id*2+1].lcol[i]; // cout<<'%'<<tree[id*2].rcol[i]<<' '<<tree[id*2+1].lcol[i]<<endl; tree[id].col--; //代表这里改变了 } } } for(int i=1;i<=n;i++){ tree[id].lcol[i]=find(tree[id].lcol[i]); tree[id].rcol[i]=find(tree[id].rcol[i]); tree1[id].lcol[i]=tree[id].lcol[i],tree1[id].rcol[i]=tree[id].rcol[i]; //tree1 保护 tree 的内层元素 } tree1[id].col=tree[id].col; } void build(int id,int l,int r){ tree[id].l=l,tree[id].r=r; tree1[id].l=l,tree1[id].r=r; if(l==r){ for(int i=1;i<=n;i++){ tree[id].lcol[i]=fa[id_[i][l]]; tree[id].rcol[i]=fa[id_[i][r]]; // cout<<'!'<<id<<' '<<l<<' '<<r<<' '<<fa[id_[i][l]]<<' '<<fa[id_[i][r]]<<' '<<tree[id].lcol[i]<<' '<<tree[id].rcol[i]<<endl; tree1[id].lcol[i]=tree[id].lcol[i],tree1[id].rcol[i]=tree[id].rcol[i]; } tree[id].col=col_in_column[l]; return; } int mid=l+r>>1; build(id*2,l,mid),build(id*2+1,mid+1,r); pushup(id,mid); //合并左右边界 } SGT query(int id,int l,int r){ int l1=tree[id].l,r1=tree[id].r; if(l1>=l&&r1<=r){ return tree[id]; //直接返回整一个的节点 } int mid=l1+r1>>1; SGT A,B,C; int tag=0; if(l<=mid) A=query(id*2,l,r),tag++; if(r>mid) B=query(id*2+1,l,r),tag+=2; if(tag==1) return A; if(tag==2) return B; //两个区间合并技巧 C.col=A.col+B.col; int L=mid,R=mid+1; for(int i=1;i<=n;i++){ C.lcol[i]=find(A.lcol[i]),C.rcol[i]=find(B.rcol[i]); A.rcol[i]=find(A.rcol[i]),B.lcol[i]=find(B.lcol[i]); if(a[i][L]==a[i][R]){ if(A.rcol[i]!=B.lcol[i]){ fa[A.rcol[i]]=B.lcol[i]; cal.push(A.rcol[i]),cal.push(B.lcol[i]); C.col--; } } } for(int i=1;i<=n;i++){ C.lcol[i]=find(C.lcol[i]); C.rcol[i]=find(C.rcol[i]); } return C; } int main(){ n=read(),m=read(),q=read(); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ id_[i][j]=++idx,a[i][j]=read(); if(a[i][j]==a[i-1][j]) fa[id_[i][j]]=fa[id_[i-1][j]]; else fa[id_[i][j]]=id_[i][j],col_in_column[j]++; //在这里 } } // cout<<endl; // for(int i=1;i<=n;i++){ // for(int j=1;j<=m;j++){ // cout<<fa[id_[i][j]]<<' '; // } // cout<<endl; // } // cout<<endl; // for(int i=1;i<=m;i++){ // cout<<col_in_column[i]<<' '; // } // cout<<endl; build(1,1,m); //后面的操作仅需修复相关的并查集即可 // for(int i=1;i<=4*m;i++){ // cout<<"谭总的世界-022"<<endl; // cout<<'#'<<i<<' '<<tree1[i].l<<' '<<tree1[i].r<<' '<<tree1[i].col<<endl; // for(int j=1;j<=n;j++){ // cout<<tree1[i].lcol[j]<<' '; // } // cout<<endl; // for(int j=1;j<=n;j++){ // cout<<tree1[i].rcol[j]<<' '; // } // cout<<endl<<endl; // } // cout<<endl; for(int i=1;i<=idx;i++){ fa[i]=i; //又把他搞回来 } while(q--){ int l=read(),r=read(); write2(query(1,l,r).col); while(!cal.empty()){ int w=cal.front(); cal.pop(); fa[w]=w; } } putchar('\n'); return 0; }//2017.5.27 ``` 该代码存在一严重问题:随着合并的不断推进,并查集编号会发生变化,所以需要维护并查集编号的历史值,具体来说再开一颗相同的线段树就够了! 时间复杂度 $O(nm\log m\alpha (n)+q\log m\alpha (n))$。 ```cpp #include<bits/stdc++.h> #define mp(a,b) make_pair(a,b) using namespace std; inline int read(){ int a=0,b=1; char c=getchar(); while(!isdigit(c)){ if(c=='-') b=-1; c=getchar(); } while(isdigit(c)){ a=(a<<1)+(a<<3)+(c-'0'); c=getchar(); } return a*b; } inline void write(int x){ if(x<0) putchar('-'),x=-x; if(x>=10) write(x/10); putchar(x%10+48); } inline void write1(int x){ write(x),putchar(' '); } inline void write2(int x){ write(x),putchar('\n'); } const int MAXN=114514; struct SGT{ int l,r; //代表这里的左右区间情况 int lcol[15],rcol[15]; //代表左右两边的颜色 其实是并查集的参数 int col; //颜色数目 }tree[MAXN*5],tree1[MAXN*5]; //tree1 代表永久保存版本的 tree //vector<int> cal; //在计算结束之后要还原的位置 queue<int> cal; int n,m,q; int id_[15][MAXN],a[15][MAXN]; int idx=0; int fa[15*MAXN]; //代表所有的父亲 并查集 不要求其可撤销 int col_in_column[MAXN]; //代表一列的情况 inline int find(int u){ if(fa[u]==u) return u; return fa[u]=find(fa[u]); } void pushup(int id,int mid){ int L=mid,R=mid+1; //由这两边的颜色决定 //将两边合并 tree[id].col=tree[id*2].col+tree[id*2+1].col; for(int i=1;i<=n;i++){ tree[id].lcol[i]=find(tree[id*2].lcol[i]); tree[id].rcol[i]=find(tree[id*2+1].rcol[i]); // if(a[L][i]==a[R][i]){ // tree[id].lcol[i]=find(tree[id].lcol[i]); // tree[id].rcol[i]=find(tree[id].rcol[i]); // if(tree[id].lcol[i]!=tree[id].rcol[i]){ // fa[tree[id].lcol[i]]=tree[id].rcol[i]; // } // } //仍然是从两边往上上传 tree[id*2].rcol[i]=find(tree[id*2].rcol[i]),tree[id*2+1].lcol[i]=find(tree[id*2+1].lcol[i]); if(a[i][L]==a[i][R]){ // cout<<'#'<<'('<<i<<','<<L<<')'<<' '<<'('<<i<<','<<R<<')'<<endl; if(tree[id*2].rcol[i]!=tree[id*2+1].lcol[i]){ // cout<<'@'<<'('<<i<<','<<L<<')'<<' '<<'('<<i<<','<<R<<')'<<endl; // 说明两侧颜色不一 需要调整 fa[tree[id*2].rcol[i]]=tree[id*2+1].lcol[i]; // cout<<'%'<<tree[id*2].rcol[i]<<' '<<tree[id*2+1].lcol[i]<<endl; tree[id].col--; //代表这里改变了 } } } for(int i=1;i<=n;i++){ tree[id].lcol[i]=find(tree[id].lcol[i]); tree[id].rcol[i]=find(tree[id].rcol[i]); tree1[id].lcol[i]=tree[id].lcol[i],tree1[id].rcol[i]=tree[id].rcol[i]; //tree1 保护 tree 的内层元素 } tree1[id].col=tree[id].col; } void build(int id,int l,int r){ tree[id].l=l,tree[id].r=r; tree1[id].l=l,tree1[id].r=r; if(l==r){ for(int i=1;i<=n;i++){ tree[id].lcol[i]=fa[id_[i][l]]; tree[id].rcol[i]=fa[id_[i][r]]; // cout<<'!'<<id<<' '<<l<<' '<<r<<' '<<fa[id_[i][l]]<<' '<<fa[id_[i][r]]<<' '<<tree[id].lcol[i]<<' '<<tree[id].rcol[i]<<endl; tree1[id].lcol[i]=tree[id].lcol[i],tree1[id].rcol[i]=tree[id].rcol[i]; } tree[id].col=col_in_column[l]; tree1[id].col=tree[id].col; return; } int mid=l+r>>1; build(id*2,l,mid),build(id*2+1,mid+1,r); pushup(id,mid); //合并左右边界 } SGT query(int id,int l,int r){ // cout<<'*'<<id<<' '<<l<<' '<<r<<endl; int l1=tree[id].l,r1=tree[id].r; if(l1>=l&&r1<=r){ return tree1[id]; //直接返回整一个的节点 } int mid=l1+r1>>1; SGT A,B,C; int tag=0; if(l<=mid) A=query(id*2,l,r),tag++; if(r>mid) B=query(id*2+1,l,r),tag+=2; if(tag==1){ // cout<<'A'<<' '<<l<<' '<<r<<' '<<A.col<<endl; return A; } if(tag==2){ // cout<<'B'<<' '<<l<<' '<<r<<' '<<B.col<<endl; return B; //两个区间合并技巧 } C.col=A.col+B.col; // cout<<"col"<<' '<<A.col<<' '<<B.col<<endl; int L=mid,R=mid+1; // cout<<'C'<<' '<<id<<endl; // for(int i=1;i<=n;i++){ // cout<<'!'<<A.lcol[i]<<' '<<A.rcol[i]<<endl; // } // for(int i=1;i<=n;i++){ // cout<<'@'<<B.lcol[i]<<' '<<B.rcol[i]<<endl; // } for(int i=1;i<=n;i++){ C.lcol[i]=find(A.lcol[i]),C.rcol[i]=find(B.rcol[i]); A.rcol[i]=find(A.rcol[i]),B.lcol[i]=find(B.lcol[i]); if(a[i][L]==a[i][R]){ if(A.rcol[i]!=B.lcol[i]){ fa[A.rcol[i]]=B.lcol[i]; // cout<<A.rcol[i]<<" "<<B.lcol[i]<<endl; cal.push(A.rcol[i]),cal.push(B.lcol[i]); C.col--; // cout<<'&'<<i<<endl; // cout<<"谭总的世界-035"<<endl; } } } for(int i=1;i<=n;i++){ C.lcol[i]=find(C.lcol[i]); C.rcol[i]=find(C.rcol[i]); // cout<<'!'<<C.lcol[i]<<' '<<C.rcol[i]<<endl; } return C; } int main(){ n=read(),m=read(),q=read(); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ id_[i][j]=++idx,a[i][j]=read(); if(a[i][j]==a[i-1][j]) fa[id_[i][j]]=fa[id_[i-1][j]]; else fa[id_[i][j]]=id_[i][j],col_in_column[j]++; //在这里 } } // cout<<endl; // for(int i=1;i<=n;i++){ // for(int j=1;j<=m;j++){ // cout<<fa[id_[i][j]]<<' '; // } // cout<<endl; // } // cout<<endl; // for(int i=1;i<=m;i++){ // cout<<col_in_column[i]<<' '; // } // cout<<endl; build(1,1,m); //后面的操作仅需修复相关的并查集即可 // for(int i=1;i<=4*m;i++){ // cout<<"谭总的世界-022"<<endl; // cout<<'#'<<i<<' '<<tree1[i].l<<' '<<tree1[i].r<<' '<<tree1[i].col<<endl; // for(int j=1;j<=n;j++){ // cout<<tree1[i].lcol[j]<<' '; // } // cout<<endl; // for(int j=1;j<=n;j++){ // cout<<tree1[i].rcol[j]<<' '; // } // cout<<endl<<endl; // } // cout<<endl; for(int i=1;i<=idx;i++){ fa[i]=i; //又把他搞回来 } while(q--){ int l=read(),r=read(); write2(query(1,l,r).col); while(!cal.empty()){ int w=cal.front(); cal.pop(); fa[w]=w; } } putchar('\n'); return 0; }//2017.5.27 ``` $205$ 行的实现肯定算不上好,相信各位都有比我好的做法!

评论

0 条评论,欢迎与作者交流。

正在加载评论...