[18.8.21]校内NOIP模拟赛

T1 Victory

题意

记得是16年百度之星初赛原题。

问有多少$n$以内的正整数$x$,满足没有峰。

峰的定义是,存在一个$x$,使能找到一对$i,j(i<x<j)$,满足$s_i<s_x,s_j<s_x$。

$1\leq n \leq 10^{100}$

题解

显然是数位dp。

$dp_{i,j,st1,st2}$表示当前到了第$i$位,上一位是$j$,前缀与原数前缀是否相等,当前是处于上升还是下降。

对于初值:由于不考虑前导零,所以可以枚举位数和最高位的数字。由于处于下降状态可以变为上升,而上升无法变为下降,所以可以认为初始情况处于下降状态。

转移显然。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
template<typename __T>
inline void read(__T &x)
{
    x=0;
    int f=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')   f=-1;c=getchar();}
    while(isdigit(c))   {x=x*10+c-'0';c=getchar();}
    x*=f;
}
const int mod=1000000007;
char str[105];
int s[105];
int n;
int dp[105][2][2][10];
int main()
{
    scanf("%s",str);
    n=strlen(str);
    for(int i=0;i<n;i++)
        s[i]=str[n-i-1]-'0';
    for(int i=1;i<s[n-1];i++)    dp[n-1][0][0][i]=1;
    dp[n-1][1][0][s[n-1]]=1;
    for(int i=0;i<n-1;i++)
        for(int j=1;j<=9;j++)
            dp[i][0][0][j]=1;
    for(int i=n-1;i>0;i--)
    {
        for(int j=0;j<=9;j++)
        {
            if(dp[i][0][0][j])
            {
                for(int k=0;k<=j;k++)
                    (dp[i-1][0][0][k]+=dp[i][0][0][j])%=mod;
                for(int k=j+1;k<=9;k++)
                    (dp[i-1][0][1][k]+=dp[i][0][0][j])%=mod;
            }
            if(dp[i][0][1][j])
                for(int k=j;k<=9;k++)
                    (dp[i-1][0][1][k]+=dp[i][0][1][j])%=mod;
        }
        int j=s[i];
        int ne=s[i-1];
        if(dp[i][1][0][j])
        {
            if(ne<=j)
            {
                for(int k=0;k<=(ne-1);k++)
                    (dp[i-1][0][0][k]+=dp[i][1][0][j])%=mod;
                (dp[i-1][1][0][ne]+=dp[i][1][0][j])%=mod;
            }
            else
            {
                for(int k=0;k<=j;k++)
                    (dp[i-1][0][0][k]+=dp[i][1][0][j])%=mod;
                for(int k=j+1;k<ne;k++)
                    (dp[i-1][0][1][k]+=dp[i][1][0][j])%=mod;
                (dp[i-1][1][1][ne]+=dp[i][1][0][j])%=mod;
            }
        }
        if(dp[i][1][1][j])
        {
            for(int k=j;k<=min(9,ne-1);k++)
                (dp[i-1][0][1][k]+=dp[i][1][1][j])%=mod;
            if(ne>=j)
                (dp[i-1][1][1][ne]+=dp[i][1][1][j])%=mod;
        }
    }
    long long ans=0;
    for(int i=0;i<=9;i++)
        ans=(ans+dp[0][0][0][i]+dp[0][0][1][i]+dp[0][1][0][i]+dp[0][1][1][i])%mod;
    printf("%lld\n",ans);
    return 0;
}

T2 技能大赛

题意

给你一张有$n$个点,$m$条边的图,求所有满足条件的点集的价值之和。

满足条件是指,每条边的至少一个顶点被选中。

点集的价值是所有点的乘积。

$1\leq n \leq 36$。

题解

显然,这题可以用折半搜索。

具体来讲,先将所有点分成左右两类,对于左边的一类,枚举选择的方案,之后计算两个顶点均在左侧的边是否满足条件。如果满足条件,再求出右侧至少要有哪些点需要被选中。显然,这个右侧最小集合的所有超集都是满足条件的。

因此可以先求出右侧点中,所有内部边都被满足的选择方案,之后做一个类似子集和变换(高维前缀和?)的操作。然后对左侧点进行枚举,算出对右侧的依赖,之后直接累加答案(答案变化量为左侧的价值乘右侧所有方案的价值和,结论显然)。

一个结论是,合法的选择方案,一定满足:对于所有未被选中的点,其直接连点均被选中。

因此我们可以处理一个掩码,表示哪些点与这些点链接,在判断的时候就可以做到$O(1)$判断了。

