首页 > 其他分享 >CSP历年复赛题-P1310 [NOIP2011 普及组] 表达式的值

CSP历年复赛题-P1310 [NOIP2011 普及组] 表达式的值

时间:2024-05-30 17:11:15浏览次数:27  
标签:方案 cnt str NOIP2011 int 操作数 运算符 P1310 CSP

原题链接:https://www.luogu.com.cn/problem/P1310

题意解读:+代表按位或运算,*代表按位与运算,给定一个没有填数字的表达式,要求结果为0的数字方案数。

解题思路:

下面一步一步,由浅入深的来解决本题

思路一(20分做法):

观察得知,20%的数据,只有10个符号,且没有括号,也就是对应数字最多11个,可以考虑DFS枚举所有长度是n+1的01数字组合,然后根据运算符、数字进行表达式计算。

需要注意的点是,对于中序表达式,在求值的时候,需要使用两个栈:运算符栈、操作数栈,主要流程如下:

1、遍历操作符和操作数

2、如果操作符栈为空,则将操作符和对应两个操作数入栈

3、如果操作符栈不为空,且当前操作符优先级不高于栈顶操作符的优先级,则将栈顶操作符和对应的两个操作数弹出进行运算,结果入操作数栈

4、再把当前操作符和下一个操作数分别入栈

5、最后,对栈中剩余的操作符和操作数进行一遍运算

6、结果即操作数栈顶元素

20分代码:

#include <bits/stdc++.h>
using namespace std;

const int MOD = 10007;
int n;
string str;
int a[15]; //n+1个0/1数字的组合
int ans = 0;

map<char, int> priority = {{'+', 1} , {'*', 2}};

int eval()
{
    stack<char> op; //运算符栈
    stack<int> num; //数字栈
    for(int i = 0; i < str.size(); i++) 
    {
        if(op.empty()) //运算符栈为空,则将运算符,左右操作数入栈
        {
            op.push(str[i]);
            num.push(a[i]);
            num.push(a[i + 1]);
            continue;
        }
        //如果运算符栈不空,且当前运算符优先级不高于栈顶运算符优先级
        //则弹出栈顶运算符和相应数字,进行计算,之后再把当前运算符和右操作数入栈
        if(op.size() && priority[str[i]] <= priority[op.top()]) 
        {
            char c = op.top(); op.pop();
            int a = num.top(); num.pop();
            int b = num.top(); num.pop();
            int res;
            if(c == '*') res = a & b;
            else res = a | b;
            num.push(res);
        }
        op.push(str[i]);
        num.push(a[i + 1]);
    }
    //最后对栈中剩余运算符和操作数进行计算
    while(op.size())
    {
            char c = op.top(); op.pop();
            int a = num.top(); num.pop();
            int b = num.top(); num.pop();
            int res;
            if(c == '*') res = a & b;
            else res = a | b;
            num.push(res);
    }
    //最终结果为栈顶数字
    return num.top();
}   

void dfs(int k)
{
    if(k == n + 1) //枚举到一组n + 1个0、1的数的组合
    {
        if(eval() == 0) ans++; //计算表达式的值
        return;
    }
    a[k] = 0;
    dfs(k + 1);

    a[k] = 1;
    dfs(k + 1);
}

int main()
{
    cin >> n >> str;
    dfs(0);
    cout << ans % MOD;
    return 0;
}

思路2(50分做法):

由于前50%的数据长度不超过1000,且没有括号,可以利用组合数的思想:

对于一个+*组成的表达式,要为0,必须+对应的所有操作数都为0

表达式中可能有多个连续的*,形如+*+**+,完整表达式为_+_*_+_*_*_+_

结果为0,则有_ , _*_, _*_*_, _这四个都是0

_,代表一个数,为0的方案数只有1种

_*_,代表两个数做与运算,为0的方案有01,10,00 3种,也就是2个数,其中有可能有1个0,2个0,对应的方案数为C(2,1)+C(2,2) = 3种

_*_*_,代表三个数做与运算,为0的方案有C(3,1)+C(3,2)+C(3,3) = 7种

_,代表一个数,为0的方案只有1种

所以,_+_*_+_*_*_+_表达式为0的方案数为1 * 3 * 7 * 1 = 21种

基于以上分析,只需要找到表达式中的连续的*,每cnt个*,代表有cnt+1个数做与运算,为0的方案是C(cnt+1,1)+C(cnt+1,2)+...+C(cnt+1,cnt+1)

所有连续*的方案数相乘即可。

