8.21模拟赛

Victory
【问题描述】
迟到大王rsw喜欢V这个字母,因为V代表着victory,当一个数字,从左向右数的时候,没有出现过先递减,再递增的情况,就被称作Victory数。
也就是说,这个数字可以是递增的,也可以是递减的,也可以是先递减再递增的,这个过程中可以出现相邻数字相等的情况,但是,就是不能出现过先递减再递增的情况。
问题是:给定n,问:1~n之间有多少个victory数。
【输入格式】
一行,一个数字n。
【输出格式】
一行,一个数字,表示答案mod 1 000 000 007
【样例输入】
120
【样例输出】
119
【数据范围】
对于30%的数据,1<=n<=108
对于100%的数据,1<=n<=10100

题解:
一道数位dp题,用到记忆化搜索,一看范围十的一百次方,肯定按位做,对于每一位,如果它的上一位没有卡到上界,那么这一位随意,如果上一位卡了上届,那么这一位也需要卡上界,注意先开始相等算下降的。
dp[i][j][k]表示位置为i的数字,当前位是什么,目前是持续上升还是持续下降的。
考试时用的递推,结果写太复杂导致多卡了一些情况GG。


#include <cstdio>
#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
#define ll long long
#define mod 1000000007
char num[200];
ll dp[150][15][5];
ll wei[150];
int len;
ll dfs(int pos, int pre, int now, int flag)
{
    if (pos > len - 1)
    {
        if (now == 4)
            return 0;
        return 1;
    }
    if (!flag && dp[pos][pre][now] != -1)
        return dp[pos][pre][now];
    ll up, sum = 0;
    if (flag == 0)
        up = 9;
    else
        up = wei[pos];
    for (int i = 0; i <= up; i++)
    {
        if (now == 3 && pre <= i)
        {
            sum = (sum + dfs(pos + 1, i, now, flag && i == up) % mod) % mod;
        }
        if (now == 2)
        {
            if (pre >= i)
                sum = (sum + dfs(pos + 1, i, now, flag && i == up) % mod) % mod;
            else
                sum = (sum + dfs(pos + 1, i, 3, flag && i == up) % mod) % mod;
        }
        if (now == 4)
        {
            if (i == 0)
                sum = (sum + dfs(pos + 1, i, now, flag && i == up) % mod) % mod;
            else
                sum = (sum + dfs(pos + 1, i, 2, flag && i == up) % mod) % mod;
        }
    }
    if (!flag)
        dp[pos][pre][now] = sum;
    return sum % mod;
}
int main()
{
    memset(dp, -1, sizeof(dp));
    cin >> num;
    len = strlen(num);
    for (int i = 0; i < len; i++)
    {
        wei[i] = num[i] - '0';
    }
    ll ans = dfs(0, 0, 4, 1) % mod;
    printf("%lld\n", ans);
}

技能大赛
【问题描述】
rsw因为迟到次数太多被列入黑名单,于是被派去参加陕西妇女儿童技能大赛,大赛中共安排了m个比赛项目,算上rsw在内,共有n位选手报名参加本次比赛。(如rsw,zrx,kh,ljm,cky,大耳朵图图,大头儿子等)
经过m场比赛,组委会发现,每个项目,有且仅有两个人实力超群。(比如穿针引线项目,rsw,ljm独领风骚,健美操项目,cky,cjy风姿绰约)。
现在,组委会要推选一些人去参加全国比赛,因为每个项目都必须有人擅长,所以推选的这些人,对于每一个项目,至少要有一个人擅长。
已知,选中每个人参加比赛是有一个费用a[i],比如选中了1,3,5三个人参加比赛,就要支付a[1]*a[3]*a[5]的费用。
现在的问题是:组委会所有可行选人方案的费用总和是多少?

【输入格式】
第一行n,m,q。
接下来1行n个数字a[1]~a[n]。
接下来m行,每行2个数字u,v,代表u和v两个人共同擅长第i个项目。
【输出格式】
一行,1个数字,表示最后的结果mod q。
【样例输入】
3 2 998244353
1 1 1
1 2
2 3
【样例输出】
5
【样例解释】
合法情况有:[1,2],[1,3],[1,2,3],[2],[2,3]五种,每种的费用都是1,所以结果是1+1+1+1+1=5。
【数据范围】
对于30%的数据,n<=20
对于70%的数据,n<=28
对于100%的数据,n<=36,q<=109
题解:
本体题意对一个一般图,求一个点集,使得所有的边都被覆盖,看范围发现,2的36次方过不去,而2的18次方能过,发现这又是一道折半搜索的题,对于左边集合,如果有一边的左端点没选,那么一定要选右端点,并且分为在左边集合,右边集合,还是跨两边集合的情况,对于右边,因为我们需要当前状态的总代价,于是,用到高位前缀合的办法,只需要在还是0的位置,异或成1即可,即

