学习任何东西都是这样吧。如果长时间没有练习就会生疏和遗忘。
大概唯一的办法就是长时间的反复练习。或者专题训练+打比赛也是不错的。
学而时习之,不会忘乎。。。。。。。。。。
希望自己能越来越会学习。
官方题解是动态规划,我的是记忆化搜索,不过都大同小异了。dp[i]表示后缀i的答案,dx[i]表示后缀i的最少划分。
然后就一路记忆化搜索过去即可,最多有n个状态,时间复杂度为O(n)。
一开始自己以为是数学题,然后不会。想到搜索但觉得搜索量太大会超时。当时觉得搜索的时间复杂度为O(2^n),每个位子都可以分开或者不分开嘛。
我想这大概是因为自己对搜索算法的理解不够深了。
有一种搜索是无返回值,无记录值的搜索,只在找到一个可行解时对全局变量+1,在找不到时就返回。这个方法叫做回溯法,优点是能找到所有可行解及其具体解法,顺便维护了解的个数。但缺点也很明显,那就是时间复杂度高。如果用回溯法对本题进行搜索那么时间复杂度确实是O(2^n)无疑的。但是我们又不需要知道所有的可行解的具体解法,我们甚至连一个可行解的具体解法都不需要知道。我们只需要知道有多少个解就好了难道不是吗?
那么这时候的搜索算法就可以进行改进了,我们的目的不再是搜索出所有解,而是找到解的个数。搜索的目的变了,搜索的很多本质与细节也就变了,我们不再想通过枚举所有解的方式来找到解的个数,而是希望借助某些计算方法,通过搜索的方式把答案计算出来。这又和纯数学题目不同,纯数学题就是带公式计算,而搜索过程中的计算,就有点动态规划或者记忆化搜索的意思了。
充分利用问题的漏洞和规律,通过记录一些值,维护一些值的方式来最大限度降低搜索量,这样的搜索才不叫暴搜。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 1010;
const ll mod = 1e9+7;
const ll inf = 0x3f3f3f3f;
ll n;
char s[maxn];
ll a[30];
ll MAX;
ll dp[maxn];
ll dx[maxn];
ll dfs(ll now)
{
if(now==n) return 1;
if(dp[now]) return dp[now];
ll next=now+1;
ll l=a[s[next-1]-'a'];
while(1)
{
if(next-now+1>l) break;
if(next-now+1>a[s[next]-'a']) break;
l=min(l,a[s[next]-'a']);
next++;
}
MAX=max(MAX,next-now);
ll ret=0;
for(ll i=now+1;i<=next;i++)
{
ret=(ret+dfs(i))%mod;
dx[now]=min(dx[now],dx[i]+1);
}
return dp[now]=ret;
}
int main()
{
memset(dx,inf,sizeof(dx));
scanf("%lld",&n);
dx[n]=0;
scanf("%s",s);
for(ll i=0;i<26;i++)
scanf("%lld",a+i);
MAX=1;
printf("%lld\n",dfs(0));
printf("%lld\n%lld\n",MAX,dx[0]);
return 0;
}