注意需要对组合数进行预处理。

50分代码:

#include <bits/stdc++.h>
using namespace std;

const int MOD = 10007;
int n;
string str;
int ans = 1;

int c[1005][1005];

int main()
{
    cin >> n >> str;

    //初始化组合数
    for(int i = 0; i <= 1000; i++)
    {
        for(int j = 0; j <= i; j++)
        {
            if(j == 0) c[i][j] = 1;
            else c[i][j] = (c[i-1][j-1] + c[i-1][j]) % MOD;
        }
    }

    int cnt = 0; 
    for(int i = 0; i < str.size(); i++)
    {
        if(str[i] == '*') cnt++;
        else
        {
            //处理连续cnt个*
            if(cnt > 0)
            {
                int sum = 0; //cnt个*对应的表达式计算为0的方案数,也就是cnt+1个数有几个1~cnt+1个0的方案数:c(cnt,0)+c(cnt,1)+...+c(cnt,cnt)种
                for(int j = 1; j <= cnt + 1; j++)
                {
                    sum += c[cnt+1][j]; sum %= MOD;
                }   
                ans *= sum; ans %= MOD;
                cnt = 0;
            }
        }
    }
    if(cnt > 0)
    {
        int sum = 0; //cnt个*对应的表达式计算为0的方案数,也就是cnt+1个数有几个1~cnt+1个0的方案数:c(cnt,0)+c(cnt,1)+...+c(cnt,cnt)种
        for(int j = 1; j <= cnt + 1; j++)
        {
            sum += c[cnt+1][j]; sum %= MOD;
        }   
        ans *= sum; ans %= MOD;
        cnt = 0;
    }
    cout << ans;

    return 0;
}

思路3(100分做法):

如果知道了中缀表达式的计算方法,也知道了此题跟组合数的关系,更进一步可以得出:

如果要计算结果是0的方案数

a+b是0的方案数 = a是0的方案数 * b是0的方案数

a*b是0的方案数 = a是0的方案数 * b是0的方案数 + a是0的方案数 * b是1的方案数 + a是1的方案数 * b是0的方案数

因此,我们可以借助表达式求值的代码流程,但实际进行计算是,不是具体计算两个数的+或者*,而是递推的计算

{结果是0的方案数、结果是1的方案数},

操作数栈存的就是一个结构体

struct node 
{
    int cnt0; //结果为0的方案数
    int cnt1; //结果为1的方案数
};

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int MOD = 10007;
int n;
string str;

struct node
{
    int cnt0; //结果为0的方案数
    int cnt1; //结果为1的方案数
};

stack<char> op;
stack<node> num;

map<char, int> priority = {{'+', 1} , {'*', 2}}; //运算符的优先级


void eval()
{
    char c = op.top(); op.pop();
    node a = num.top(); num.pop();
    node b = num.top(); num.pop();
    node res;
    if(c == '+')
    {
        //两边都是0,+才是0
        res.cnt0 = (a.cnt0 * b.cnt0) % MOD; 
        //有一边是1,+就是1
        res.cnt1 = (a.cnt0 * b.cnt1 + a.cnt1 * b.cnt0 + a.cnt1 * b.cnt1) % MOD; 
    } 
    else if(c == '*')
    {
        //有一边是0,*就是0
        res.cnt0 = (a.cnt0 * b.cnt1 + a.cnt1 * b.cnt0 + a.cnt0 * b.cnt0) % MOD;
        //两边都是1,*才是1
        res.cnt1 = (a.cnt1 * b.cnt1) % MOD;
    }
    num.push(res);
}

int main()
{
    cin >> n >> str;

    num.push({1, 1}); //第一个操作数,只有1位,结果为0和为1的方案数都是1
    for(int i = 0; i < str.size(); i++)
    {
        if(str[i] == '(') op.push(str[i]);
        else if(str[i] == ')')
        {
            while(op.top() != '(') eval();
            op.pop(); //弹出')'
        }
        else 
        {
            while(op.size() && op.top() != '(' && priority[str[i]] <= priority[op.top()])
                eval();
            op.push(str[i]); //存入当前运算符
            num.push({1, 1}); //存入右操作数
        }
    }
    while(op.size()) eval(); //处理栈中剩余操作符

    cout << num.top().cnt0; //数据栈的结果0的方案数即答案

    return 0;
}

 

标签:方案,cnt,str,NOIP2011,int,操作数,运算符,P1310,CSP
From: https://www.cnblogs.com/jcwy/p/18222771

