22.4.7

1,走方格(dfs转dp);2,网络分析(对邻接表和bfs的使用)


1,走方格

题意:

找路径个数,dfs肯定可以,并且写着顺手,直接dfs,但是在(30,29)这个点tle了;

 (正好复习下dfs板子,好久不写了)

#include<bits/stdc++.h>
#define rep1(i,a,n) for(register ll i=a;i<n;i++) 
#define rep2(i,a,n) for(register ll i=a;i<=n;i++) 
#define per1(i,n,a) for(register ll i=n;i>a;i--) 
#define per2(i,n,a) for(register ll i=n;i>=a;i--)
#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
#define INF 0x3f3f3f3f
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
typedef double db;
int n,m;
int f[110][110];
int ans;
int dx[]={0,1},dy[]={1,0};
bool canmove(int x,int y)
{
	if(x>n||y>m||(x%2==0&&y%2==0))return 0;
	return 1;
}
void dfs(int x,int y)
{
	if(x==n&&y==m)
	{
		ans++;
		return;
	}
	rep1(i,0,2)
	{
		int x1=x+dx[i],y1=y+dy[i];
		if(canmove(x1,y1))dfs(x1,y1);
	}
}
signed main()
{
	quick_cin();
	cin>>n>>m;
	if(n%2==0&&m%2==0)cout<<0;
	else 
	{
		dfs(1,1);
		cout<<ans;
	}
	return 0;
}   

dfs不行,考虑dp

按照闫式dp分析法:
第一步:状态表示:

f[i , j ]表示从起点到1,1的到i ,j的方案数

属性:数量

第二步:状态计算:
先划分集合:根据最后一步的走法:有两种

要么从(i-1,j)向下走走到(i,j)要么从(i,j-1)向右走,走到(i,j)

这样的划分是不重不漏地;

然后计算

求子集,

左子集刚好为f[ i-1, j ] 右子集也是f[ i , j-1 ]

所以状态转移方程:f[ i ,  j ]=f[ i-1, j ] +f[ i ,j-1 ];

#include<bits/stdc++.h>
#define rep1(i,a,n) for(register ll i=a;i<n;i++) 
#define rep2(i,a,n) for(register ll i=a;i<=n;i++) 
#define per1(i,n,a) for(register ll i=n;i>a;i--) 
#define per2(i,n,a) for(register ll i=n;i>=a;i--)
#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
#define INF 0x3f3f3f3f
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
typedef double db;
int n,m;
int f[110][110];
signed main()
{
	quick_cin();
	cin>>n>>m;
	f[1][1]=1;
	rep2(i,1,n)
	 rep2(j,1,m)
	 {
	 	if(i==1&&j==1)continue;
	 	if(i%2||j%2)f[i][j]=f[i-1][j]+f[i][j-1];
	 }
	 cout<<f[n][m];
	return 0;
}   

2,网络分析

题意:给n个节点和m个操作,建网络是个逐渐的过程,若输入为1,则建立a和b的边,为2,则从节点p开始,先网络中每个与p连通的节点+信息值b;最后逐一输出每个节点的信息值;

本来是想着用bfs去循环整个已形成的图,每个节点加b,但是发现邻接表的真正使用;它其实只可以存储当前头节点下的子结点,可以理解只有一层;

(当然题意是无向图,只有边)这里我想从1开始,整个遍历图是只靠h[1]办不到的;

h[1]只能到2,而h[2]能到3,4所有就需要把每一层的头结点存下来,也就是bfs,所以邻接表的遍历又一次加深了理解;

然后就是加b操作,因为可能会有头结点时另一结点的子结点或者重复遍历到了,加b只能一次,所以需要判断是否遍历过;

#include<bits/stdc++.h>
#define rep1(i,a,n) for(register ll i=a;i<n;i++) 
#define rep2(i,a,n) for(register ll i=a;i<=n;i++) 
#define per1(i,n,a) for(register ll i=n;i>a;i--) 
#define per2(i,n,a) for(register ll i=n;i>=a;i--)
#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
#define INF 0x3f3f3f3f
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
typedef double db;
int n,m,k;
const int N=1e4+10;
int e[N],ne[N],h[N],w[N],idx;
void add(int a,int b)
{
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int q[N];
int hh,tt;
unordered_map<int,int>appear;
void bfs(int a,int b)
{
	hh=1;
	unordered_map<int,int>hs;
	while(hh<=tt)
	{
		int t=q[hh++];
		//cout<<"t:"<<t<<endl;
		if(hs[t]==0)w[t]+=b;
		hs[t]=1;		
	//	cout<<"+:"<<t<<" "<<b<<endl;
		for(int i=h[t];i!=-1;i=ne[i])
		{
			if(!hs[e[i]])
			{
				w[e[i]]+=b;
			//	cout<<"+:"<<e[i]<<" "<<b<<endl;
				hs[e[i]]=1;
			}
		}
	}
}
signed main()
{
	quick_cin();
	int n,m;
	cin>>n>>m;
	memset(h,-1,sizeof h);
	while(m--)
	{
		int c,a,b;
		cin>>c>>a>>b;
		if(c==1)
		{
			if(a==b)continue;
			add(a,b);
			if(appear[a]==0)q[++tt]=a;
			appear[a]=1;
			appear[b]=1;
		}
		else
		{	
			if(appear[a]==0)w[a]+=b;
			else bfs(a,b);
		}
	}
	rep2(i,1,n)cout<<w[i]<<" ";
	return 0;
}   

但是这样写会错,也不太明白哪一步错了;

正确解法是并查集的做法:

操作1:加一条边,这个对于并查集来说是基础操作;

操作2:给连通块的每一个点加b:

对于这个操作,我们认为遍历连通块,逐个加b很浪费时间(tle),考虑给根结点加b(连通块在并查集中的表现形式是树)来代表给整个连通块都加b,那么是否可行呢?答案是可行的;我们只需要考虑每个点到根节点的路径权值之和,因为是考虑了根节点,也就考虑了之前的加b操作,所以可行;

但是对于合并两个连通块来说,是不能直接合并的 ;

直接把+5连向+4,会导致+5及其子结点都加4,这是不允许的;

有两种解决方法:

一:新建一个根节点,左右孩子为这两个子树;

二:+5改为+1,再连向+4;这样也合法;

#include<bits/stdc++.h>
#define rep1(i,a,n) for(register ll i=a;i<n;i++) 
#define rep2(i,a,n) for(register ll i=a;i<=n;i++) 
#define per1(i,n,a) for(register ll i=n;i>a;i--) 
#define per2(i,n,a) for(register ll i=n;i>=a;i--)
#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
#define INF 0x3f3f3f3f
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
typedef double db;
int n,m,k;
const int N=1e4+10;
int p[N],d[N];
int find(int x)
{
	if(p[x]==x||p[x]==p[p[x]])return p[x];
	int r=find(p[x]);
	d[x]+=d[p[x]];
	p[x]=r;
	return r;
}
signed main()
{
	quick_cin();
	int n,m;
	cin>>n>>m;
	rep2(i,1,n)p[i]=i;
	while(m--)
	{
		int t,a,b;
		cin>>t>>a>>b;
		if(t==1)
		{
			a=find(a),b=find(b);
			if(a!=b)
			{
				d[a]-=d[b];
				p[a]=b;
			}
		}
		else
		{
			a=find(a);
			d[a]+=b;
		}
	}
	rep2(i,1,n)
	{
		if(i==find(i))cout<<d[i]<<" ";
		else cout<<d[i]+d[find(i)]<<" ";
	}
	return 0;
}   
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dull丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值