Codeforces 611D.New Year and Ancient Prophecy (dp + lcp)

题目链接:

http://codeforces.com/problemset/problem/611/D

题意:

长为n的只有数字组成的字符串(n<=5000),问能分割成多少组数字,这些数字里不含前导0,且数字的大小满足严格单调递增

思路:

from: http://blog.csdn.net/qwb492859377/article/details/50445450  qwb orz

最难的地方,就是如何去快速判断两个数字的大小谁大谁小呢?

我们先来讲下最长公共前缀lcp的定义。如果有串A和串B,lcp[i][j]表示的是串A从原串第i位置开始,串B从原串第j位置开始,那么从这两个位置开始的有多少个字符相等

那么如何来求lcp呢,方程很简单,看了都能懂

lcp[i][j]=lcp[i+1][j+1]+1,if(s[i]==s[j])

lcp[i][j]=0,if(s[i]!=s[j])

求出lcp后如何快速判断两个区间的大数字是否相等呢?

假如lcp[a][b]>=len ,说明两个区间的数字是完全相等的,此时肯定数字是相等的

如果lcp[a][b]<len,那么我们只需要比较s[a+lcp[a][b]]和s[b+lcp[a][b]]的大小就可以了,因为这个位置是两个子串第一个不一样的位置.

知道了这个的话,我们就能再来考虑这道题的dp了。

设dp[i][j](j<=i)表示现在只考虑前i个,最后一个数字是以第j个开头。

那么就能得到转移方程

dp[i][j]+=dp[j-1][k], max(j+1-len,1)<=k<=j-1

dp[i][j]+=dp[j-1][j-len],如果j-len>=1且以j-len开头比以j开头且长度为len数字要小

边界条件是j=1,此时应该等于1

感觉还有地方,,就是写dp的时候总是处理不好边界条件

其实感觉如果就在第二层for里面写if判断边界,这是一种非常好的方法,减少了很多思考,。

收获:

考虑如何转移,dp[i][j]可以从前j-1个并且长度小于当前长度的位置转移过来,之后再深入的考虑长度相等的时候要不要转移,就要判断两个字符串大小了,要用O(1)优秀的做法, 就是lcp。 容易想到n^3的dp【思考许久,其实我一点都没有思路】, dp[i][j] += dp[j-1][k], 学到了使用前缀和优化成n^2, pre[i][j]就是考虑到第i个位置,数字以第1~j开头的所有方法数。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MS(a) memset(a,0,sizeof(a))
#define MP make_pair
#define PB push_back
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3fLL;
inline ll read(){
    ll x=0,f=1;char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1;ch=getchar();}
    while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘;ch=getchar();}
    return x*f;
}
//////////////////////////////////////////////////////////////////////////
const int maxn = 5e3+10;
const int mod = 1e9+7;

int n;
char s[maxn];
int dp[maxn][maxn],lcp[maxn][maxn],pre[maxn][maxn];

bool check(int a,int b,int len){
    int t = lcp[a][b];
    if(t<len && s[a+t]<s[b+t]) return true;
    return false;
}

int main(){
    cin >> n >> s+1;

    for(int i=n; i>=1; i--)
        for(int j=n; j>=1; j--){
            if(s[i]==s[j]) lcp[i][j] = lcp[i+1][j+1]+1;
            else lcp[i][j] = 0;
        }

    for(int i=1; i<=n; i++){
        for(int j=1; j<=i; j++){
            if(s[j]==‘0‘) continue;
            if(j==1) dp[i][j]=1;
            int len = i-j+1;
            int l = max(j-len+1,1), r = j-1;
            if(l<=r) dp[i][j] = (dp[i][j]+pre[j-1][r]-pre[j-1][l-1]+mod)%mod;
            if(j-len>=1 && check(j-len,j,len)) dp[i][j] = (dp[i][j]+dp[j-1][j-len])%mod;
        }
        for(int j=1; j<=i; j++){
            pre[i][j] = (pre[i][j-1]+dp[i][j])%mod;
        }
    }

    int ans = 0;
    for(int i=1; i<=n; i++){
        ans = (ans+dp[n][i])%mod;
    }
    cout << ans << endl;

    return 0;
}
时间: 2024-10-07 07:17:32

Codeforces 611D.New Year and Ancient Prophecy (dp + lcp)的相关文章

Codeforces 611D New Year and Ancient Prophecy DP

