算法学习入门---算法题DAY3

1.小苯的迷宫行走

错误代码:

cpp 复制代码
#include<iostream>
#include<queue>
#include<string.h>
#define int long long
using namespace std;

const int N = 4010;
int a[N][N];
int dp[N][N];
bool vst[N][N];
int n,m;
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
typedef pair<int,int> PII;

int bfs()
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            vst[i][j] = false;
            dp[i][j] = 0;
        }
	queue<PII> q;
	vst[1][1] = true;
	q.push({1,1});
	dp[1][1] = a[1][1];
	while(q.size())
	{
		//开始bfs
		auto t = q.front();q.pop();
		int i = t.first,j = t.second;
		for(int k=0;k<4;k++)
		{
			int x = i + dx[k],y = j + dy[k];
			if(x>=1&&x<=n&&y>=1&&y<=m)
			{
				if(vst[x][y]==false)
				{
					//尚未访问过
					vst[x][y] = true;
					q.push({x,y});	
				}
				dp[x][y] = max(dp[x][y],dp[i][j]|a[x][y]);	
			}	
		} 
	}
	return dp[n][m];
}

signed main()
{
    ios::sync_with_stdio();
    cin.tie(0);cout.tie(0);
	int T;cin>>T;
	while(T--)
	{
		cin>>n>>m;
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				cin>>a[i][j];
		cout<<bfs()<<'\n';
	}
	return 0;
}

代码注意点:

多组测试用例,要注意清空初始化的操作

for(int i=1;i<=n;i++)

for(int j=1;j<=m;j++)

{

vstij = false;

dpij = 0;

}


代码错误原因:

需要注意的是,动态规划需要满足无后效性,即后续的状态只能依赖当前状态继续往后,不能因为前面的状态修改了,后续的状态也跟着修改了

错误代码使用了bfs+动态规划的办法,假设结点一本来20的,对该节点的后续结点完成全部操作后,又被某一结点改为了30,那么最大值结果就乱套了


简单题简单做,可以通过标粗的地方看该题的解法。

  1. 面积为奇数的n*m矩阵,即 n*m = 奇数
  2. 走过的方格不能再走,则可以使用黑白染色规则来判断走到终点的步数奇偶性

证明:

color=(x+y)mod2,color = 0 为黑色(起点为黑色),color = 1 为白色

n*m = 奇数,则说明 n奇数m奇数,所以 n+m = 偶数,即 color = 0 为黑色

则需要奇数步才能达到终点,例如 黑->白->黑(起点也算1步,即 k = 1开始往后算)

则可得到哈密顿路径存在,可以遍历完整个棋盘,不会有点剩余下来

  • 哈密顿路径:当起点终点颜色一致时,则代表可以遍历整个图,反之不可

或运算性质:越多数参加到或运算最后结果就越大,因为不管哪一位只要有个1,就恒定有1

根据该性质+哈密顿路径存在,得到把所有数参加到或运算,就能得到结果

ac代码:

cpp 复制代码
#include<iostream>
#define int long long
using namespace std;

signed main()
{
	int T;cin>>T;
	while(T--)
	{
		int n,m;cin>>n>>m;
		int ret = 0;
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				{
					int in;cin>>in;
					ret|=in;
				}
		cout<<ret<<endl;
	}
	return 0;
}

2.小苯的好数

超时+错误代码:

cpp 复制代码
#include<iostream>
#define int long long
using namespace std;

const int N = 2e5 + 10;
int a[N];//统计每个数是否为好数 

bool flag(int num)
{
	//函数用来判断是否一个奇数的因子为奇数 
	int sum = 1;//1肯定是因子,且不参加到循环中
	for(int i=2;i*i<=num;i++)
	{
		if(num%i==0)
		{
			//说明此时可以除尽
			sum+=i;
			int another = num/i;//除尽时,还有一个大的数可以作为因子
			if(another!=i) sum+=another;	
		}	
	} 
	if(sum%2) return false;
	else return true;
}

