The Third Week
战略上藐视敌人,战术上重视敌人 ———— 毛泽东主席
一、前言
周六打了场cf,只过了俩题而且比较慢,给我的id上个颜色怎么这么费事呢。链接: link
周一队内训练赛,ac俩题速度还行,但是有不少人ac四题,dfs和dp的应用都没有想到,后来都补出来了。链接: link
周二晚上有一场cf,没打,周三早上打的,div2 ac俩题。还没补题。
周三下午河南萌新联赛,签到题特别签到,算法题特别算法,打得不是特别好,速度还行。补题也非常费劲,算法补的我眼花缭乱了已经。链接: link
周五早上队内训练赛,打的非常不好,很多思维题的思路都需要很长时间才能想出来,更尴尬的是在结束后几秒a了一道题,这要是在赛场上得气死,但是思维题也不知道该怎么才能快点想出来。链接: link
周六早上队内训练赛,不想说了已经。补题了还没写题解。
二、算法
1.KMP算法
adbq学了但是不会用,等过俩天再来补这个算法
2.线性DP
问题的最优解包含着它的子问题的最优解。即不管前面的策略如何,此后的策略必须是基于当前状态(由上一次决策产生)的最优决策。(异或,除余都不适用常规动态规划)
- 结合原问题和子问题确定状态:
(1)描述位置的:前(后)i单位,第i到第j单位,坐标为(i,j)第i个之前(后)且必须取第i个等
(2)描述数量的:取i个,不超过i个,至少i个等
(3)描述对后有影响的:状态压缩的,一些特殊的性质 - 确定转移方程:
(1)检查参数是否足够
(2)分情况:最后一次操作的方式,取不取,怎么样取
(3)初始边界是什么
(4)注意无后效性。(比如说,求A求要求B,求B就要求C,求C就要求A,这就不符合无后效性。) - 需不需要优化
- 确定编程实现方式
(1)递推
(2)记忆化搜索
<1>(最长上升子序列 II)
题解:
相比较I,II的数据增大了100倍,所以无法直接采用o(n * n)的算法,而是用了一种o(n * log(n))的算法,贪心➕二分。
数组q[i]储存长度为i的序列的最小的结尾元素,最终输出q的长度即可。
代码:
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<cmath>
#include<queue>
#include<utility>
using namespace std;
#define int long long
const int N = 1e5+10;
int n;
int a[N],q[N];
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int res = 0;
q[++res] = a[1];
//可以简单看出q数组是递增的
for (int i = 2; i <= n; i++) {
if(a[i] > q[res])q[++res] = a[i];
//如果比末尾值大,继续加入即可
else {
int l = 1,r = res;
while(l < r) {
//二分
int mid = (l+r)/2;
if(q[mid] < a[i])l = mid+1;
else r = mid;
}
//找出第一个大于等于a[i]的值
//把那个位置的q[r]变成a[i]
q[r] = a[i];}
}
cout << res << endl;
return 0;
}
3.背包DP
- 01背包
给定n件重量为w,价值为v的物品,问一个可承载m重量的背包最多能获得多少价值,是每件东西的取存问题,所以是01背包
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if(j < w[i]) dp[i][j] = dp[i-1][j]; //放不下这件物品
else dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
//选取放和不放之间的较优态
}
}
- 完全背包
还是n类重量为w,价值为v的物品,这次每个物品可以无限次的取
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
dp[i][j] = dp[i-1][j];
//考虑一下要不要放啦
if(j >= w[i])dp[i][j] = max(dp[i][j],dp[i][j-w[i]]+v[i]);
//直接在当前的第i个物品处考虑需不需要往下继续放
}
}
- 多重背包
每个物品有个数限制
以上,都是在这个视频中学习所得,致谢🙏,点击可进入 - 二维费用的背包问题
背包不仅限制重量,还限制体积 - 分组背包
要从每个组别里取一个放入背包的最大值
<1>(「木」迷雾森林)
这题真的很…哈,代码见吧
题解:
从地图左下角走到地图右上角,只能够向上或向右行走,答案对2333取模,1的地方不能走。
一个比较简单的dp,不过我会先给出一段错误代码。
代码:
这是一段全部都MLE了的代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
//看这里,long long所占的内存是int的俩倍
//题目给出的空间限制是131072k,相比之下比别的题目小非常多
const int mod = 2333;
int m,n;
int a[3100][3100];
//如果把a改成bool数组也可以卡过,因为bool数组只给每个位置俩种情况
int dp[3100][3100];
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> m >> n;
for (int i = 1; i <= m; i++){
for (int j = 1; j <= n; j++) {
cin >> a[i][j];
}
}
dp[m][1] = 1;
for (int i = m; i >= 1; i--) {
for (int j = 1; j <= n; j++) {
if(i == m && j == 1)continue;
dp[i][j] = (dp[i][j-1] + dp[i+1][j])%mod;
if(a[i][j] == 1)dp[i][j] = 0;
}
}
// for (int i = 1; i <= m; i++) {
// for (int j= 1; j <= n; j++){
// cout << dp[i][j] << ' ';
// }cout << endl;
// }
cout << dp[1][n];
return 0;
}
//这是一段面目全非的ac代码
#include<bits/stdc++.h>
using namespace std;
const int N = 3100;
int a[N][N];
int dp[N][N];
const int mod = 2333;
template<class T>inline void read(T &res)
{
char c;T flag=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
while((c=getchar())>='0'&&c<='9')res=res*10+c-'0';res*=flag;
}
signed main() {
int m,n;
read(m);
read(n);
for (int i = 1; i <= m; i++){
for (int j = 1; j <= n; j++) {
read(a[i][j]);
}
}
dp[m][0] = 1;
for (int i = m; i >= 1; i--) {
for (int j = 1; j <= n; j++) {
if(!a[i][j])dp[i][j] = (dp[i][j-1] + dp[i+1][j])%mod;
}
}
printf("%d",dp[1][n]);
return 0;
}
4. 其它
<1>(Ubiquity)
题解:
给定一个数字n,问如果有一个包含n个数字的数列a,每个数字只能是0——9,必须存在一个0和一个9的方案数有多少,输出方案数mod1e9+7的结果。
方法比较简单,每个位置都有可能有十个数字,也就是10的n次方种方案,其中没有0的方案数有9的n次方,没有9的也是9的n次方,即没有0又没有9的是8的n次方种。
注意:有可能是极小的负数,小于-1e9-7
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mod 1000000007
ll n;
ll ksm(ll a, ll b) {
ll ans = 1;
while (b)
{
if (b & 1)ans = (ans * a) % mod;
a = (a * a) % mod;
b >>= 1;
}
return ans%mod;
}
signed main() {
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> n;
if(n < 2) {
cout << 0;
return 0;
}
ll res = ((ksm(10ll,n)-2*ksm(9ll,n)+ksm(8ll,n))%mod+mod)%mod;
//如果是非常小的负数,直接加上mod可能还是负数,所以要先mod再加再mod,之后也要注意
cout << res;
return 0;
}
三、总结
记忆化搜索就是将前面计算过的标记一下,不用反复计算同一个数字,如f(6)=f(2)+f(4),f(5)=f(2)+f(3),f(2)就会对它进行俩次递归,可以直接记录它的值然后调用。
MLE要注意#define int long long,还可以将int数组改为bool数组
mod的时候要注意是负数的情况,得先mod再+mod再mod,因为会有负数小于-2mod的情况。
为什么这周的题这么少呢…因为我题单做的不多,这周一直在补题…而且也没补完…加上dp确实不太会所以每道题都得用一段时间去做,尽量这俩天抽时间先把题都补完吧