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问题
我们可以把该题分为两个模块:
- 如何通过给定的数组,算出得到最大公约数 g
- 如何根据 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;
}