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

1.商店

解题重点1:琴生不等式,总和固定时,变量越平均,函数值总和越小

解题重点2:快速幂


1.当 m 固定时,变量越平均,函数值总和就会越小(琴生不等式),m/n 表示把m拆分成n份(最多只能拆分成n份,得出的结果即为每份重量)

2.当 n >= m 时,m / n = 0,此时必须要把每份重量视作1,即表示用m份重量为1的物品

3.当 n < m 时,m / n = 每份重量(记作p),还剩下 m - np = q 没有分配(此处的q为m%p的结果)。那么就从原来分配好的 n 件商品中,挑出 q 件并让每件的重量 + 1 即可,原来的 n - q 件商品不变。

写成公式的话即为:p*(n-q) + (p+1)*q = m

4.最后的价格即为:2^p*(n-q) + 2^(p+1)*q,即 2^q 价格的 (n-q) 件,2^q 价格的 q 件

解题代码:

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

const int P = 1e9 + 7;
int n,m;

int qpow(int a,int b)
{
	int ret = 1;
	while(b)
	{
		if(b&1) ret = ret*a%P;
		a = a*a % P;
		b>>=1;
	}
	return ret;
}

signed main()
{
	int T;cin>>T;
	while(T--)
	{
		cin>>n>>m;
		//分类讨论先
		int ans = 0;
		if(n>=m)
		{
			ans = (2*m)%P;
		} 
		else
		{
			int p = m/n;
			int q = m%n;
			ans = ((qpow(2,p)*(n-q))%P + (qpow(2,p+1)*q)%P)%P;
		}
		cout<<ans<<endl;
	}
	return 0;
}

2.可恶的墨水

我的错误代码:

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

const int N = 2e5 + 10;
int a[N];
int n;

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int T;cin>>T;
	while(T--)
	{
		cin>>n;
		for(int i=1;i<=n-1;i++)cin>>a[i];
		int left,right;
		left = 1;
		int ret = 1;
		int flag = 0;
		for(right = 2;right<=n;right++)
		{
			if((a[right-1]+1)==a[right])
			{
				ret = max(ret,right-left+1+flag);
				continue;
			}
			else
			{
				//没用
				if(!flag&&(a[right]==(a[right-1]+2))) 
				{
					flag = 1;
					ret = max(ret,right-left+1+flag);//此处需要+2,因为除了right与right-1位置的2个元素外,还要统计上被删除的 
					continue;
				}
				else //用了的情况,或者两者不满足条件 
				{
					//没用,我们可以把它用掉 
					if(!flag) ret = max(ret,right-left+1); 
					left = right;
					flag = 0;
				}
			}
		}
		cout<<ret<<endl;
	}
	return 0;
}

1.后面又遇到一个非法差,我直接 left=right,会把前面已经用掉的那个 "2 差" 窗口完全丢弃,但2差窗口还是有值是满足条件的,这些满足条件的应该也在下一次寻找"2差"窗口时被统计上。所以正确的做法是把 left 移到上一次出现 2 差的下一个位置,而不是直接移到当前位置。

2.flag 只标记了 "是否用过一次缺失",但没有记录缺失发生的位置,需要标记缺失发生的位置,然后下一次不满足条件时,让left跳到原缺失发生的位置,让flag跳到当前缺失的位置(需要判断缺失位置是否相差为2)

ac代码:

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

const int N = 2e5 + 10;
int a[N];

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int T; cin >> T;
    while (T--) {
        int n; cin >> n;
        for (int i = 1; i <= n - 1; ++i) cin >> a[i];
        if (n == 2) {
            cout << "2\n";
            continue;
        }
        int left = 1, bad = -1, ans = 1;
        for (int right = 2; right <= n - 1; ++right) {
            int diff = a[right] - a[right - 1];
            if (diff != 1) {
                if (diff == 2) {
                    if (bad != -1) {
                        left = bad + 1;
                    }
                    bad = right - 1;
                } else {
                    left = right;
                    bad = -1;
                }
            }
            int len = right - left + 1;
            ans = max(ans, min(len + 1, n));
        }
        cout << ans << '\n';
    }
    return 0;
}

当diff不为连续的时候,进行处理

1.diff == 2 时

bad是指填充2的位置,当已经有个位置了(不再为-1时),那么让left变为填充位置+1

若依据处于未填充状态,赋予其一个位置

该步操作相当于让填充2的位置改变,所以bad = right - 1 不需要if语句

2.diff !=2 时

说明无法填充,让left直接跳到right,同时窗口清空了,所以bad = -1

3.统计长度时,默认对窗口+1,即使是[2,3]依然+1,相当于在窗口的首部or尾部添加了一个消失的元素

3.缝缝补补

如下图所示,当 x (即初始卡牌点数) 间于数组所有元素的 最小最大值之间,无论其怎么变化,先得复制 (n-1) 张牌;然后假设有两张 x - m 的牌,那我只要减m减一次,就可以有两张 x - m 的牌;我最小要减到min,最大要增加到max,并且减到 min、max的过程中,形如 x - m 的牌也都被考虑进来了,所以有公式 (n-1)+(max-min)

但也得考虑到,x < min 以及 x > max 的情况,所以两者赋初值时应该为x

ac代码:

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

signed main()
{
	int T;cin>>T;
	while(T--)
	{
		int n,x;cin>>n>>x;
		int small = x,large = x;
		for(int i=1;i<=n;i++)
		{
			int in;cin>>in;
			large = max(large,in);
			small = min(small,in);
		}
		cout<<large-small+n-1<<endl;
	}	
	return 0;
}

4.一道GCD问题

