第四周:
第一题 A-B数对:
这题怎么写呢??暴力肯定是不行的。本来以为要用双指针的策略求解,实则不然,这题就是之前做过某一题的弱化版,map存放每个数的个数,然后从1到最大值一直枚举B的情况,每次都让ans+=map[i]*map[i+c]就可以了。循环2^30看起来有点多,但是事实上也能过(悬),如果想要加快速率使用unordered_map要好,因为哈希表读取数据时常量复杂度,而map内部使用类似二叉树的存储结构需要O(logn)读取数据。
#include <bits/stdc++.h>
using namespace std;
int n, c;
map<int, int> cnt;
int ans, mmax;
int main(void)
{
cin >> n >> c;
vector<int> arr(n+1);
for(int i = 1; i <= n; ++i)
{
cin >> arr[i];
mmax = max(mmax, arr[i]);
cnt[arr[i]]++;
}
int i = 1;
while(i+c <= mmax)
{
ans += cnt[i]*cnt[i+c];
++i;
}
cout << ans;
return 0;
}
第二题 数位计算
大数模拟,事实上只需要对每一次的操作求余就可以了,本来想用python自带高精度数来模拟,但是事实上这样容易错,至于为什么就不明白了。雷区:每一次加,减,乘操作都需要进行取余,否则就会出现溢出或者结果不正确。这里主要用到前n项和公式,不能暴力的模拟要不然会TLE
//#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MOD = 998244353;
signed main()
{
int n;
cin >> n;
int sum = 0;
for(int i = 1; i <= n; i*=10)
{
if(i <= n/10)
{
int a = (9*i)%MOD, b = (9*i+1)%MOD;
sum = sum+(a*b/2)%MOD;
sum %= MOD;
}
else
{
int a = (n-i+1)%MOD, b = (n-i+2)%MOD;
sum = sum+(a*b/2)%MOD;
sum %= MOD;
}
}
cout << sum%MOD;
return 0;
}
第三题 新国王游戏
主要用到贪心策略,但是不能瞎做。做贪心类题如果无从下手那么就考虑只有两个数的情况,再证明能够由此递推到更多数的情况即可。仅凭直觉时不一定可以的。然后数据量比较大为了避免卡输入输出可以用快读快写的板子。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 1E6+6, mod = 1000000007;
struct person
{
int l, r;
} p[maxn];
//像这种问题就考虑两个人的时候怎么划算
//2个人时假设1和2,1在前面,奖金就是b1*a2+b2
//1在后面就是b2*a1+b1
//前大于后,贪心算法这种题的求解就是要考虑两个的情况。
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int n;
cin >> n;
for(int i = 0; i < n; ++i)
{
cin >> p[i].l >> p[i].r;
}
sort(p, p+n, [](const person &p1, const person &p2)
{
return p1.r*p2.l+p2.r > p2.r*p1.l+p1.r;
});
int sum = 0, mul = 1;
for(int i = n-1; i >= 0; --i)
{
sum = sum+(mul*p[i].r)%mod;
sum %= mod;
mul *= p[i].l;
mul %= mod;
}
cout << sum;
return 0;
}
第四题 完美数
这题就比较难了,主要是对于排列组合问题的求解,但是可惜的是数据太大了,除法操作的取模是相当难的。这里利用求幂的方法来求逆元(离散数学没好好学的下场),逆元的求解方法为本元的MOD-2次方,至于原理我就不明白了。然后就是要用到快速幂的方法了。由于需要取模所以不能用到pow的方法。
#include <bits/stdc++.h>
#define int long long
using namespace std;
//目前有点bug
int a, b, m, ans;
const signed MOD = 1E9+7, N = 1E6+6;
int fac[N] = {1}, infac[N] = {1};
//可以用dp的办法来直接求阶乘
//An = n!, Amn = n!/m!,Cmn = n!/m!/(n-m)!
int qmi(int a, int b)
{
int res = 1;
while (b)
{
if (b & 1)
res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
//dp求阶乘和反向阶乘
void dp()
{
fac[0] = infac[0] = 1;
for(int i = 1; i < N; ++i) //乘以下
{
fac[i] = fac[i-1]*i%MOD;
}
infac[N-1] = qmi(fac[N-1], MOD-2);
for(int i = N-2; i; --i)
{
infac[i] = infac[i+1]*(i+1)%MOD;
}
}
int c(int a, int b) //a比b大
{
return fac[a]*infac[b]%MOD*infac[a-b]%MOD;
}
signed main()
{
cin >> a >> b >> m;
dp();
for(int i = 0; i <= m; ++i)
{
int now = i*a+(m-i)*b, flag = 1;
while(now)
{
int p = now%10;
if(p != a && p != b)
{
flag = 0;
break;
}
now /= 10;
}
if(flag)
{
ans = (ans+c(m, i))%MOD;
}
}
cout << ans%MOD << endl;
return 0;
}
第五题 lusir的游戏
这题主要涉及二分答案的策略,还有个坑点就是如果每次都获得能量值,那么能量值的增长速度将会很快,建筑物高度均为0的时候会指数倍增,即使开long long也不管用。策略就是能量值大于高度的最大范围就直接判取胜。(有时候大思路有了却败在小细节上面)
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1E5+5;
int n, h[MAXN], ans = MAXN;
signed check(int now)
{
for(int i = 1; i <= n; ++i)
{
if(h[i] > now)
{
now -= h[i]-now;
if(now < 0)
{
return 0;
}
}
else
{
now += now-h[i]; //能量值可能会指数倍增爆longlong
if(now >= MAXN) //因此如果now非常大了就直接win
{
return 1;
}
}
}
return 1;
}
signed main()
{
cin >> n;
for(int i = 1; i <= n; ++i)
{
cin >> h[i];
}
//妥妥的二分答案题
int l = 0, r = MAXN, mid;
while(l <= r)
{
mid = l+(r-l)/2;
if(check(mid))
{
ans = min(ans, mid); //往小的方向找
r = mid-1;
}
else
{
l = mid+1; //往大的方向找
}
}
cout << ans << endl;
return 0;
}
第六题 BFS练习1
正如这题,我本来以为直接用bfs暴力模拟是一种不好的策略,但是确实能做出来。原理就是bfs是广度优先遍历,所以已经搜索到的数据的深度<=没有搜索到的数据深度。因此可以用vis数组来存放数据是否已被访问到。vis[x]=1就没必要访问了,因为得不到更好的答案。同样的题目dfs却无法做到。
#include <bits/stdc++.h>
using namespace std;
int a, q;
const int MAX = 1E5+5; //数据范围最大是MAX-1
int f[MAX], vis[MAX];
typedef pair<int, int> my;
//这题是树的存储结构,参数是起始点和数据规模
void bfs(int s)
{
queue<my> q; //一个队列
q.push({s, 0}); //起始点
while(!q.empty())
{
int n = q.front().first, d = q.front().second;
q.pop();
if(!vis[n])
{
vis[n] = 1;
f[n] = d;
if(n+1 < MAX && !vis[n+1])
{
q.push({n+1, d+1});
}
if(n-1 >= 0 && !vis[n-1])
{
q.push({n-1, d+1});
}
if(2*n < MAX && !vis[2*n])
{
q.push({2*n, d+1});
}
if(3*n < MAX && !vis[3*n])
{
q.push({3*n, d+1});
}
}
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> a >> q;
bfs(a);
int flag = 1;
while(q--)
{
int n;
cin >> n;
if(flag)
{
flag = 0;
}
else
{
cout << ' ';
}
cout << f[n];
}
return 0;
}
第七题 01序列2
什么,这个题非常简单?emmm,其实还是排列组合问题。可以枚举1出现的个数。然后在剩下n-(i-1)*k个未被连续的0占满的位置中选择i个位置放置1,剩下的位置放置0即可。连续的0放到哪里没有影响。代码实现就是板子,但是比较难想。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MOD = 1E9+7, MX = 1E6+6;
int n, k;
int f[MX], inf[MX];
//传入的参数是MOD-2
int qmi(int a, int b)
{
int res = 1;
while(b)
{
if(b&1)
{
res = res*a%MOD;
}
a = a*a%MOD;
b >>= 1;
}
return res;
}
void init()
{
f[0] = inf[0] = 1; //0和1的阶乘
for(int i = 1; i < MX; ++i)
{
f[i] = f[i-1]*i%MOD;
}
inf[MX-1] = qmi(f[MX-1], MOD-2);
for(int i = MX-2; i ; --i)
{
inf[i] = inf[i+1]*(i+1)%MOD;
}
}
int C(int a, int b) //a>b
{
return f[a]*inf[b]%MOD*inf[a-b]%MOD;
}
signed main()
{
cin >> n >> k;
init();
int ans = 1;
for(int i = 1; i <= n-(i-1)*k; ++i)
{
ans = (ans+C(n-(i-1)*k, i))%MOD;
}
cout << ans << endl;
return 0;
}
第八题 整数光棍
这题就比较适合用python来做了,直接秒杀。
#python直接秒
x = eval(input()) #
b = n = 1
while (1):
if (b % x == 0):
print(int(b // x), n)
break
b = b*10+1
n = n+1
第九题 碰撞2
这就是纯模拟题了,遍历每一层(y高度)然后寻找能碰撞的点就可以了,可以用二维数组,也可以用一维数组+按y坐标排序。
#include <bits/stdc++.h>
using namespace std;
const int MX = 2E5+5;
struct person{
int x, y; //坐标
int dir;
};
int n;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
vector<person> arr(n+1);
for(int i = 1; i <= n; ++i)
{
cin >> arr[i].x >> arr[i].y;
}
string str;
cin >> str;
for(int i = 0; i < n; ++i)
{
arr[i+1].dir = (str[i]=='L'? -1:1);
}
sort(arr.begin()+1, arr.end(), [](const person& p1, const person& p2){
return p1.y<p2.y;
}); //定义排序规则
int mmin = INT_MAX, mmax = INT_MIN, now = arr[1].y;
for(int i = 1; i <= n; ++i)
{
if(arr[i].y != now)
{
if(mmin < mmax)
{
cout << "Yes" << endl;
return 0;
}
mmin = INT_MAX;
mmax = INT_MIN;
now = arr[i].y;
}
if(arr[i].dir == 1) //右走
{
mmin = min(mmin, arr[i].x);
}
else
{
mmax = max(mmax, arr[i].x);
}
}
if(mmin < mmax)
{
cout << "Yes" << endl;
}
else
{
cout << "No" << endl;
}
return 0;
}
第十题 优美,最长上升子序列
动态规划dp,但是需要时间复杂度O(nlogn),第一次做看错题了。每次找一个元素然后遍历他的下标倍数的元素就行。这样时间复杂度比O(n)快,也能符合要求。
#include <bits/stdc++.h>
using namespace std;
int t;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> t;
while(t--)
{
int n, ans = 1;
cin >> n;
vector<int> arr(n+1), dp(n+1, 1);
for(int i = 1; i <= n; ++i)
{
cin >> arr[i];
}
for(int i = 1; i <= n; ++i)
{
for(int j = 2*i; j <= n; j+=i)
{
if(arr[j] > arr[i])
{
dp[j] = max(dp[j], dp[i]+1);
ans = max(ans, dp[j]);
}
}
}
cout << ans << endl;
}
return 0;
}