SMU Summer 2024 div2 3rd


The Third Week

战略上藐视敌人,战术上重视敌人 ———— 毛泽东主席

一、前言

周六打了场cf,只过了俩题而且比较慢,给我的id上个颜色怎么这么费事呢。链接: link
周一队内训练赛,ac俩题速度还行,但是有不少人ac四题,dfs和dp的应用都没有想到,后来都补出来了。链接: link
周二晚上有一场cf,没打,周三早上打的,div2 ac俩题。还没补题。
周三下午河南萌新联赛,签到题特别签到,算法题特别算法,打得不是特别好,速度还行。补题也非常费劲,算法补的我眼花缭乱了已经。链接: link
周五早上队内训练赛,打的非常不好,很多思维题的思路都需要很长时间才能想出来,更尴尬的是在结束后几秒a了一道题,这要是在赛场上得气死,但是思维题也不知道该怎么才能快点想出来。链接: link
周六早上队内训练赛,不想说了已经。补题了还没写题解。


二、算法

1.KMP算法

adbq学了但是不会用,等过俩天再来补这个算法

2.线性DP

问题的最优解包含着它的子问题的最优解。即不管前面的策略如何,此后的策略必须是基于当前状态(由上一次决策产生)的最优决策。(异或,除余都不适用常规动态规划)

  1. 结合原问题和子问题确定状态:
    (1)描述位置的:前(后)i单位,第i到第j单位,坐标为(i,j)第i个之前(后)且必须取第i个等
    (2)描述数量的:取i个,不超过i个,至少i个等
    (3)描述对后有影响的:状态压缩的,一些特殊的性质
  2. 确定转移方程:
    (1)检查参数是否足够
    (2)分情况:最后一次操作的方式,取不取,怎么样取
    (3)初始边界是什么
    (4)注意无后效性。(比如说,求A求要求B,求B就要求C,求C就要求A,这就不符合无后效性。)
  3. 需不需要优化
  4. 确定编程实现方式
    (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

  1. 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]);
           //选取放和不放之间的较优态
       }
   }
  1. 完全背包
    还是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. 多重背包
    每个物品有个数限制
    
    
    以上,都是在这个视频中学习所得,致谢🙏,点击可进入
  2. 二维费用的背包问题
    背包不仅限制重量,还限制体积
  3. 分组背包
    要从每个组别里取一个放入背包的最大值

<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确实不太会所以每道题都得用一段时间去做,尽量这俩天抽时间先把题都补完吧

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值