1.算法板块
今天结束了区间dp的学习,明天开始学习树形dp。
该题本身的思路很常规,难点在于高精度,这里偷了个懒用了int128也能过。
#define first x
#define second y
#include<bits/stdc++.h>
using namespace std;
using i128 = __int128;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll , ll> pii;
typedef pair <int , char> pic;
const int N = 105,inf = 0x3f3f3f3f,mod = 1e9 + 7;
inline i128 read()
{
i128 x = 0;
char c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') {
x = x * 10 + c - '0';
c = getchar();
}
return x;
}
inline void print(i128 x)
{
if (x > 9) print(x / 10);
putchar(x % 10 + '0');
}
i128 dp[N][N];
int n;
i128 a[N];
int main()
{
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
n = read();
for(int i = 1 ; i <= n ; i++)
{
a[i] = read();
}
for(int len = 3 ; len <= n ; len++)
{
for(int i = 1 ; i + len - 1 <= n ; i++)
{
int l = i, r = i + len - 1;
dp[l][r] = 1e30;
for(int k = l ; k <= r ; k++)
{
dp[l][r] = min(dp[l][r] , dp[l][k] + dp[k][r] + a[l] * a[k] * a[r]);
}
}
}
print(dp[1][n]);
}
2.例题2P1436 棋盘分割 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
该题需要进行棋盘的分割,每次要么横切要么竖切,对于每次切割之后的状态表示,我们是通过记录左上角的坐标以及右下角的坐标就可以描述出一片区域,而且该题是只在切割之后一分为二的两片区域中选择一片进行接下来的操作,那么我们就可以只考虑其中一片区域,这里的区间表示不再是线段而是面积的意义,如果写成for循环会非常麻烦因此使用记忆化搜索,区间dp的思想进行进行解题,记忆化搜索中我们需要将f数组初始化为-1表示该状态还没有被表示过,若该状态已经有值则可以直接返回该值,防止重复运算,该题的状态表示的含义是第i次切割处理左上角坐标为x1,y1,右下角坐标为w2,y2的区域,因为要频繁使用到区间和,因此可以使用前缀和优化,状态转移则是每次对于枚举的面积我们将选择继续操作的区域继续递归而另一片区域则直接计算得分即可。
#define first x
#define second y
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll , ll> pii;
typedef pair <int , char> pic;
const int N = 10,M = 20,inf = 0x3f3f3f3f,mod = 1e9 + 7;
ll f[N][N][N][N][M];
ll a[N][N],s[N][N];
int n;
ll get(int x1,int y1,int x2,int y2)
{
ll sum = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
return sum * sum;
}
ll dp(int x1,int y1,int x2,int y2,int k)
{
ll &v = f[x1][y1][x2][y2][k];
if(v != -1)
{
return v;
}
if(k == 1)
{
v = get(x1 , y1 , x2 , y2);
return v;
}
v = 1e18;
for(int i = x1 ; i < x2 ; i++)
{
v = min(v , get(x1 , y1 , i , y2) + dp(i + 1 , y1 , x2 , y2 , k - 1));
v = min(v , get(i + 1 , y1 , x2 , y2) + dp(x1 , y1 , i , y2 , k - 1));
}
for(int i = y1 ; i < y2 ; i++)
{
v = min(v , get(x1 , y1 , x2 , i) + dp(x1 , i + 1 , x2 , y2 , k - 1));
v = min(v , get(x1 , i + 1 , x2 , y2) + dp(x1 , y1 , x2 , i , k - 1));
}
return v;
}
int main()
{
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n;
for(int i = 1 ; i <= 8 ; i++)
{
for(int j = 1 ; j <= 8 ; j++)
{
cin>>a[i][j];
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
}
}
memset(f , -1 , sizeof f);
cout<<dp(1 , 1 , 8 , 8 , n);
}
例题3.D-切割 01 串 2.0_牛客小白月赛98 (nowcoder.com)
也是典型的区间dp,我们状态表示就是[l ~ r]最多可以进行的操作次数,预处理出前i个字符中0和1的数量,在状态转移的时候只用判断是否符合转移条件后即可进行转移,这里给出区间dp的一般做法和记忆化搜索的做法
#define first x
#define second y
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll , ll> pii;
typedef pair <int , char> pic;
const int N = 505,inf = 0x3f3f3f3f,mod = 1e9 + 7;
ll n,L,R;
ll dp[N][N];
string s;
ll cnt0[N],cnt1[N];
int main()
{
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>L>>R>>s;
s = " " + s;
for(int i = 1 ; i <= n ; i++)
{
if(s[i] == '1')
{
cnt1[i] = 1;
}else
{
cnt0[i] = 1;
}
}
for(int i = 1 ; i <= n ; i++)
{
cnt0[i] += cnt0[i - 1];
cnt1[i] += cnt1[i - 1];
}
for(int len = 1 ; len <= n ; len++)
{
for(int i = 1 ; i + len - 1 <= n ; i++)
{
int l = i,r = l + len - 1;
for(int k = l ; k < r ; k++)
{
int c0 = cnt0[k] - cnt0[l - 1],c1 = cnt1[r] - cnt1[k];
if(abs(c0 - c1) >= L && abs(c0 - c1) <= R)
{
dp[l][r] = max(dp[l][r] , dp[l][k] + dp[k + 1][r] + 1);
}
}
}
}
cout<<dp[1][n];
}
#define first x
#define second y
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll , ll> pii;
typedef pair <int , char> pic;
const int N = 505,inf = 0x3f3f3f3f,mod = 1e9 + 7;
int n,L,R;
int f[N][N];
string s;
int cnt0[N],cnt1[N];
int dp(int l,int r)
{
if(f[l][r] != -1)
{
return f[l][r];
}
for(int k = l ; k < r ; k++)
{
int c0 = cnt0[k] - cnt0[l - 1],c1 = cnt1[r] - cnt1[k];
if(abs(c0 - c1) >= L && abs(c0 - c1) <= R)
{
f[l][r] = max(f[l][r] , dp(l , k) + dp(k + 1 , r) + 1);
}
}
return f[l][r];
}
int main()
{
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>L>>R>>s;
s = " " + s;
for(int i = 1 ; i <= n ; i++)
{
if(s[i] == '1')
{
cnt1[i] = 1;
}else
{
cnt0[i] = 1;
}
}
for(int i = 1 ; i <= n ; i++)
{
cnt0[i] += cnt0[i - 1];
cnt1[i] += cnt1[i - 1];
}
memset(f , -1 , sizeof f);
cout<<dp(1 , n);
}
例题4.Attachments - The 2024 CCPC Online Contest - Codeforces
本次ccpc网络预选赛的d题,最难想到的是它的状态表示,dp[i][l][r]表示的含义是t[l ~ r]在si出现的次数,我们考虑两种情况,1.si拓展的时候新加入的si不是t的某一个字符,这时我们只需要考虑si-1中与t[l ~ k]匹配的数量以及si-1与t[k + 1 ~ r]匹配的数量,两者相乘即可,第二种情况就是s[i] == t[k]的时候,这时候的数量是si-1中与t[l ~ k - 1]匹配的数量以及si-1与t[k + 1 ~ r]匹配的数量,并且因为中间都是乘法运算,因此我们要将所有无解的情况初始化为1,例如dp[i][10][1]等不合理情况
#define first x
#define second y
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll , ll> pii;
typedef pair <int , char> pic;
const int N = 105,inf = 0x3f3f3f3f,mod = 998244353;
ll dp[N][N][N]; //dp[i][l][r] t[l ~ r]在si中出现的次数
string s,t;
int main()
{
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>s>>t;
int n = s.size(),m = t.size();
s = " " + s,t = " " + t;
for(int i = 0 ; i <= n ; i++)
{
for(int j = 0 ; j <= m + 1 ; j++)
{
for(int k = 0 ; k < j ; k++)
{
dp[i][j][k] = 1;
}
}
}
for(int i = 1 ; i <= n ; i++)
{
for(int len = 1 ; len <= m ; len++)
{
for(int l = 1 ; l + len - 1 <= m ; l++)
{
int r = l + len - 1;
for(int k = l - 1 ; k <= r ; k++)
{
dp[i][l][r] = (dp[i][l][r] + dp[i - 1][l][k] * dp[i - 1][k + 1][r]) % mod;
if(s[i] == t[k + 1] && k + 1 <= r)
{
dp[i][l][r] = (dp[i][l][r] + dp[i - 1][l][k] * dp[i - 1][k + 2][r]) % mod;
}
}
}
}
}
cout<<dp[n][1][m];
}
2.web板块
学习完了各种选择器,其中需要着重记忆的是类,id,标签选择器,其他选择器也要注意
div::first-letter {
color: red;
}
div::selection {
color: yellow;
background-color: purple;
}
/*伪元素选择器*/
描述颜色的时候可以用rgb表示,rgb(红色 , 绿色 , 蓝色 , 透明度(可省略))