for (int j = 0; j < re; j++)
        for (int st = 0; st < (1 << re); st++)
            if (~st >> j & 1)
                sum[st] = (sum[st] + sum[st | (1 << j)]) % q;

代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
#define LL long long
const int maxn = 18;
int n, m, q, u, v, a[maxn << 1], sum[1 << maxn], ll[maxn], lr[maxn], rr[maxn], need;
LL now, res;
int main()
{
    cin >> n >> m >> q;
    for (int i = 0; i < n; i++)
        scanf("%d", &a[i]);
    int le = (n + 1) / 2, re = n - le;
    for (int i = 1; i <= m; i++)
    {
        scanf("%d%d", &u, &v);
        u--, v--;
        if (u > v)
            swap(u, v);
        if (u < le)
        {
            if (v < le)
                ll[u] |= (1 << v);
            else
                lr[u] |= (1 << (v - le));
        }
        else
            rr[u - le] |= (1 << (v - le));
    }
    for (int st = 0; st < (1 << re); st++)
    {
        LL now = 1;
        for (int j = 0; j < re; j++)
        {
            if (st >> j & 1)
                now = now % q * a[j + le] % q;
            else
                now *= ((rr[j] | st) == st);
        }
        sum[st] = now;
    }
    for (int j = 0; j < re; j++)
        for (int st = 0; st < (1 << re); st++)
            if (~st >> j & 1)
                sum[st] = (sum[st] + sum[st | (1 << j)]) % q;
    int res = 0;
    for (int st = 0; st < (1 << le); st++)
    {
        LL now = 1;
        int need = 0;
        for (int j = 0; j < le; j++)
        {
            if (st >> j & 1)
                now = now % q * a[j] % q;
            else
                now *= ((ll[j] | st) == st), need |= lr[j];
        }
        res = (res + now % q * sum[need] % q) % q;
    }
    printf("%d\n", res);
}

冒泡排序2
【问题描述】
前一天的冒泡排序对rsw来说太简单了,所以又有了冒泡排序2,给定n,k,q,问:有多少个不同的1~n的排列,能够使得,冒泡排序k趟后,得到一个几乎正确的序列。
一个几乎正确的序列指的是:它的最长上升子序列的长度至少是n-1。

【输入格式】
一行,n,k,q
【输出格式】
一个数字,表示答案mod q。
【样例输入】
5 2 998244353
【样例输出】
114
【数据范围】
对于30%的数据,n<=10
对于100%的数据,n,k<=50,q<=109

题解:
首先我们考虑,如何使一个1~n的排列变成最长上升子序列大于等于n-1,考虑发现当把一端区间循环左移或右移一位,即能变成结果序列,例如:1 2 3 4 5 6 7 8 9 (k==3)将3 4 5 6变换后,能过形成1 2 6 3 4 5 7 8 9 或1 2 4 5 6 3 7 8 9,由冒泡排序一,我们能知道结果序列中当前第i位,是剩下的i+k中的最小值,那么我们同理,知1原来有四种,2有四种,6有四种,而3却只能放在7处,否则此位应该是3,剩下的4 5同理,最后的7 8 9,即3*2*1种方法。所以当前向→移一位总结得到,每次变换一个长度为i的序列,他的情况值即就是k的阶乘×(n-k+1-i)×(k+1)的(n-k+1-i)次方,另一种变换方式即向左移一位,即只会有一个数字被固定,即k的阶乘乘以(n-k+1-i)*(k+1)的(n-k-1)次方。
其系数n-k+1-i,即就是在长度为n-k+1的序列中,只能找到n-k+1-i种方式。
第二次枚举长度注意从3开始,因为位置为2时,向左向右移一位是一样的。

代码很短很短,思路很毒很毒:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#define ll long long
using namespace std;
ll pw[55],n,k,q;
int main()
{
    cin>>n>>k>>q;
    k=min(n,k);
    pw[0]=1;
    for(int i=1;i<=n;i++) pw[i]=(k+1)*pw[i-1]%q;
    ll res=pw[n-k];
    for(int i=2;i<=n-k;i++) res=(res+(n-k+1-i)*pw[n-k+1-i]%q)%q;
    for(int i=3;i<=n-k;i++) res=(res+(n-k+1-i)*pw[n-k-1]%q)%q;
    for(int i=1;i<=k;i++)
    res=res*i%q;
    printf("%lld",res);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值