【CodeForces】115D Unambiguous Arithmetic Expression 组合数学

本文介绍了一种解决UAE表达式计数问题的方法。通过将问题转化为括号序列的计数,使用区间DP实现O(n3)算法,并考虑特殊情况进行优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description(translated)

定义明确表达式UAEUAE
1. 自然数都是UAEUAE(带前导零的也算)——换句话说,数字串是UAEUAE
2. 对于两个UAEUAEXXY(X)+ororor/(Y)(X)+or−or∗or/(Y)UAEUAE
3. 对于一个UAEUAEXX+(X)(X)−(X)都是UAEUAE
给出一个UAEUAE,它的括号都被删掉了,请求出可以满足这个条件的UAEUAE的个数

Input

一行,一个字符串,仅包含+/+、−、∗、/以及数字090到9,不超过20002000个字符

Output

一行,一个整数,表示方案数

Sample Input

sample1.in
1+2*3

sample2.in
03+-30+40

sample3.in
5/0

Sample Output

sample1.out
2

sample2.out
3

sample3.out
1

Solution

好神的一道题……
第一,区间DPDPO(n3)O(n3)不说了,简单

然后,如果没有连续的符号,很显然每个符号都等价了
设符号有aa个,那么ans=f[a+1]=i=1af[i]f[a+1i]f[1]=1f[1]=1
可以O(n)O(n)求出,当然数据范围小,O(n2)O(n2)也没人打你

现在我们考虑有连续的符号该怎么办
先简单判一下无解
1. 如果/∗、/前面是运算符或者前面没有字符,无解
2. 如果末尾出现运算符,无解
然后就都是有解的了……

考虑一下删去括号之前的UAEUAE,为其添加一些奇怪的东西——在正负号前面添加一组括号。举个栗子——(+(233))(8)(+(233))−(8)变成(()+(233))(8)(()+(233))−(8)
由于每个运算符前后都有括号,我们去掉后方括号,上式变成(()+233)8(()+233)−8,省略数字和运算符就变成了(())(())

神奇的是,每个运算符号恰好对应一个右括号,运算的顺序和括号的运算顺序也是一样的,不难发现,每个满足条件的UAEUAE恰好对应一个合法括号序列,每个合法括号序列也恰好对应一个满足条件的UAEUAE。但是考虑一下作为正负号的++、−,可以发现它的右括号前面是空的,也就是说必须左右括号紧挨着才可以,写一个组合数学DPDP不是什么难事。

做这道题的时候莫名想起了pruferprufer数列,每一棵有标号无根树都可以转化为唯一的pruferprufer数列,而每一个pruferprufer数列都可以转化为唯一的有标号无根树,而树不容易计数,但pruferprufer数列却很好计数,这样一转化就简单多了。

这题也是一样,把不好计数的UAEUAE转化为比较容易计数的括号序列,好神啊……

PS:PS:我还是太弱了,看了题解还是WA了,忘了特判末尾有运算符的无解情况……

Code:Code:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 4005
#define mod 1000003
char S[N];
int n,m,v[N],f[N][N];
int main()
{
    scanf("%s",S+1);
    n=strlen(S+1);
    for(int i=1;i<=n;i++)
    {
        if(S[i]=='+'||S[i]=='-')
        {
            m++;
            if(S[i-1]<'0'||S[i-1]>'9')
                v[m]=1;
        }
        if(S[i]=='*'||S[i]=='/')
        {
            m++;
            if(S[i-1]<'0'||S[i-1]>'9')
            {
                puts("0");
                return 0;
            }
        }
    }
    if(S[n]<'0'||S[n]>'9')
    {
        puts("0");
        return 0;
    }
    f[0][0]=1;
    for(int i=0;i<=2*m;i++)
    {
        for(int j=0;j<=min(m,i);j++)
        {
            int k=i-j;
            if(j<k)continue;
            if(j<m)
                f[i+1][j+1]=(f[i+1][j+1]+f[i][j])%mod;
            if(j>0)
            {
                if(v[k+1])
                    f[i+1][j]=(f[i+1][j]+f[i-1][j-1])%mod;
                else
                    f[i+1][j]=(f[i+1][j]+f[i][j])%mod;
            }
        }
    }
    printf("%d\n",f[2*m][m]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值