所以总时间复杂度是$O(m+2^nn)$。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
template<typename __T>
inline void read(__T &x)
{
    x=0;
    int f=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')   f=-1;c=getchar();}
    while(isdigit(c))   {x=x*10+c-'0';c=getchar();}
    x*=f;
}
const int magi=18;
int mod;
int n,m;
bool mp[50][50];
long long mask[50];
long long ans=0;
long long cnt[1<<18];
int aa[50];
int main()
{
    read(n);
    read(m);
    read(mod);
    int a,b;
    for(int i=0;i<n;i++)
        read(aa[i]);
    for(int i=0;i<m;i++)
    {
        read(a);
        read(b);
        a--;
        b--;
        mp[a][b]=mp[b][a]=1;
        mask[a]|=(1ll<<b);
        mask[b]|=(1ll<<a);
    }
    if(n<=20)
    {
        for(int st=0;st<(1<<n);st++)
        {
            bool fg=0;
            long long np=1;
            for(int i=0;i<n;i++)
            {
                if((st>>i)&1)
                    np=np*aa[i]%mod;
                if(((st>>i)&1)==0 && (st|mask[i])!=st)
                {
                    fg=1;
                    break;
                }
            }
            if(!fg) (ans+=np)%=mod;
        }
        ans%=mod;
        printf("%lld\n",ans);
        return 0;
    }
    int mmm=(1<<magi)-1;
    for(int st=0;st<(1<<magi);st++)
    {
        bool fg=0;
        long long ans=1;
        for(int i=0;i<magi;i++)
        {
            if((st>>i)&1)
                ans=ans*aa[i]%mod;
            if(((st>>i)&1)==0 && (st|(mask[i]&mmm))!=st)
            {
                fg=1;
                break;
            }
        }
        if(!fg)
            (cnt[st]+=ans)%=mod;
    }
    for(int i=0;i<magi;i++)
        for(int st=0;st<(1<<magi);st++)
            if(((st>>i)&1)==0)    (cnt[st]+=cnt[st|(1<<i)])%=mod;
    int mxx=n-magi;
    for(int st=0;st<(1<<mxx);st++)
    {
        bool fg=0;
        long long np=1;
        for(int i=0;i<mxx;i++)
        {
            if((st>>i)&1)
                np=np*aa[i+magi]%mod;
            if(((st>>i)&1)==0 && (st|(mask[i+magi]>>magi))!=st)
            {
                fg=1;
                break;
            }
        }
        if(fg)  continue;
        int msk=0;
        for(int i=0;i<magi;i++)
            if((st|(mask[i]>>magi))!=st)  msk|=(1<<i);
        (ans+=cnt[msk]*np)%=mod;
    }
    printf("%lld\n",ans);
    return 0;
}

T3 冒泡排序2

题意

给定$n,k$,求有多少种排列满足,进行$k$ 轮冒泡排序外层循环后,数列是“几乎正确”的。

几乎正确的定义是:最长上升子序列至少为$n-1$。

$1\leq n,k \leq 50$。

题解

这题由于最开始特判写假了所以多了些没有用的代码。。

首先考虑,几乎正确的序列(已经排序好的序列),一定可以由如下操作得到:

从一个已经排好序的序列内随意抽出一个数放到其它位置。

在之前已经得到一条结论,进行$k$次操作后的序列,一定是之前剩下的和后面$k$个数的最小值。

显然对于一段已经排好序的序列来讲,每一个数都有$k+1$种位置可以放(除了末尾位置不够的情况)。

考虑如下两种移动数的情况:

如果是将数字$x$向前移,那么被移动的这个数字也是自由的(有$k+1$种位置可以放),但是后面的数字只有$1$种位置可以放置(因为后面的数字比它小,所以只能放到它影响不到的地方,显然只有一个)。同理,后面第二个数字也只有$1$种情况(因为原数字影响不到的地方有两个,但是之前已经占了一个,所以只剩一个)。这种情况直到遇到一个比原数字$x$大的数字。之前这段相当于整体往右侧平移了$k$格,因此必须要满足,大于$x$的数字有至少$k$个。

那么对于所有比$x$大的数字,放置方案是自由的,有$k+1$种(包含了之前空出的一段以及后面新增的部分,末尾可能会不够,此时用阶乘计算,下面同理)。

如果是数字$x$向后移,显然对于所有位置在数字$x$之前的数字,均是自由的。

对于数字$x$来讲,由于$x$前面的数字比它大,所以$x$的原位置必须在上一个数字影响不到的地方(所以当$x$后面的数字不足$k$个的时候不合法),显然只有一种选择。

对于在数字$x$之后的数字,由于均比$x$大,所以也是自由的(排除末尾位置不够)。

注意去重。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
template<typename __T>
inline void read(__T &x)
{
    x=0;
    int f=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')   f=-1;c=getchar();}
    while(isdigit(c))   {x=x*10+c-'0';c=getchar();}
    x*=f;
}
int n,k,mod;
int main()
{
    read(n);
    read(k);
    read(mod);
    k=min(n-1,k);
    long long aans=0;
    for(int l=1;l<=n;l++)
        for(int r=l+1;r<=n;r++)
        {
            long long ans=1;
            if(n-r>=k)
            {
                int rem=n-r+l;
                for(int i=k+1;i<=rem;i++)
                    (ans*=(k+1))%=mod;
                for(int i=1;i<=k;i++)
                    (ans*=i)%=mod;
            	aans=(aans+ans)%mod;
            }
            if(r==l+1)  continue;
            if(r<=n-k)
            {
            	ans=1;
                int rem=n-1;
                for(int i=k+1;i<=rem;i++)
                    (ans*=(k+1))%=mod;
                for(int i=1;i<=k;i++)
                    (ans*=i)%=mod;
            	aans=(aans+ans)%mod;
            }
        }
    int rem=n;
    long long ans=1;
    for(int i=k+1;i<=rem;i++)
        (ans*=(k+1))%=mod;
    for(int i=1;i<=k;i++)
        (ans*=i)%=mod;
    aans=(aans+ans)%mod;
    printf("%lld\n",aans);
    return 0;
}
posted @ 2018-08-21 22:36  ranwen  阅读(288)  评论(0编辑  收藏  举报