WaWa的奇妙冒险(第九周集训自闭现场)

第九周周记(刷水题的一周,还算有点收获)

  • (一)bfs联通块思想
  • (二)关于负进制
  • (三)环形区间dp
  • (四)无限dfs和有限dfs的判断
  • (五)二分思想最小化最大值、最大化最小值
  • (六)状压dfs和并查集判断无向图是否联通
  • 总结:

(一)bfs联通块思想

对于bfs的经典例题,红黑格子数块数这种做法想必大家都十分熟悉了,然后这周写题的时候,发现一道十分类似的01迷宫题目,但要求多次查询,且查询次数非常之大,每次都做一次bfs必然超时,这时候有了联通块的思想,即能走到的地方算作一块地图,上面的值都一样,再用记忆化搜索得到解。

洛谷 P1141 01迷宫

#include 
using namespace  std;

struct node{
	int x,y;
	node(int x,int y):x(x),y(y){}
};

int n,m;
map <int,int> p;  ##  记录该联通块有多少个能走的格子
int maps[1005][1005];
int vis[1005][1005];
int mvx[] = {1,0,0,-1};
int mvy[] = {0,1,-1,0};

inline void clear(){
	for(int i = 1;i <= n;++i){
		for(int j = 1;j <= n;++j){
			vis[i][j] = 0;
		}
	}
	return ;
}

inline void read(){
	for(int i = 1;i <= n;++i){
		for(int j = 1;j <= n;++j){
			scanf("%1d",&maps[i][j]);
		}
	}
	return ;
}

inline bool check(int x,int y,int key){
	if(x < 1 || x > n || y < 1 || y > n || maps[x][y] == key || vis[x][y]) return 0;
	return 1;
}

inline void bfs(int x,int y,int k){
	int sum = 0;
	queue <node> q;
	
	vis[x][y] = k;
	sum++;
	node t(x,y);
	q.push(t);
	
	while(!q.empty()){
		t = q.front();q.pop();
		
		for(int i = 0;i < 4;++i){
			int tx = t.x + mvx[i];
			int ty = t.y + mvy[i];
			if(check(tx,ty,maps[t.x][t.y])){
				vis[tx][ty] = k;
				node tt(tx,ty);
				q.push(tt);
				sum++;
			}
		}
	}
	
	p[k] = sum;
	return ;
}

inline void solve(){
	int x,y,cnt = 1;  ##  用于联通块标号
	for(int i = 0;i < m;++i){
		scanf("%d%d",&x,&y);
		if(vis[x][y]) printf("%d\n",p[vis[x][y]]);
		else{
			bfs(x,y,cnt);
			printf("%d\n",p[vis[x][y]]);
			cnt++;
		}
	}
	return ;
} 

int main()
{
	scanf("%d%d",&n,&m);
	clear();
	read();
	solve();
	
	return 0;
}

(二)关于负进制

负进制也算第一次接触吧,写的时候发现有点懵逼,因为余出来会是负数,这就涉及到了之前老师给我们讲的取余和取模的区别,因为此处需要取余,所以要做特殊处理,对于余为负数的情况,使商加1,并且使余数加上m(转为m进制的情况下),从取模变成取余,即可正确完成进制转换

题目也只水了一道 洛谷 P1017 进制转换

#include 
using namespace std;

void itoR(int n,int r){
	if(!n) return ;
	int k = n%r;
	n /= r;
	if(k < 0){
		k -= r;
		n++;
	}
	itoR(n,r);
	printf("%c",k >= 10 ?k-10+'A' :k+'0');
}

int main()
{
	int n,r;
	scanf("%d%d",&n,&r);
	printf("%d=",n);
	if(n == 0) printf("0");
	else itoR(n,r);
	printf("(base%d)",r);
	
	return 0;
}

(三)环形区间dp