我们可以把该题分为两个模块:

  1. 如何通过给定的数组,算出得到最大公约数 g
  2. 如何根据 g 或者 直接 求出k

对于问题1,我们不难发现

当 g | (ai + k) 且 g | (aj + k) 时, g | (ai+k) - (aj+k) = g | (ai-aj)

前者为mg,后者为ng,最后两者相减为 (m-n)g,依旧可以被 g 整除

所以能得出重要性质:g 这个最大公约数,可以整除数组中任意两个数的差值

同时,当 g | (ai - a1) 且 g | (aj - a1) 时,同理能够得到 g | ai - a1 - aj + a1 = g | ai - aj

所以只要求出所有数和a1的差值的最大公约数,这个最大公约数就可以作为所有数对的差值的最大公约数

因此,只需要**求出gcd((a2-a1),(a3-a1),(a4-a1),......,(an-a1))**即可


对于问题2,我们此时已经得到了 a1 + k = mg 中的 a1 与 g ,所以可以先去思考是否可以通过这个关系式得到 k

假设 a1 % g = r,那就有 mg + r = a1;又 g | a1 + k ,所以 g | mg + r + k

mg 肯定能够被 g 整除,所以我们现在只需要满足 r + k 可以被g整除即可,所以我们可以得到:

r + k = ng ( n = 1,2,3,......)

当 r = 0 时,a1 可以直接被 g 整除,此时 k = 0

当 r != 0 时,要求 k 最小,那么此时 n 取 1,所以 k = g - r


剩下的注意点:

  • 求 gcd 时,我们要保证差值为正,这样用 gcd 函数才能求出正确的解;所以我们可以排序,让 a1 = 数组最小值
  • gcd 用辗转相除法来求

ac代码:

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

const int N = 1e6 + 10;
int a[N];

int gcd(int a,int b)
{
	return b == 0 ? a : gcd(b,a%b);
}

signed main()
{
	int n;cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	sort(a+1,a+n+1,less<int>());
	int x = gcd(0,a[2]-a[1]);
	for(int i=3;i<=n;i++)
		x = gcd(x,a[i]-a[1]);
	//x即为最大公约数
	int r = a[1] % x;
	if(!r) cout<<x<<" "<<0;
	else cout<<x<<" "<<(x-r); 
	return 0;
}

5.小红蹦跳蹦跳

超时代码:

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

const int N = 1e6 + 7,MOD = 1e9 + 7;
int f[N][2];

signed main()
{
	int n;cin>>n;
	for(int i=1;2*i<=n;i++)
		f[2*i][0] = f[2*i-1][1] = 1;
	if(n%2) f[n][1] = 1;
	//动态规划
	for(int i=3;i<=n;i++)
	{
		for(int k=1;i-(2*k-1)>=1;k++) f[i][1] = (f[i][1]+f[i-(2*k-1)][0])%MOD;	
		for(int k=1;i-2*k>=1;k++) f[i][0] = (f[i][0]+f[i-2*k][1])%MOD;
	} 
	cout<<(f[n][1]+f[n][0])%MOD;
	return 0;
}

以上是我写的动规的超时代码,能看到时间复杂度达到了O(n^2),对于1e6的数据必然会超时,f[i][1] 表示用左脚走奇数级台阶到达 i 级台阶,f[i][0]表示用右脚,即左1右0

此处不难发现,最大的超时部分是 k 的部分,此时假设左脚为 fl 数组,右脚为 fr 数组

fl[i] = fr[i-1] + fr[i-3] + fr[i-5] + ......

fr[i-3] + fr[i-5] + ...... = fl[i-2]

所以 fl[i] = fr[i-1] + fl[i-2] -> f[i][1] = f[i-1][0] + f[i-2][1]

同理可得 fr[i] = fl[i-2] + fr[i-2] -> f[i][0] = f[i-2][0] + f[i-2][1]

这样我们就可以把 k 的循环给去掉了,时间复杂度降到了 O(n),可以通过

ac代码:

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

const int N = 1e6 + 7,MOD = 1e9 + 7;
int f[N][2];

signed main()
{
	int n;cin>>n;
	for(int i=1;2*i<=n;i++)
		f[2*i][0] = f[2*i-1][1] = 1;
	if(n%2) f[n][1] = 1;
	//动态规划
	for(int i=3;i<=n;i++)
	{
		f[i][1] = (f[i-1][0]+f[i-2][1])%MOD;
        f[i][0] = (f[i-2][1]+f[i-2][0])%MOD;
	} 
	cout<<(f[n][0]+f[n][1])%MOD;
	return 0;
}
相关推荐
凌波粒1 小时前
LeetCode--513.找树左下角的值(二叉树)
java·算法·leetcode
子琦啊1 小时前
构造函数、this指向和原型链机制
javascript·算法·贴图
WHS-_-20221 小时前
Millimeter Wave ISAC-SLAM: Framework and RFSoC Prototype
人工智能·算法·原型模式
吃好睡好便好1 小时前
在Matlab中绘制杆状图
开发语言·学习·算法·matlab·信息可视化
带带弟弟学爬虫__1 小时前
dyAPP数据采集-个人主页、发布、搜索、评论
服务器·python·算法·flutter·java-ee·django
桀人1 小时前
C++——内存管理——new和delete的超详细解析
开发语言·c++
Shadow(⊙o⊙)1 小时前
Shell进程替换,自定义Shell解释器——字符串库函数灵活操作!
linux·运维·服务器·开发语言·c++·学习
sali-tec1 小时前
C# 基于OpenCv的视觉工作流-章75-线-线角度
图像处理·人工智能·opencv·算法·计算机视觉
_F_y1 小时前
树形 DP 从入门到进阶:普通树形DP、树形背包、换根DP
c++·动态规划