signed main()
{
	int T;cin>>T; 
	while(T--)
	{
		int n,q;cin>>n>>q;
		for(int i=1;i<=n;i++)
		{
			int in;cin>>in;
			if(in%2==0)//为偶数时,必为好数 
				a[i] = 1;
			else//为奇数时,只要因子和不为奇数,即为好数 
				if(flag(in)) a[i] = 1; 
		}
		for(int i=1;i<=n;i++) a[i] = a[i] + a[i-1];
		while(q--)
		{
			int l,r;cin>>l>>r;
			cout<<(a[r]-a[l-1])<<endl;
		}
	}
	return 0;
}
  • 超时原因:flag判断最多要 1e3 次,最多要进入flag函数 2e5 次,6e8 次的时间开销肯定会超时
  • 错误原因:没有初始化数组,上一次遗留的数组元素会影响到当前判断,所以要重新对数组进行初始化
  • 解决办法:memset和isGood函数( O(1)判断完全平方数)

isGood函数如何得来?

当 x 为奇数时,只有完全平方数才能是好数。(完全平方数开完平方后依旧是一个整数的数字)

证明:

x 为奇数时,x = a*a 由于 a 必然是一个奇数(奇数*奇数才会为奇数),所以 a+1 为偶数

此时无论该完全平方数还能被拆分成什么数,都是两个奇数的状态,而 奇数+奇数 = 偶数

偶数 + 偶数 = 偶数 -> 偶数*奇数 = 好数

x为偶数时,由于无论怎么拆,都会多出来一个 1 没有数字和他配对,所以不能是好数

ac代码:

cpp 复制代码
#include<iostream>
#include<cmath>
#include<cstring>
#define int long long
using namespace std;

const int N = 2e5 + 10;
int a[N];//统计每个数是否为好数 

bool isGood(int x)
{
    int sq=sqrt(x);
    return sq*sq==x;
}

signed main()
{
	int T;cin>>T; 
	while(T--)
	{
		memset(a,0,sizeof(a));
		int n,q;cin>>n>>q;
		for(int i=1;i<=n;i++)
		{
			int in;cin>>in;
			if(in%2==0)//为偶数时,必为好数 
				a[i] = 1;
			else//为奇数时,只要因子和不为奇数,即为好数 
				if(isGood(in)) a[i] = 1; 
		}
		for(int i=1;i<=n;i++) a[i] = a[i] + a[i-1];
		while(q--)
		{
			int l,r;cin>>l>>r;
			cout<<(a[r]-a[l-1])<<endl;
		}
	}
	return 0;
}

3.小苯的ovo

什么是不相交?

ovo 是长度 = 3 的连续三个字符,不相交 = 各个选中的 ovo 片段不能共用任何下标位置

所以题目准确的表述是 不相交的ovo子串个数 ,看到题目的数据量不难想到用动态规划

  1. dpij:在前 i 个字符中找,获取 j 个 ovo 所需要的操作数量
  2. 不新增一个 ovo ,dpij = dpi-1j ;新增一个 ovo ,dpij = dpi-3j-1 + cost。此处的cost为从 i-3 -> i 如果要创造出一个ovo所需要的最少操作次数,这样天然解决了不相交的问题
  3. 初始化时,dp00 = 0,同时需要把无法填表的地方都设置为正无穷,要不然都为0的话就会出现问题
  4. 从左往右进行填表
  5. 返回结果时,遍历 dpnj 当中的 j ,第一次 j <= k 时表中存放的结果即为最终答案

ac代码:

cpp 复制代码
#include<iostream>
#include<cstring>
using namespace std;

const int N = 5010,max_j = 1670;
int dp[N][max_j];

signed main()
{
	int T;cin>>T;
	while(T--)
	{
		memset(dp,0x3f3f3f3f,sizeof(dp));
        dp[0][0] = 0;
		int n,k;cin>>n>>k;
		string s;cin>>s;
		for(int i=1;i<=n;i++)
			for(int j=0;j<=i/3;j++)
			{
				dp[i][j] = dp[i-1][j];
				if(i>=3&&j>0)
				{
                    int cost = (s[i-3]!='o')+(s[i-2]!='v')+(s[i-1]!='o');
                    dp[i][j] = min(dp[i][j],dp[i-3][j-1]+cost);
                }
			}
		for(int j=n/3;j>=0;j--)
		{
			if(dp[n][j]<=k) 
			{
				cout<<j<<endl;
				break;
			}
		}
	}
	return 0;
}

