Codeforces Round #129 (Div. 1) (各种好dp)

http://codeforces.com/contest/204

A. Little Elephant and Interval

给你l,r,问[l,r]区间内一个数的第一位和最后一位相等的数有多少个。

简单的数位dp,其实随便对数位恶搞一下就能过。


// Author : JayYe  Created Time: 2013-11-8 15:43:42
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
typedef __int64 ll;

ll mul[22];

int a[22];
ll solve(ll n) {
    if(n <= 9) return   n+1;
    int len = 0;
    while(n) {
        a[++len] = n%10;
        n /= 10;
    }
    ll ret = 0;
    for(int i = len-1;i >= 1; i--) {
        if(i == 1)  ret += 10;
        else    ret += mul[i-2]*9;
    }
    ret += (a[len]-1)*mul[len-2];
    for(int i = len-1;i > 1; i--) {
        if(a[i] > 0)
            ret += (a[i])*mul[i-2];
    }
    if(a[len] <= a[1])  ret++;
    return ret;
}

ll get(ll n) {
    ll ret = 0;
    for(int i = 0;i <= n; i++) {
        if(i == 0) {
        
            ret++; continue;
        }
        ll cur = i;
        int len = 0;
        while(cur) {
            a[++len] = cur%10;
            cur /= 10;
        }
        if(a[len] == a[1])  ret++;
    }
    return ret;
}

int main() {
    mul[0] = 1;
    for(int i = 1;i <= 17; i++)
        mul[i] = mul[i-1]*10;
    ll l, r;
    scanf("%I64d%I64d", &l ,&r);
    printf("%I64d\n", solve(r) - solve(l-1));
//    printf("%I64d\n", get(r) - get(l-1));
    return 0;
}


B. Little Elephant and Cards

水题,比A题简单多了。。


C. Little Elephant and Furik and Rubik

给你两串字符串,长度相等都为n,现在从两个字符串中分别取出长度 x (1<=x<=n)的字串,问取出来的串对齐后对应位置字符相同的期望数为多少。 也就是说 取出长度为x的字串a和字串b,ai = bi的期望个数为多少。


思路: 首先考虑某个字符,比如说a字符,这个a字符在串1中很多位置都可能出现,在串2中也一样,假设a字符在串1中i位置有出现,在串2中j位置有出现,那么取出字串后i位置刚好和j位置对应的情况有 min(i, j)* min(n-i+1, n-j+1)种。知道两个位置可以这样子算对应情况后,如果直接枚举两个串相同字符位置的话复杂度是O(n^2)的,可以把所有情况分成三种,第一种是s1[i] = s2[i],第二种是s1[i] = s2[j] (i > j),第三种是 s1[i] = s2(j) (i < j)。对于第一种情况很好算,第二种情况的话,s1[i] = s2[j],因为i > j,所以情况数肯定是 j * (n-i+1)!所以可以直接边预处理边计算情况数,对于字符x前面可以计算出x在s2中位置i的和cnt[x],对于当前s1的字符y,ans += cnt[y]*(n-i+1)即可。复杂度就变成了O(n)了,对于第三种情况也是一样的。


// Author : JayYe  Created Time: 2013-11-8 18:32:26
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

const int maxn = 200000 + 5;

char a[maxn], b[maxn];
double pre[33];

int main() {
    int n;
    scanf("%d", &n);
    scanf("%s%s", a, b);
    double ans = 0;
    for(int i = 0;i < n; i++) {
        ans += (double)pre[a[i]-'A']*(n-i);
        pre[b[i] - 'A'] += i+1;
    }
    memset(pre, 0, sizeof(pre));
    for(int i = 0;i < n; i++) {
        ans += (double)pre[b[i]-'A']*(n-i);
        pre[a[i]-'A'] += i+1;
    }
    for(int i = 0;i < n; i++) if(a[i] == b[i]) {
        ans += (double)(i+1)*(n-i);
    }
    double div = 0;
    for(int i = 1;i <= n; i++)  div += (double)i*i;
    printf("%.10lf\n", ans/div);
    return 0;
}

D. Little Elephant and Retro Strings

非常不错的dp。。。

给你一个串,里面有字符B,W,X。X可以是B或者W,问有至少k个连续的B并且右边有至少k个连续的W的情况数有多少种。

思路: 因为可能有多个连续的B或者连续的W存在,所以为了避免计算重复,我就直接找最左边的至少k个连续的B和最右边的至少k个连续的W,两个中间是什么都无所谓。所以要处理出到i位置还没出现过k个连续的B的情况数no_b[i]数组,可以这样子算,首先所有情况很容易计算。如果刚好到i位置出现了k个连续的B,假设小于i的no_b数组计算正确了,因为要使得到i位置刚好出现了k个连续的B,所以 a[ i-k ]必须不能是B,然后no_b[i]要减去no_b[i-k-1]即可。对于计算W的情况也差不多,反向dp一下即可。

计算出到i位置还没出现过k个连续的B的情况数no_b[i]数组后,接下来就很简单了,正向dp然后反向dp下就可以了。


// Author : JayYe  Created Time: 2013-11-9 9:36:21
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
typedef __int64 ll;

const int maxn = 1000000 + 5;
const int mod = 1000000007;

char s[maxn];
ll no_b[maxn], no_w[maxn], tot[maxn], cnt[maxn];
int sb[maxn], sw[maxn];

int exgcd(int a, int b, int &x, int &y) {
    if(!b) {
        x = 1; y = 0;
        return a;
    }
    int ret = exgcd(b, a%b, y, x);
    y -= a/b*x;
    return ret;
}

int inv(int a, int mod) {
    int x, y, d = exgcd(a, mod, x, y);
    if(x < 0)   x += mod;
    return x;
}

int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    scanf("%s", s + 1);
    tot[n+1] = 1;
    for(int i = n;i >= 1; i--) {
        if(s[i] == 'X') tot[i] = tot[i+1]*2%mod;
        else    tot[i] = tot[i+1];
    }

    sb[0] = 0; s[0] = 'W';
    no_b[0] = 1;
    cnt[0] = 0;
    for(int i = 1;i <= n; i++) {
        if(s[i] == 'X') no_b[i] = no_b[i-1]*2%mod;
        else    no_b[i] = no_b[i-1];
        if(s[i] == 'W') sb[i] = 0;
        else    sb[i] = sb[i-1] + 1;
        cnt[i] = cnt[i-1];
        if(sb[i] >= k && (i == k || s[i-k] != 'B')) {
            ll cur = i == k ? 1 : no_b[i-k-1];
            no_b[i] = (no_b[i] - cur)%mod;
            cnt[i] = (cnt[i] + cur*tot[i+1])%mod;
        }
    }

    no_w[n+1] = no_w[n+2] = 1;
    sw[n+1] = 0; s[n+1] = 'B';
    ll ans = 0;
    for(int i = n;i >= 1; i--) {
        if(s[i] == 'X') no_w[i] = no_w[i+1]*2%mod;
        else    no_w[i] = no_w[i+1];
        if(s[i] == 'B') sw[i] = 0;
        else    sw[i] = sw[i+1] + 1;
        if(sw[i] >= k && (n-i+1==k || s[i+k] != 'W')) {
            ll cur = no_w[i+k+1];
            no_w[i] = (no_w[i] - cur)%mod;
            ans = (ans + cnt[i-1]*cur%mod*inv(tot[i], mod)%mod)%mod;
        }
    }
    printf("%I64d\n", (ans%mod + mod)%mod);
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值