宇宙第一小正太\(^o^)/~萌量爆表求带飞=≡Σ((( つ^o^)つ~ dalao们点个关注呗~
试题:扩散
题目描述:
本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。
小蓝在一张无限大的特殊画布上作画。
这张画布可以看成一个方格图,每个格子可以用一个二维的整数坐标表示。
小蓝在画布上首先点了一下几个点:(0,0),(2020,11),(11,14),(2000,2000)。
只有这几个格子上有黑色,其它位置都是白色的。
每过一分钟,黑色就会扩散一点。具体的,如果一个格子里面是黑色,它就会扩散到上、下、左、右四个相邻的格子中,使得这四个格子也变成黑色(如果原来就是黑色,则还是黑色)。
请问,经过 2020分钟后,画布上有多少个格子是黑色的。
解析:
做法一:这就是很典型的bfs问题。bfs是按层次搜索,而这里的层次实际上就是时间点,按照时间往后面扩散就可以了。
做法二:枚举每个坐标上的点,有没有可能被扩散到,如果能被扩散到,ans++。判断的标准是:曼哈顿距离(即两点的横坐标和纵坐标的差值的长度)。这是因为扩散的方向是上下左右,不是像墨水一样散开。
正解代码:
#include <iostream>
#include <queue>
using namespace std;
#define Max_N 10005
#define ADD 2021
typedef pair<int, int> P;
int map[Max_N][Max_N] = {0};
long long ans = 4;
int dx[5] = {0, 0, 1, -1};
int dy[5] = {-1, 1, 0, 0};
int main() {
memset(map, -1, sizeof(map)); // map设置为-1代表没有访问过。map存放的值是:时间
queue<P> Que;
Que.push(P(0 + ADD, 0 + ADD));
map[0 + ADD][0 + ADD] = 0;
Que.push(P(2020 + ADD, 11 + ADD));
map[2020 + ADD][11 + ADD] = 0;
Que.push(P(11 + ADD, 14 + ADD));
map[11 + ADD][14 + ADD] = 0;
Que.push(P(2000 + ADD, 2000 + ADD));
map[2000 + ADD][2000 + ADD] = 0;
while (!Que.empty()) {
int ix = Que.front().first, iy = Que.front().second;
Que.pop();
if (map[ix][iy] == 2020) {
continue;
}
for (int ddt = 0; ddt < 4; ddt++) {
int tx = ix + dx[ddt], ty = iy + dy[ddt];
if (map[tx][ty] == -1) {
Que.push(P(tx, ty));
map[tx][ty] = map[ix][iy] + 1;
ans++;
}
}
}
cout << ans << endl;
return 0;
}
#include<bits/stdc++.h>
using namespace std;
signed main()
{
int ans = 0;
for(int x = 0 - 2020 ; x <= 2020 + 2020 ; x ++)
{
for(int y = 0 - 2020 ; y <= 2020 + 2020 ; y ++)
{
if(abs(x - 0) + abs(y - 0) <= 2020 || abs(x - 2020) + abs(y - 11) <= 2020
|| abs(x - 11) + abs(y - 14) <= 2020 || abs(x - 2000) + abs(y - 2000) <= 2020)
ans ++ ;
}
}
cout << ans << '\n';
return 0;
}
试题:本质上升序列
题目描述:
解析:
这还是一道很今典的动态规划问题。LIS。
只不过现在要求了一句,就是本质不同。
所以需要dp数组执行一个去重的工作,什么意思呢?
我们首先想想,普通的LIS为什么会出现重复的情况。比如现在,我们有一个dp数组。
那么dp[4]就是说以st[4]为结尾的序列的LIS的个数。
那如果在8的位置上又重复了一次n。
那么如果不进行去重的话,dp[8]就存放的个数是绿色花括号里面的结果。
重复的是之前的dp[4]里面的存放的红色花括号里面的结果。
随意最终的正确答案是粉色花括号里面的结果。
让我们扩展一下,就会发现:如果Z无论怎么去拼接,都不会产生重复的状态。
正解代码:
#include <iostream>
using namespace std;
int dp[220];
int main()
{
string str ="tocyjkdzcieoiodfpbgcncsrjbhmugdnojjddhllnofawllbhfiadgdcdjstemphmnjihecoapdjjrprrqnhgccevdarufmliqijgihhfgdcmxvicfauachlifhafpdccfseflcdgjncadfclvfmadvrnaaahahndsikzssoywakgnfjjaihtniptwoulxbaeqkqhfwl";
for(int i = 0;i<200;i++){
dp[i] = 1;
for(int j=0;j<i;j++){
if(str[i]>str[j]){
dp[i] += dp[j];
}else if(str[i] == str [j]){
dp[i] -= dp[j];
}
}
}
long long ans = 0;
for(int i=0;i<200;i++){
ans+=dp[i];
}
cout<<ans<<endl;
return 0;
}
试题:玩具蛇
题目描述
本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。
小蓝有一条玩具蛇,一共有 16 节,上面标着数字 1 至 16。每一节都是一个正方形的形状。相邻的两节可以成直线或者成 90 度角。
小蓝还有一个 4 × 4的方格盒子,用于存放玩具蛇,盒子的方格上依次标着字母 A 到 P 共 16 个字母。
小蓝可以折叠自己的玩具蛇放到盒子里面。他发现,有很多种方案可以将玩具蛇放进去。
下图给出了两种方案:
请帮小蓝计算一下,总共有多少种不同的方案。如果两个方案中,存在玩具蛇的某一节放在了盒子的不同格子里,则认为是不同的方案。
解析:
枚举每一个点,放入蛇的头部,然后再使用dfs来递归的放入其他的身体。最终计算出答案。
正解代码:
#include <iostream>
using namespace std;
int map[7][7] = {0};
long long ans = 0;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
void dfs(int x, int y, int step) {
map[x][y] = step;
if (step == 16) {
ans++;
map[x][y] = 0; //一定注意要有这一句话。
return;
}
for (int it = 0; it < 4; it++) {
if (map[x + dx[it]][y + dy[it]] == 0 && 1 <= x + dx[it] && x + dx[it] <= 4 && 1 <= y + dy[it] && y + dy[it] <= 4) {
dfs(x + dx[it], y + dy[it], step + 1);
}
}
map[x][y] = 0;
}
int main() {
for (int i = 1; i <= 4; i++) {
for (int j = 1; j <= 4; j++) {
memset(map, 0, sizeof(map));
dfs(i, j, 1);
}
}
cout << ans;
return 0;
}
试题:皮亚诺曲线距离
题目描述:
皮亚诺曲线是一条平面内的曲线。
下图给出了皮亚诺曲线的 1 阶情形,它是从左下角出发,经过一个 3 × 3的方格中的每一个格子,最终到达右上角的一条曲线。
下图给出了皮亚诺曲线的 2 阶情形,它是经过一个 ×
的方格中的每一个格子的一条曲线。它是将 1 阶曲线的每个方格由 1 阶曲线替换而成。
下图给出了皮亚诺曲线的 3 阶情形,它是经过一个 ×
的方格中的每一个格子的一条曲线。它是将 2 阶曲线的每个方格由 1 阶曲线替换而成。
皮亚诺曲线总是从左下角开始出发,最终到达右上角。
我们将这些格子放到坐标系中,对于 kk 阶皮亚诺曲线,左下角的坐标是(0, 0),右上角坐标是 (3^k − 1, 3^k − 1),右下角坐标是 (3^k − 1, 0),左上角坐标是(0, 3^k − 1)。
给定 k阶皮亚诺曲线上的两个点的坐标,请问这两个点之间,如果沿着皮亚诺曲线走,距离是到少?![](https://img-blog.csdnimg.cn/202106011944309.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0MDEzMjQ3,size_16,color_FFFFFF,t_70)
解析:
分成九个区域,每个区域其实都是从第一个区域变化来的。然后递归去变换就可以了。
红色线是分开区域的意思,分开了九个区域,绿色箭头是辅助我们去想到底是证明变换的。
谈蓝色的圆圈是标记了每个区域是怎么进入的。
黄色的是坐标轴,x和y的方向被告诉给我们了。
https://www.lanqiao.cn/courses/3981/learning/?id=134761
正解代码:
(没有考虑高精度,慎用)
#include <cmath>
#include <iostream>
using namespace std;
long long k, x1, y1_1, x2, y2;
long long GetInf(long long k, long long x, long long y) {
long long Len = pow(3, k - 1);
long long Lx = x / Len, Ly = y / Len;
if (Lx == 0) {
if (Ly == 0)
return 1;
if (Ly == 1)
return 2;
if (Ly == 2)
return 3;
} else if (Lx == 1) {
if (Ly == 2)
return 4;
if (Ly == 1)
return 5;
if (Ly == 0)
return 6;
} else if (Lx == 2) {
if (Ly == 0)
return 7;
if (Ly == 1)
return 8;
if (Ly == 2)
return 9;
}
}
long long dfs(long long k, long long x, long long y) {
//最基础的一层
if (k == 1) {
if (x == 0) {
if (y == 0)
return 0;
if (y == 1)
return 1;
if (y == 2)
return 2;
} else if (x == 1) {
if (y == 2)
return 3;
if (y == 1)
return 4;
if (y == 0)
return 5;
} else if (x == 2) {
if (y == 0)
return 6;
if (y == 1)
return 7;
if (y == 2)
return 8;
}
}
//查找上一层 k-1
long long UpperTotal = pow(9, k - 1) - 1; //上一层的所有点的距离。
UpperTotal++; //考虑两个区域的连接线。
long long Len = pow(3, k - 1); //上一层的长度。
long long No = GetInf(k, x, y); //判断是上一层的第几个的转移点。
switch (No) {
case 1: {
return 0 * UpperTotal + dfs(k - 1, x, y);
break;
}
case 2: {
long long X = Len - 1; //缩放因子
return 1 * UpperTotal + dfs(k - 1, X - (x % Len), y % Len);
break;
}
case 3: {
return 2 * UpperTotal + dfs(k - 1, x % Len, y % Len);
break;
}
case 4: {
long long Y = Len - 1; //缩放因子
return 3 * UpperTotal + dfs(k - 1, x % Len, Y - (y % Len));
break;
}
case 5: {
long long X = Len - 1; //缩放因子
long long Y = Len - 1; //缩放因子
return 4 * UpperTotal + dfs(k - 1, X - (x % Len), Y - (y % Len));
break;
}
case 6: {
long long Y = Len - 1; //缩放因子
return 5 * UpperTotal + dfs(k - 1, x % Len, Y - (y % Len));
break;
}
case 7: {
return 6 * UpperTotal + dfs(k - 1, x % Len, y % Len);
break;
}
case 8: {
long long X = Len - 1; //缩放因子
return 7 * UpperTotal + dfs(k - 1, X - (x % Len), y % Len);
break;
}
case 9: {
return 8 * UpperTotal + dfs(k - 1, x % Len, y % Len);
break;
}
}
}
int main() {
scanf("%lld%lld%lld%lld%lld", &k, &x1, &y1_1, &x2, &y2);
cout << abs(dfs(k, x1, y1_1) - dfs(k, x2, y2));
return 0;
}
#include<bits/stdc++.h>
#define ll long long
using namespace std;
template<typename T>void Out(T x)
{
if(x < 0) putchar('-') , x = -x;
if(x > 9) Out(x / 10);
putchar(x % 10 + '0');
}
const int N = 40;
ll p[N];
map<pair<int , int> , int>mp;
ll calc(int n , ll x , ll y , map<pair<int , int> , int>mp)
{
__int128 ans = 0;
ll x_ = x / p[n] , y_ = y / p[n];
if(!n) return mp[make_pair(x_ , y_)] - 1;
else
{
ans += (mp[make_pair(x_ , y_)] - 1) * p[n] * p[n];
map<pair<int , int> , int>mp1;
if((x_ == 0 && y_ == 1) || (x_ == 2 && y_ == 1))
{
for(int i = 0 ; i <= 2 ; i ++)
{
for(int j = 0 ; j <= 2 ; j ++)
mp1[make_pair(2 - i , j)] = mp[make_pair(i , j)];
}
}
else if((x_ == 1 && y_ == 2) || (x_ == 1 && y_ == 0))
{
for(int i = 0 ; i <= 2 ; i ++)
{
for(int j = 0 ; j <= 2 ; j ++)
mp1[make_pair(i , 2 - j)] = mp[make_pair(i , j)];
}
}
else if(x_ == 1 && y_ == 1)
{
for(int i = 0 ; i <= 2 ; i ++)
{
for(int j = 0 ; j <= 2 ; j ++)
mp1[make_pair(2 - i , 2 - j)] = mp[make_pair(i , j)];
}
}
else mp1 = mp;
return ans += calc(n - 1 , x % p[n] , y % p[n] , mp1);
}
}
void init()
{
p[0] = 1;
for(int i = 1 ; i < N ; i ++) p[i] = p[i - 1] * 3;
mp[make_pair(0 , 0)] = 1;
mp[make_pair(0 , 1)] = 2;
mp[make_pair(0 , 2)] = 3;
mp[make_pair(1 , 2)] = 4;
mp[make_pair(1 , 1)] = 5;
mp[make_pair(1 , 0)] = 6;
mp[make_pair(2 , 0)] = 7;
mp[make_pair(2 , 1)] = 8;
mp[make_pair(2 , 2)] = 9;
}
signed main()
{
init();
int k ;
ll x1 , y1 , x2 , y2;
cin >> k >> x1 >> y1 >> x2 >> y2;
__int128 a = calc(39 , x1 , y1 , mp);
__int128 b = calc(39 , x2 , y2 , mp);
if(a > b) Out(a - b);
else Out(b - a);
puts("");
return 0;
}
试题:游园安排
题目描述:
解析:
本题目是一道很典型的动态规划问题,他的模型就是最长上升子序列。LIS。
难点在于(1)这么保证字典序最小。(2)怎么去输出路径的结果。
难点分步突破:
突破(1):我们在拼接的时候可以使用贪心法。什么意思。比如现在我们有序列ab,和ac。虽然把z拼接到ab和ac的后面长度都是3,但是为了找出最小的字典序,所以我们最后还是把z拼接到了b上面。
意思就是,每次保证dp[i]的第i个字符,能拼接的这个dp[j]是所有能拼接dp[j]中字典序最小的那一个。那我怎么知道当前的i是从哪里的j接上去的呢?
突破(2):我们使用pos数组,来记录我们是从哪里的dp[j]走过来的,最后使用dfs还原路径。
# 算法证明:
对于“黄色”位置,那么“红色”位置是“黄色”能保证最长情况下可以找到的字典序最小的一个位置。
对于“蓝色”位置,那么“黄色”位置是“蓝色”能保证最长情况下可以找到的字典序最小的一个位置。
。。。
就这样,贪心保证了答案的正确性。
正解代码:
代码如下:(不一定正确,通过了蓝桥杯的样例,但它的那个样例太水了,就是数据规模太小了)
下面这个代码是我写的。O(N^2)
#include <cmath>
#include <iostream>
using namespace std;
int dp[100000], pos[1000000];
string st[100000] /*拆分之后的字符串*/, sst /*用来保存答案*/;
int ans = -1;
void Out(int x) {
if (x == pos[x]) {
cout << st[x];
return;
}
Out(pos[x]);
cout << st[x];
}
int main() {
cin >> sst;
int Len = 0;
string tmp = "";
for (int i = 0; i < sst.length(); i++) {
tmp.push_back(sst[i]);
if (sst[i + 1] >= 'A' && sst[i + 1] <= 'Z' || i == sst.length() - 1) {
st[Len++] = tmp;
tmp.clear();
}
}
for (int i = 0; i < Len; i++) {
dp[i] = 1;
pos[i] = i;
//开始拼接LIS。
for (int j = 0; j < i; j++) {
//可以拼接,然后长度可以更长。
if (st[i] > st[j] && dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1;
pos[i] = j;
}
//可以拼接,长度是一样的,但是字典序可以更小。
if (st[i] > st[j] && dp[j] + 1 == dp[i] && st[j] < st[pos[i]]) {
pos[i] = j;
}
}
}
int F_I = 0;
for (int i = 1; i < Len; i++) {
if (dp[i] > dp[F_I]) {
F_I = i;
}
if (dp[i] == dp[F_I] && st[i] < st[F_I]) {
F_I = i;
}
}
Out(F_I);
return 0;
}
本题还有一种做法是O(NLogN)的做法,请看官方题解(需要9.9元):https://www.lanqiao.cn/courses/3981/learning/?id=134762
我在这里大致讲一讲它的想法:
首先你要掌握LIS的O(NLogN)的做法的原理。其中LogN从N的复杂度降下是因为使用了一个优先队列来优化。
具体参考lxt-Lucia小仙女的这篇文章的解法二:https://blog.csdn.net/lxt_Lucia/article/details/81206439
好,现在我假定您已经会了使用NLogN做LIS的解法。那么现在首先问问自己,为什么使用了这个解法就能保证字典序是最小的呢?
如果您不能准确的告诉小熊,小熊建议您再次把上文的链接读一遍。如果您知道了答案,那很好。它保证了字典序最小的原理其实就是因为每次都会使用s[i]来更新生成的dp(LIS序列)。
另外,dp数组保存的是最终得到的LIS序列的答案吗?
别搞错,dp[N]存放的是N长度的LIS的末尾位置。
那我们怎么还原出这个LIS的样貌呢?
方法是:建立一个POS[i]数组,记录i进入dp[LEN]的时候,是在什么长度[也就是LEN]进入的。
比如现在有
st字符串:
st[0] | st[1] | st[2] | st[3] |
a | d | f | c |
dp数组:
再次强调,dp[x]数组里面的自变量x是LIS子串的长度。
dp[1] | dp[2] | dp[3] |
a | d | f |
c想要进入,因该进入长度为2的dp[2]中,也就是长度为2的子序列的末尾可以更小,所以
pos[3]=2。
最后的dp数组:
dp[1] | dp[2] | dp[3] | dp[4] |
a | c | f |
最后的pos数组:
pos[0] | pos[1] | pos[2] | pos[3] |
1 | 2 | 3 | 2 |
然后我们来还原原来的LIS的样子。
最长的是pos[2]也就是末尾是s[2]('c')的LIS是最长的,它是从谁的身上接上去的呢?
有两个pos的值是2,也就是3,要想变成3,一定是从2的身上接上去的。
考虑pos[3],st[2]是接在st[3]的身上吗?显然不可能,这是因为,st[3]在st[2]之后,怎么样都不可能拼接在一起。
那么pos[1]也是2,所以st[2]是从st[1]拼接起来的。
同理st[1]也是由st[0]拼接的。
那么有人问,
pos[...] | pos[i-2] | pos[i-1] | pos[i] |
... | 2 | 2 | 3 |
st[i]是从谁拼接的呢。
从pos[i-1]还是从pos[i-2]呢。
首先搞明白为什么pos[i-2]==pos[i-1]这是因为在长度为2的子串的时候,s[i-1]能比s[i-2]字典序更小,然后也能满足dp[2]的LEN==2的要求,所以其实s[i] 既可以从s[i-1]拼接,也可以s[i-2]拼接,只不过是我们要求字典序最小,所以我们选择了pos[i-1](也就是最靠后的,pos的值是2的,那个位置)来拼接。
小熊想请您仔细阅读:
int tmp = n;
while (len>0) {
if (pos[tmp] == len) {
ans.push_back(s[tmp]);
len--;
tmp--;
}
else
tmp--;
}
这段代码,这段代码就是我上面所讲的这个思想。
然后下面是这道题目代码的全貌。
#include<iostream>
#include<vector>
using namespace std;
const int N = 1e6 + 10;
string s[N]/*拆分出的字符串数组*/ , dp[N] /*递推数组*/, S/*原始字符串*/;
vector<string> ans;
int pos[N] /*记录位置*/, n = -1;
signed main() {
ios::sync_with_stdio(false);
cin >> S;
/*===拆分字符串===*/
string now = "";
for (int i = 0; i < S.size(); i++) {
if (S[i] >= 'A' && S[i] <= 'Z') {
s[++n] = now;
now = S[i];
} else now += S[i];
}
/*===拆分字符串 end===*/
int len = 1;
s[++n] = now;
dp[len] = s[1];// 递推数组,dp(i)表示以长度为i的lis的结尾元素
pos[1] = 1;
for (int i = 2; i <= n; i++) {
if (dp[len] < s[i]) { // i位置的元素比较大,那么lis长度会变化
dp[++len] = s[i], pos[i] = len; // 每次从s数组里加入到𝑑𝑝中时,可以记录下s[𝑖]的位置
} else {
int j = lower_bound(dp + 1, dp + 1 + len, s[i]) - dp; // 折半查找
dp[j] = s[i]; // 更新
pos[i] = j;
}
}
int tmp = n;
while (len>0) {
if (pos[tmp] == len) {
ans.push_back(s[tmp]);
len--;
tmp--;
}
else
tmp--;
}
// ans.push_back(s[tmp]);
for (int i = ans.size() - 1; i >= 0; i--) cout << ans[i];
cout << '\n';
return 0;
}
试题:
题目描述:
解析:
先把题目读懂,s+a时间一到,消息就发出去了。
对于这道题,其实如果想不出来正确的解法。小熊建议可以写一个全排列程序,然后做模拟找出时间总和最小的排列,先把一部分的分数拿到手再说。
如果要想知道正确的解法,就必须仔细思考。
假设:我们有1~n个同学需要去答疑。
第一个同学发消息的时刻是
第二个同学发消息的时刻是
第三个同学发消息的时刻是
第四个同学发消息的时刻是
....
第n个同学发消息的时刻是
所以总时刻是,把上面这些时刻全部加起来。
请观察上面的规律。
有
也就是第n - i +1位同学(n是固定的,比如现在i=n,那就是第1位同学),对上一行这个式子的贡献是:
或者说第i位同学的贡献值是:
要求保证这个最终答案最小。我们s,a,e是天生的,改变的是乘以他们的因子。
对于s1和a1,n是他们的因子。对于sn和an,1是他们的因子。
所以s1+a1尽量选择小一点的 sn和an尽量选择大一点的。
这就是贪心。
本题解答完毕,具体实现请看代码。
正解代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
struct node {
int s, a, e;
//重新定义排序的规则
bool operator<(const node& x) const { return (s + a + e) < (x.s + x.a + x.e); }
};
vector<node> vec;
signed main() {
int n, ans = 0;
cin >> n;
for (int i = 1; i <= n; i++) {
int s, a, e;
cin >> s >> a >> e;
vec.push_back(node{s, a, e});
}
sort(vec.begin(), vec.end());
for (int i = 0; i < n; i++) {
int s = vec[i].s, a = vec[i].a, e = vec[i].e;
ans += (n - i) * (s + a) + (n - i - 1) * e;
}
cout << ans << '\n';
return 0;
}
宇宙第一小正太\(^o^)/~萌量爆表求带飞=≡Σ((( つ^o^)つ~ dalao们点个关注呗~