4.红色和紫色

  • 小红和紫都可以选择染成红色或紫色,而不是只能染一种颜色
  • 对角线位置不算相邻的格子
cpp 复制代码
#include<iostream>
using namespace std;

signed main()
{
	int n,m;cin>>n>>m;
	if((n*m)%2)cout<<"akai";
	else cout<<"yukari";
	return 0;
}

5.kotori和素因子

错误代码:

cpp 复制代码
#include<iostream>
#define int long long
using namespace std;

const int N = 1010;
bool vst[N];//判断是否可以拆分为一个质因子 
int n;

void deprime(int in)
{
	while(in%2==0)
	{
		vst[2] = true;
		in /= 2;
	}
	for(int i=3;i*i<=in;i+=2)
	{
		while(in%i==0)
		{
			vst[i] = true;
			in/=i;
		}
	}
	if(in>1)
		vst[in] = true;
}

signed main()
{
	cin>>n;
	int mx = 2;
	for(int i=1;i<=n;i++)
	{
		int in;cin>>in;
		deprime(in);//获取其质因子
		mx = max(mx,in);
	}
	int cur = n,ret = 0;
	for(int i=2;i<=mx;i++)
	{
		if(!cur) break;
		if(vst[i]) 
		{
			ret+=i;
			cur--;
		}
	}
	if(cur) cout<<-1;
	else cout<<ret;
	return 0;
}
  • 错误原因:没考虑到 a、b 两个因子可能不重合,然后 a 的两个因子被统计到结果里了,但 b 的因子却没有

所以,我们可以通过dfs的方式做这道题

打表+dfs

ac代码:

cpp 复制代码
#include<iostream>
using namespace std;

const int N = 20,max_num = 1010;
int ret = 0x3f3f3f3f;
int a[N],prime[max_num],pos;//prime数组打表,获取1000以下的所有质数 
int n;
bool vst[max_num];//统计哪些质数已经被用过了
int path;//统计某一情况下可以获得的最大值 

bool flag(int x)
{
	for(int i=2;i*i<=x;i++)
		if(x%i==0) return false;
	return true;
}

void init()
{
	for(int i=2;i<=max_num;i++)
	{
		if(flag(i)) prime[++pos] = i;//flag函数用来判断是否为素数,如果是素数那么就放入prime表 
	}
}

void dfs(int pos)
{
	if(pos==n+1)
	{
		ret = min(ret,path);
		return;
	}
	int cur = a[pos];
	//一个个试过去
	for(int i=1;prime[i]<=cur;i++)
	{
		if(!vst[i])
		{
			int p = prime[i];
			if(cur%p==0)
			{
				//说明p是cur的一个因子,即该素数可以整除数组当前数
				vst[i] = true;
				path+=p;
				dfs(pos+1);
				path-=prime[i];
				vst[i]=false;	
			}	
		}	
	} 
}

signed main()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	init();
	dfs(1); 
	cout<<(ret==0x3f3f3f3f?-1:ret);
	return 0;
}
相关推荐
洛水水1 小时前
图床项目实现:注册登录 + 文件上传等功能的完善
网络·c++·mysql·图床
七夜zippoe1 小时前
DolphinDB向量化计算:高性能数据处理
算法·dolphindb
悠仁さん1 小时前
哈夫曼树的简单介绍
算法
bIo7lyA8v1 小时前
算法与数据结构协同优化的设计思想的技术8
数据结构·算法
Lsk_Smion1 小时前
力扣实训 _ [98].验证二叉搜索树 _ 将二叉树展开成链表
数据结构·算法·leetcode
坚果派·白晓明1 小时前
鸿蒙PC三方库使用:使用 AtomCode + Skills 自动完成鸿蒙化三方库11Zip集成
c语言·c++·华为·harmonyos
8Qi81 小时前
LeetCode 377:组合总和 Ⅳ(Combination Sum IV)—— 题解 ✅
算法·leetcode·动态规划·完全背包
凯瑟琳.奥古斯特1 小时前
力扣1002题C++解法详解
开发语言·c++·算法·leetcode·职场和发展
钟灵9211 小时前
C++【模板初阶】
开发语言·c++·笔记·c#