2022-02-09 每日打卡:难题精刷
写在前面
“这些事儿在熟练之后,也许就像喝口水一样平淡,但却能给初学者带来巨大的快乐,我一直觉得,能否始终保持如初学者般的热情、专注,决定了在做某件事时能走多远,能做多好。” 该系列文章由python编写,所刷题目共三个来源:之前没做出来的 ;Leetcode中等,困难难度题目; 周赛题目;某个专题的经典题目,所有代码已AC。每日1-3道,随缘剖析,希望风雨无阻,作为勉励自己坚持刷题的记录。
乌龟棋
最后结果和摆放位置有关->无法通过排序统计的方法。考虑【创建相同大小数组】动态规划,动态规划只能通过枚举【是否进行上一步选择】:
#include<bits/stdc++.h>
using namespace std;
const int maxj = 400;
int a[maxj],f[110][110][110][110],cnt[5];
int main(){
// 可以通过tie(0)(0表示NULL)
// 来解除cin与cout的绑定,进一步加快执行效率。
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int n,m;
cin>>n>>m;
for(int i = 1 ;i <= n; ++i)cin>>a[i];
for(int i = 1;i <= m; ++i){
int t;
cin>>t;
cnt[t]++;//记录四种卡片的个数
}
for(int i = 0;i <= cnt[1]; ++i){
for(int j = 0;j <= cnt[2]; ++j){
for(int z = 0;z <= cnt[3]; ++z){
for(int h = 0;h <= cnt[4]; ++h){
// 记录位置,注意从第一开始
int t=i+j*2+z*3+h*4+1;
//卡片必须有
if(i != 0)
f[i][j][z][h] = max(f[i][j][z][h],f[i-1][j][z][h]+a[t]);// 记录最优分数。走一步。
if(j != 0)
f[i][j][z][h] = max(f[i][j][z][h],f[i][j-1][z][h]+a[t]);// 走两步到
if(z != 0)
f[i][j][z][h] = max(f[i][j][z][h],f[i][j][z-1][h]+a[t]);// 走三步到
if(h != 0)
f[i][j][z][h] = max(f[i][j][z][h],f[i][j][z][h-1]+a[t]);// 走四步到
}
}
}
}
cout<<f[cnt[1]][cnt[2]][cnt[3]][cnt[4]]+a[1]<<'\n';//用完卡片后,要加上第一步的分数。
return 0;
}
圆形石子合并
石子合并问题有三种版本,第一种是任意两个堆石子合并,可以直接使用贪心的思想。第二种是一条直线的相邻的两堆石子合并,动态规划使用ij分别表达起始和结束位置。这里是第三种,圆形相邻石子合并,一般为了使【圆形】可循环起来,有两种做法:
- 重新定义状态转移方程:定义ij为从i起的j堆,这样我们可以通过加法取余确定下标。
- 在数组后面叠加数组,取的时候控制边界,这样就化成了直线问题:
n = int(input())
nums = list(map(int,input().split()))
def solve(nums):
n = len(nums)
nums = nums*2
from itertools import accumulate
from functools import lru_cache
# 使用前缀和相减来计算某个区间之间的和
accu = [0]+list(accumulate(nums))
@lru_cache(None)
def recur(i,j):
if i==j:
return 0
return max(recur(i,k)+recur(k+1,j) for k in range(i,j))+accu[j+1]-accu[i]
@lru_cache(None)
def recur1(i,j):
if i==j:
return 0
return min(recur1(i,k)+recur1(k+1,j) for k in range(i,j))+accu[j+1]-accu[i]
return max(recur(i,i+n-1) for i in range(0,n)),min(recur1(i,i+n-1) for i in range(0,n))
r1,r2 = solve(nums)
print(r2)
print(r1)