题意: 把一个字符串分割成多个小串,小串组成严格递增序列,n<=5000 这是一个DP. s代表原串 dp[i][j]代表当前到i位置最后一个串是以j为开头的方案数.答案就是dp[n][1]+...+dp[n][n] 很容易得到dp[i][j]=dp[j-1][k]+dp[j-1][k+1]……dp[j-1][j-1]    (i-j=j-k) 如果s[k-1....j-1] >=s[j....i], dp[i][j]+=dp[j-1][k-1] 这样dp的递推式就有了,很容易发现dp[i][

codeforces 161D - Distance in Tree(树形dp)

题目大意: 求出树上距离为k的点对有多少个. 思路分析: dp[i][j] 表示 i 的子树中和 i 的距离为 j 的点数有多少个.注意dp[i] [0] 永远是1的. 然后在处理完一颗子树后,就把自身的dp 更新. 更新之前更新答案. 如果这颗子树到 i 有 x 个距离为j的.那么答案就要加上 dp[i] [ k-j-1] * x; #include <iostream> #include <cstdio> #include <cstring> #include &l

CodeForces 258B Little Elephant and Elections 数位DP

前面先用数位DP预处理,然后暴力计算组合方式即可. #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #include <climits> #include <string> #include <iostream> #include <map> #include <cstdlib> #include

Codeforces 437E The Child and Polygon(区间DP)

题目链接:Codeforces 437E The Child and Polygon 题目大意:给出一个多边形,问说有多少种分割方法,将多边形分割为多个三角形. 解题思路:首先要理解向量叉积的性质,一开始将给出的点转换成顺时针,然后用区间dp计算.dp[i][j]表示从点i到点j可以有dp[i][j]种切割方法.然后点i和点j是否可以做为切割线,要经过判断,即在i和j中选择的话点k的话,点k要在i,j的逆时针方. #include <cstdio> #include <cstring&g

Codeforces 219D. Choosing Capital for Treeland (树dp)

题目链接:http://codeforces.com/contest/219/problem/D 树dp 1 //#pragma comment(linker, "/STACK:102400000, 102400000") 2 #include <algorithm> 3 #include <iostream> 4 #include <cstdlib> 5 #include <cstring> 6 #include <cstdio&

CodeForces Round#229 DIV2 C 递推DP

对这道题目也只好说呵呵了,没注意k的范围最大才10,所以昨晚纠结了很久,没什么好的方法来处理,后来无奈想去翻翻题解,发现人家开头就来了句,因为k的范围比较小 所以.........我只好暂停马上回头看看题目,是的,k比较小所以完全可以先在询问前预处理DP一遍, DP就比较清晰了,dp[i][j]  (i>=0 && i<k,,,,j>=i && j <=n)代表意义呢 以i为开头的  区间[1,j]注意 是 1~j的 所需要的操作数,题目问的是最小操

Codeforces Beta Round #1 C. Ancient Berland Circus

果然Java还是不靠谱啊,一个NaN把我整了半天~~ 题目大意: 有一个正多边形,给出任意三个顶点的坐标,求这个正多边形的最小面积. 解题思路: 首先要知道这三个顶点组成的三角形的外接圆一定是这个正多边形的外接圆. 用过计算出三角形的三边长,可以计算出三角型面积,进而推出外接圆半径. 可以得到三个圆心角,找出最大公约数,那就是最大角度. 就可以计算出多边形面积了~~ 下面是代码: import java.text.DecimalFormat; import java.util.Scanner;

codeforces 235B Let&#39;s Play Osu! 概率dp

题意:给定n表示有n个格子,下面每个格子为O的概率是多少.对于一段连续 x 个O的价值就是 x^2 ;求获得的价值的期望是多少. 思路:n^2=n×(n-1)+n,设ai为第i段连续O的长度,∑ai^2 = ∑[ ai+ ai*(ai-1) ] = ∑ ai*(ai-1) + ∑ai = ∑ C(ai, 2)*2 + ∑ai,那么问题可以转 化为求长度大于1的连续段数*2+O的个数的总期望. ∑ai我们可以理解为O的总个数,所以它的期望为∑pi: C(ai, 2)*2我们可以认 为是连续ai个O

Codeforces 235B Let&#39;s Play Osu! (概率dp求期望+公式变形)

B. Let's Play Osu! time limit per test:2 seconds memory limit per test:256 megabytes You're playing a game called Osu! Here's a simplified version of it. There are n clicks in a game. For each click there are two outcomes: correct or bad. Let us deno