这周也是莫名其妙成功推出了区间dp的dp公式,刚开始很兴奋,但没看到题目要求是环形的,于是果断wa了。然后也是积攒了对于这类题目的经验:
1.扩大为2*n的区间,这样就可以将环形问题转化为直线问题
2.采用下标%n的做法进行操作,但emm 没写出来,之前写了一次wa了,之后再写写看(理论上是可以的)

简单例题记录
洛谷 P1880 [NOI1995]石子合并

#include 
using namespace std;
const int INF = 0x3f3f3f3f;

int n,dp[205][205],dp2[205][205];

void clear(){
	for(int i = 1;i <= (n << 1);++i){
		for(int j = 1;j <= (n << 1);++j){
			dp[i][j] = 0;
			dp2[i][j] = INF;
		}
	}
	return ;
}

int main()
{
	int sum[205],a[205];
	scanf("%d",&n);
	clear();
	sum[0] = 0;
	
	for(int i = 1;i <= n;++i) scanf("%d",&a[i]);
	for(int i = 1;i <= (n << 1);++i) sum[i] = sum[i-1] + a[(i-1)%n+1],dp2[i][i] = 0;
	
	for(int i = (n << 1) - 1;i >= 1;--i){
		for(int j = i+1;j <= (n << 1);++j){
			for(int k = i;k < j;++k){
				dp[i][j] = max(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
				dp2[i][j] = min(dp2[i][j],dp2[i][k]+dp2[k+1][j]+sum[j]-sum[i-1]);
			}
		}
	}
	
	int ans = 0,ans2 = INF;
	for(int i = 1;i <= n;i++){
		ans = max(ans,dp[i][i+n-1]);
		ans2 = min(ans2,dp2[i][i+n-1]);
	}
	
	printf("%d\n%d",ans2,ans);
	return 0;
} 

洛谷 P1063 能量项链

思路:环状开n*2的范围,然后开始区间dp即可,这题有一个特点,可以将n长度看做n+1长度,转化为3及以上珠子进行聚合

#include 
using namespace std;

int a[205],dp[205][205];

int main()
{
	int n;
	memset(dp,0,sizeof(dp));
	scanf("%d",&n);
	for(int i = 1;i <= n;++i) scanf("%d",&a[i]),a[n+i] = a[i];
	
	int ans = 0;
	for(int k = 3;k <= n+1;++k){
		for(int i = 1;i <= 2*n-k+1;++i){
			int j = i+k-1;
			for(int c = i+1;c <= j-1;++c){
				dp[i][j] = max(dp[i][j],dp[i][c]+dp[c][j]+a[i]*a[c]*a[j]);
			}
			ans = max(ans,dp[i][j]);
		}
	}
	
	printf("%d",ans);
	return 0;
}

可以看出,我用了两种不同的写法来写这两题,一题的dp是从边界开始枚举,一题的dp是从长度开始枚举,这也是后面补石子dp知识时看到的东西,姑且都试试
还有第三种写法,见下面blog
https://blog.csdn.net/ssllyf/article/details/84860275

(四)无限dfs和有限dfs的判断

实际上这个标题取的不好,因为这只是看题目而言的,即对于某个m*n矩阵的无限重叠,如果能过达成走到无限远这种操作,即视为无限dfs,否则是有限dfs

直接见例题吧
洛谷 P1363 幻象迷宫(唯一写的一题提高+,我真的好难)

思路:设立两种位置x,y,一种是相对位置,即对m与n取模所得到的第一张图的坐标,也就是其他图相对第一张图的位置,另外一个就是实际坐标,不取模。那么,当相对坐标出现重复走,并且实际坐标不同时,说明其重复到达了这个位置,那么,这个dfs是无限的。

好了 上代码

#include 
using namespace std;

int n,m;
char maps[1500][1500];
int sx[1500][1500],sy[1500][1500];
bool f,vis[1500][1500];
int mvx[] = {1,0,0,-1};
int mvy[] = {0,1,-1,0};

void clear(){
	for(int i = 0;i < n;++i){
		for(int j = 0;j < m;++j){
			sx[i][j] = sy[i][j] = -1;
			vis[i][j] = 0;
		}
	}
	return ;
}

void dfs(int x,int y,int lx,int ly){
	if(f) return ;
	if(vis[x][y] && (sx[x][y] != lx || sy[x][y] != ly)){f = 1;return ;}
	
	vis[x][y] = 1;
	sx[x][y] = lx;sy[x][y] = ly;
	
	for(int i = 0;i < 4;++i){
		int tx = lx + mvx[i];
		int ty = ly + mvy[i];
		int ttx = (x + n + mvx[i]) % n;
		int tty = (y + m + mvy[i]) % m;
		if(maps[ttx][tty] == '#') continue;
		if(!vis[ttx][tty] || (sx[ttx][tty] != tx || sy[ttx][tty] != ty)) dfs(ttx,tty,tx,ty);
	}
	
	return ;
}

int main()
{
	while(~scanf("%d%d",&n,&m)){
		clear();
		
		int x,y;
		for(int i = 0;i < n;++i){
			for(int j = 0;j < m;++j){
				cin >> maps[i][j];
				if(maps[i][j] == 'S'){
					x = i;
					y = j;
				}
			}
		}
		
		f = 0;
		dfs(x,y,x,y);
		if(f) printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}

(五)二分思想最小化最大值、最大化最小值

有些题目不能直接得出答案,但能确定答案范围的,可以采用二分的方式枚举答案,以达到最小化最大值和最大化最小值的要求。

洛谷 P1182 数列分段 Section II

#include 
using namespace std;
const int maxn = 1e5+5;

int n,m,a[maxn];

inline bool check(int k){
	int cnt = 0,s = 0;
	for(int i = 1;i <= n;++i){
		if(s + a[i] > k) s = 0,cnt++;
		s += a[i];
	}
	if(cnt >= m) return 1;
	else return 0;
}

int main()
{
	scanf("%d%d",&n,&m);
	
	int sum = 0,maxs = 0;
	for(int i = 1;i <= n;++i){
		scanf("%d",&a[i]);
		maxs = a[i] > maxs ?a[i] :maxs;
		sum += a[i];
	}
	
	while(maxs <= sum){
		int mid = (maxs+sum) / 2;
		if(check(mid)) maxs = mid + 1;
		else sum = mid - 1;
	}
	
	printf("%d",maxs);
	return 0;
}

洛谷 P1316 丢瓶盖

#include 
using namespace std;
const int maxn = 1e5+5;

int n,m,a[maxn];

inline bool check(int k){
	int cnt = 1,s = 1;
	for(int i = 1;i <= n;++i){
		if(a[i] - a[s] > k){
			s = i;
			cnt++;
		}
	}
	if(cnt >= m) return 1;
	else return 0;
}

int main()
{
	scanf("%d%d",&n,&m);
	
	for(int i = 1;i <= n;++i) scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	
	int l = 0,r = a[n]-a[1];
	while(l <= r){
		int mid = (l+r)/2;
		if(check(mid)) l = mid+1;
		else r = mid-1;
	} 
	
	printf("%d",l);
	return 0;
}

(六)状压dfs和并查集判断无向图是否联通

NCPU的一道题,cycles

题目大意:要求你判断无向图是否联通,若联通,有多少个不重复的环路(小于2个输出n,等于两个输出y,大于两个输出至少有三个环)

思路:并查集判断无向图是否联通,找环的话,dfs一下即可,用二进制位压缩环里面经过的点,完成去重

#include 
using namespace std;

int x,y,n,m,t,pre[20];  ##  pre为并查集
int maps[20][20],sum[20],ans,k;  ##  maps为邻接矩阵,记录路径,sum记录点的度
map <int,int> p;
bool vis[20];

void dfs(int now,int k,int zt){
	if(now == k) {if(!p.count(zt))ans++,p[zt]++;return ;}
	
	for(int i = 1;i <= n;++i){
		if(!vis[i] && maps[now][i]){
			vis[i] = 1;
			dfs(i,k,zt ^ (1 << (i-1)));  ##  zt为当前环的状态,即环中有哪几个数字
			vis[i] = 0;
		}
	}
	
	return ;
}

void init(){  ##  初始化
	for(int i = 1;i <= n;++i){
		vis[i] = 0;
		sum[i] = 0;
		pre[i] = i;
		for(int j = 1;j <= n;++j){
			maps[i][j] = 0;
		}
	}
	return ;
}

int fd(int v){
	return pre[v] = (pre[v] == v ?v :fd(pre[v]));
}

int main()
{	
	//freopen(”test.in“,”r“,stdin);
	
	scanf(%d“,&t);
	while(t—){
		scanf(%d%d“,&n,&m);
		init();
		p.clear();
		
		while(m—){
			scanf(%d%d“,&x,&y);
			maps[x][y] = maps[y][x] = 1;
			sum[x]++;sum[y]++;	
			pre[fd(x)] = pre[fd(y)];
		}
		
		k = 0;
		for(int i = 1;i <= n;++i){
			if(pre[i] == i) k++;
		}
		if(k != 1) {printf(”n\n“);continue;}
		
		ans = 0; 
		for(int i = 1;i <= n;++i){
			if(sum[i] > 1){
				for(int j = 1;j <= n;++j){
					if(maps[i][j]){
						vis[j] = 1;
						maps[j][i] = maps[i][j] = 0;
						dfs(j,i,(1 << (j-1)));
						maps[j][i] = maps[i][j] = 1;
						vis[j] = 0;
					}
				}
			}
		}
		
		if(ans < 2) printf(”n\n“);
		else if(ans == 2) printf(”y\n“);
		else printf(”y: there are at least three cycles\n“);
	}
	return 0;
}

后面这题交不了了,当时写的时候是不懂并查集,所以一直wa,后面拿到了测试样例(很水),加个并查集就过了。。。,啥时候能交了去交交看好了

总结:

刷刷水题的一周。。。(也不是纯水题啦,有那么几道觉得难的,wa了几次),收获还是有一点的

1.对于二分的理解更深刻了,之前二分的拿暴力水过去了,在做洛谷的普及组的时候,发现二分对于枚举答案有一个很神奇的帮助,可以最小化最大值或者最大化最小值,之前是真想不到,感觉就像忽然开阔了视野

2.再说说第二个收获,区间dp貌似摸到入门了,不造为啥,之前根本看的一知半解的东西,自己写石子合并的时候推着推着就出来了,虽然第一次还是wa了(那道题目是环形),后面又写了两道环形的区间dp,貌似就是到了那个点就能懂一样,很神奇,还学到三种写的方法,感觉还行

3.对于全排列的问题,貌似能自己写next_pernation了,很神奇,就感觉忽然懂了那个原理,就能写了

4.自己完成了数字hash下去重的dfs找无向图环路,虽然不是什么非常神奇的操作,但因为从头到尾都是自己构架的,有一点点成就感,记录一下

5.做负进制的时候,对取模和取余运算忽然有了一个新的理解,本来讲python和c++在这一块的区别的时候还是一知半解的

6.加深了对位运算的理解,位取反,二进制倒置,去末位1,去高位1等操作,忽然就搞明白了一样

7.并查集的运用,搞懂了看无向图是否联通,现在还在思考关于有向图的单向联通、强联通、弱联通的判断方式,感觉是有点东西可以想想

简单来说,刷了一周的水题,还算有点收获,最本质的感觉,基础扎实了,真的扎实了很多,再过一遍水题,貌似总有些新的收获

你可能感兴趣的:(萌新级)