相关文章

  • CSP历年复赛题-P1308 [NOIP2011 普及组] 统计单词数
    原题链接:https://www.luogu.com.cn/problem/P1308题意解读:给定单词a,文本b,在b中找a的个数,并找a第一次出现的位置,注意b中任何位置可能含有多个连续空格。解题思路:通过双指针找b中每一个单词的首、尾位置i,j,与a进行一一比较即可。注意1:比较时不考虑大小写,可以统一转成小写字符tolo......
  • CSP历年复赛题-P1199 [NOIP2010 普及组] 三国游戏
    原题链接:https://www.luogu.com.cn/problem/P1199题意解读:人机轮流选将,电脑策略就是破坏可能和人已选能组成最大默契值的将,问人是否必胜,求出站的一对武将的默契值。解题思路:贪心题通常比较难以下手,经过分析,人肯定不可能选到每一行的最大默契值,因为电脑会破坏;进一步思考,那人能......
  • CCF-CSP真题《202403-1 词频统计》思路+python满分题解
    哇q(≧▽≦q),第一次写博客,请大家多多关照○| ̄|_ 看到没啥人提供202403的第一题解题思路及python代码,刚好写完,心血来潮想分享解题思路,就写下了这篇博客,有其他的编码版本,欢迎大家一起探讨呀(虽然我是算法菜鸟┗(T﹏T)┛,但有问题,我会尽力回答的!!!)好了废话不多说,上解题思路!大概想了......
  • 【CSP】202012-2 期末预测之最佳阈值
    2020年第21次CCF计算机软件能力认证  202012-2 期末预测之最佳阈值原题链接:期末预测之最佳阈值时间限制: 1.0秒空间限制: 512MiB目录题目背景题目描述输入格式输出格式样例1输入样例1输出样例1解释样例2输入样例2输出子任务解题思路AC代码期末预测之安......
  • CSP历年复赛题-P1190 [NOIP2010 普及组] 接水问题
    原题链接:https://www.luogu.com.cn/problem/P1190题意解读:n个人在m个水龙头排队接水,每个人接水量不同,接完水的排队的人可以接上,求总的接水时间。解题思路:1、先把前m个人安排在m个水龙头2、对于m后面的每一个人,都排在目前m个水龙头总接水时间最短的后面3、最后看m个水龙头最大......
  • CSP历年复赛题-P1179 [NOIP2010 普及组] 数字统计
    原题链接:https://www.luogu.com.cn/problem/P1179题意解读:统计l~r之间的整数包括多少个数字2。解题思路:枚举每一个数,对每一个数的每一位数字进行判断。100分代码:#include<bits/stdc++.h>usingnamespacestd;intl,r,ans;intmain(){cin>>l>>r;f......
  • CSP历年复赛题-P1058 [NOIP2008 普及组] 立体图
    原题链接:https://www.luogu.com.cn/problem/P1058题意解读:在m*n的平面上,输出若干个立方体,每一个格子可以有高度不同的多个立方体。解题思路:此题咋一看来,无从下手,仔细分析,其实一道模拟题。如何模拟?我们一起来解决一下几个关键问题:1、如何画图?要在平面上输出图案,本质上就是将......
  • CSP历年复赛题-P1067 [NOIP2009 普及组] 多项式输出
    原题链接:https://www.luogu.com.cn/problem/P1067题意解读:模拟法依次输出多项式内容即可,但是需要考虑的周全,主要有以下关键点:1、系数为0时不输出多项式2、第一个符号,只有负号才输出3、次数不为0时,不输出为1的系数;次数为0时,输出所有系数4、次数为1时,不输出次数;次数为0时不输......
  • 利用PyCSP3库(含大量全局约束)进行组合约束建模
    文章目录1.什么是PyCSP3?2.安装方法(Windows)2.1通过Google_colab直接运行2.2通过pip进行安装3.快速入门3.1声明变量3.2更新约束3.3定义目标3.4常用的全局约束1.什么是PyCSP3?PyCSP3是Python中的一个库,用于对组合约束问题进行建......
  • CSP历年复赛题-P1057 [NOIP2008 普及组] 传球游戏
    原题链接:https://www.luogu.com.cn/problem/P1057题意解读:n个人围一圈,从1开始传球m次,每次可以往左或右传,计算球再次传给1的方案数。解题思路:求方案数,通常就是DP问题,此题DP状态并不难想,如果实在不会,也可以通过DFS暴搜得部分分。1、DFS60分代码:#include<bits/stdc++.h>using......