[6.27~7.4 做题记录]

[6.27~7.4 做题记录]

暑假集训,记录一些有意义 (多半无意义) 题目,不定时更新。

概率期望DP

OSU!

考虑设 \(E_i\) 为到第 \(i\) 次操作时的期望分数。我们发现从 \(x^3\)\((x+1)^3\) 将加上 \(3x^2+3x+1\),我们考虑 \(x\)\(x^2\) 的期望:

\[x1_i=(x1_{i-1}+1)\times p_i \]

\[x2_i=(x2_{i-1}+2\times x1_{i-1}+1)\times p_i \]

为什么呢,对于 \(x1\),有 \(p_i\) 的概率增加 \(1\)\(x2\) 同理。

那么就有:

\[E_i=E_{i-1}+(3\times x2_{i-1}+3\times x1_{i-1}+1)\times p_i \]

for(int i=1;i<=n;i++){
    x1[i]=(x1[i-1]+1)*p[i];
    x2[i]=(x2[i-1]+2*x1[i-1]+1)*p[i];
    E[i]=E[i-1]+(3*x2[i-1]+3*x1[i-1]+1)*p[i];
}
printf("%.1lf",E[n]);

灯灯灯

逆天推式子数奥题。

\(n\) 盏红灯,\(m\) 盏绿灯,考虑先假设剩下的是红灯,然后绿灯同理。

  • 设剩下 \(k\) 盏红灯,那么剩下的第 \(k+1\) 盏一定为绿灯,那么只有前 \(n+m-k-1\) 盏灯是不确定的,其中有 \(n-k\) 盏红灯,\(m-1\) 盏绿灯,这 \(n+m-k-1\) 盏灯的顺序为 \(\tbinom{n+m-k-1}{n-k}\),而这所有的 \(n+m\) 盏灯有 \(\tbinom{n+m}{n}\) 种顺序,因此剩下 \(k\) 盏红灯的概率为 \(\dfrac{\binom{n+m-k-1}{n-k}}{\binom{n+m}{n}}\)
  • 那么,剩下红灯的期望为:

\[E= \sum _{k=1}^n \dfrac{\dbinom{n+m-k-1}{n-k}\times k}{\dbinom{n+m}{n}} = \dfrac{\sum \limits _{k=1}^n \dbinom{n+m-k-1}{n-k}\times k}{\dbinom{n+m}{n}} \]

  • 考虑化简上式,我们主要利用了两个等式:

    1. \(\tbinom{n}{m}=\tbinom{n-1}{m}+\tbinom{n-1}{m-1}\)
    2. \(\tbinom{n}{0}=\tbinom{n+1}{0}=\tbinom{n-1}{0}\)
  • 分子化简:

    \[= \tbinom{n+m-2}{n-1}\times 1+\tbinom{n+m-3}{n-2}\times 2+\tbinom{n+m-4}{n-3}\times 3 +\dots+\tbinom{n+m-n}{n-n+1}\times (n-1)+\tbinom{n+m-n-1}{n-n}\times n \]

    • 我们发现上式类似一个三角形,最后是 \(\tbinom{m}{1}\times (n-1)+\tbinom{m-1}{0}\times n\)
    • 由式2得 \(\tbinom{m}{1}\times (n-1)+\tbinom{m}{0}\times n\),再由式1得 \(\tbinom{m+1}{1}\times (n-1)+\tbinom{m}{0}\),接着再加上 \(\tbinom{m+1}{2}\times (n-2)\),可得 \(\tbinom{m+2}{2}\times (n-2)+\tbinom{m+1}{1}+\tbinom{m}{0}\)
    • 以此类推,逐层消消消,最后得到 \(\tbinom{m+n-1}{n-1}+\tbinom{m+n-2}{n-2}+\dots+\tbinom{m+2}{2}+\tbinom{m+1}{1}+\tbinom{m}{0}\)
    • 接着,我们由式2得到 \(\tbinom{m}{0}=\tbinom{m+1}{0}\),再用式1一个一个消,不再赘述。消完就是 \(\tbinom{m+n-1}{n}\)
  • 最终得到结果为 \(\dfrac{\dbinom{m+n}{n-1}}{\dbinom{m+n}{n}}=\dfrac{n}{m+1}\)

  • 最后剩下的绿灯同理,即为 \(\dfrac{m}{n+1}\)

  • 答案即 \(\dfrac{n}{m+1}+\dfrac{m}{n+1}\)

double ans=1.0*n/(m+1)+1.0*m/(n+1);
printf("%.6lf",ans);

[NOIP2016 提高组]换教室

首先认真读题,注意各个变量的含义

先用 \(\tt{Floyd}\) 跑出多源最短路,存储在 \(s_{i,j}\) 中。

设计DP,令 \(dp_{i,j,k}\) 表示递推到第 \(i\) 节课程,申请了 \(j\) 节课,以及本节课是否申请了 \((k\in [0,1])\) 的期望路程。哎我真是唐,开始觉得是申请成功j节,觉得m是申请成功的上界。申请是一回事,人家同意是另一回事...

接下来,就是 恶心的 转移方程:

\[dp_{i,j,0}=\min \begin{cases} dp_{i-1,j,0}+s_{c_{i-1},c_i} \\ dp_{i-1,j,1}+p_{i-1}\times s_{d_{i-1},c_i}+(1-p_{i-1})\times s_{c_{i-1},c_i} \end{cases} \]

为什么呢,由于这次不申请,那么此次必定在 \(c_i\) 上课,但是上一次可以申请也可以不申请,不申请则一定是从 \(c_{i-1}\) 出发,若申请则有 \(p_{i-1}\) 的概率从 \(d_{i-1}\) 出发,以及 \(1-p_{i-1}\) 的概率从 \(c_{i-1}\) 出发即可。

\(dp_{i,j,1}\) 同理,不再赘述,大力分讨。

\[dp_{i,j,1}=\min \begin{cases} dp_{i-1,j-1,0}+p_i\times s_{c_{i-1},d_i}+(1-p_i)\times s_{c_{i-1},c_i} \\ dp_{i-1,j-1,1}+p_{i-1}\times p_i\times s_{d_{i-1},d_i}+p_{i-1}\times (1-p_i)\times s_{d_{i-1},c_i}+(1-p_{i-1})\times p_i\times s_{c_{i-1},d_i}+(1-p_{i-1})\times (1-p_i)\times s_{c_{i-1},c_i} \end{cases} \]

CODE
#include<bits/stdc++.h>
using namespace std;
#define read read()
#define pt puts("")
inline int read{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
#define T 2010
#define N 310
const int inf = 1e8;
int t,m,n,e;
int c[T],d[T];
double p[T];
int s[N][N];
void Floyed(){
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(s[i][k]+s[k][j]<s[i][j])
                    s[i][j]=s[i][k]+s[k][j];
}
double dp[T][T][2];
signed main()
{

    t=read,m=read,n=read,e=read;
    for(int i=1;i<=t;i++)  c[i]=read;
    for(int i=1;i<=t;i++)  d[i]=read;
    for(int i=1;i<=t;i++)  scanf("%lf",&p[i]);
    for(int i=1;i<=n;i++)  for(int j=1;j<=n;j++)  s[i][j]=(i==j?0:inf);
    for(int i=1,a,b,w;i<=e;i++){
        a=read,b=read,w=read;
        s[a][b]=s[b][a]=min(s[a][b],w);
    }
    Floyed();
    for(int i=1;i<=t;i++)
        for(int j=0;j<=t;j++)
            dp[i][j][0]=dp[i][j][1]=1e8;
    dp[1][0][0]=dp[1][1][1]=0;
    for(int i=2;i<=t;i++){
        for(int j=0;j<=min(i,m);j++){
            if(!j)
                dp[i][0][0]=dp[i-1][0][0]+s[c[i-1]][c[i]];
            else{
                dp[i][j][0]=min(dp[i-1][j][0]+s[c[i-1]][c[i]],dp[i-1][j][1]+(1-p[i-1])*s[c[i-1]][c[i]]+p[i-1]*s[d[i-1]][c[i]]);
                dp[i][j][1]=min(dp[i-1][j-1][0]+p[i]*s[c[i-1]][d[i]]+(1-p[i])*s[c[i-1]][c[i]],dp[i-1][j-1][1]+p[i-1]*p[i]*s[d[i-1]][d[i]]+p[i-1]*(1-p[i])*s[d[i-1]][c[i]]+(1-p[i-1])*p[i]*s[c[i-1]][d[i]]+(1-p[i-1])*(1-p[i])*s[c[i-1]][c[i]]);
            }
        }
    }
    double ans=1e9;
    for(int i=0;i<=m;i++)
        ans=min(ans,min(dp[t][i][0],dp[t][i][1]));
    printf("%.2lf",ans);
    return 0;
}

[SCOI2008]奖励关

由于正推灰常不好推,我们考虑倒推。

发现 \(n\leq 15\),那么状压。

\(dp_{i,t}\) 表示从第 \(1\) 轮到第 \(i-1\)取过的宝物状压为 \(t\),下面从第 \(i\) 轮到第 \(k\)的最大期望分值。

那么当符合吃宝物 \(j\) 条件时,\(dp[i][t]=\max(dp_{i+1,t},dp_{i+1,t|2^j}+p_j)\)

最后 \(dp_{1,0}\) 即为所求。

我们发现,大多数概率期望DP都可以通过从唯一的末状态倒推回不确定的初状态。

CODE
#include<bits/stdc++.h>
using namespace std;
#define read read()
#define pt puts("")
inline int read{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
int k,n;
int p[16];
int s[16];
#define MAX ((1<<n)-1)
double dp[110][1<<16];
signed main()
{
    k=read,n=read;
    for(int x,i=1;i<=n;i++){
        p[i]=read;
        x=read; while(x) s[i]|=1<<(x-1),x=read;
    }
    for(int i=k;i>=1;--i){
        for(int t=0;t<=MAX;t++){
            for(int j=1;j<=n;j++){
                if((t|s[j])==t)  dp[i][t]+=max(dp[i+1][t],dp[i+1][t|(1<<(j-1))]+p[j]);
                else  dp[i][t]+=dp[i+1][t];
            }
            dp[i][t]=dp[i][t]/n;
        }
    }
    printf("%.6lf",dp[1][0]);
    return 0;
}

[NOI2005]聪聪与可可

一眼最短路,\(\tt{SPFA}\)(Wang54321称这是 \(\tt{BFS}\),称 \(\tt{SPFA}\) 碰瓷) 或者 \(\tt{Dijkstra}\) 堆优化,处理出多源最短路 \(s_{i,j}\),并处理出 \(nxt_{i,j}\) 表示猫在 \(i\),鼠在 \(j\) 时,猫靠近鼠的下一步应去往的位置。

进行 DP,还是一样,倒推 DP,设 \(dp_{i,j}\),表示猫在 \(i\),鼠在 \(j\),猫抓到鼠的期望时间。

考虑进行转移:

  • 若此时猫鼠在一块,说明已经抓到。
  • 若此时猫鼠距离小于等于2,则一步就可抓到。
  • 否则,猫一定要向鼠走两步,即移动到 \(nxt_{nxt_{i,j},j}\),然后继续搜索,鼠会以 \(\frac{1}{k_j+1}\) 的概率走到相邻节点或是不走,直接求和即可,记得此时初始值为 \(1\),并记得加上留在原位的情况。
CODE
#include<bits/stdc++.h>
using namespace std;
#define read read()
#define pt puts("")
inline int read{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
const int N = 1010;
const int inf = 1e8;
int n,m;
int cat,rat;
struct EDGE{
    int next,to;
}e[N<<1];
int head[N],tot;
void add(int u,int v){
    e[++tot]={head[u],v};
    head[u]=tot;
}
double dp[N][N];
int k[N];
int dis[N][N],nxt[N][N];
queue<int>q;
bool vis[N];
void SPFA(){
    for(int i=0;i<=n;i++)
        for(int j=0;j<=n;j++)
            dis[i][j]=nxt[i][j]=inf;
    for(int i=1;i<=n;i++){
        q.push(i);
        dis[i][i]=0;vis[i]=1;
        while(!q.empty()){
            int x=q.front();q.pop();
            vis[x]=0;int y;
            for(int j=head[x];j;j=e[j].next){
                y=e[j].to;
                if(dis[i][x]+1<dis[i][y]){
                    dis[i][y]=dis[i][x]+1;
                    if(!vis[y])  vis[y]=1,q.push(y);
                }
            }
        }
    }
}
void init(){
    for(int x=1,y;x<=n;x++){
        for(int i=head[x];i;i=e[i].next){
            int j=e[i].to;
            for(int y=1;y<=n;y++){
                if(dis[j][y]+1==dis[x][y]){
                    nxt[x][y]=min(nxt[x][y],j);
                }
            }
        }
    }
}

double DP(int u,int v){
    if(dp[u][v])  return dp[u][v];
    if(!dis[u][v])  return 0;
    if(dis[u][v]<=2)  return 1;
    dp[u][v]=1;
    int t=nxt[nxt[u][v]][v];
    for(int i=head[v];i;i=e[i].next){
        int p=e[i].to;
        dp[u][v]+=DP(t,p)/(k[v]+1);
    }
    dp[u][v]+=DP(t,v)/(k[v]+1);
    return dp[u][v];
}

signed main()
{
    n=read,m=read;
    cat=read,rat=read;
    for(int a,b,i=1;i<=m;i++){
        a=read,b=read;add(a,b),add(b,a);
        k[a]++;k[b]++;
    }
    SPFA();
    init();
    double ans=DP(cat,rat);
    printf("%.3lf",ans);
    return 0;
}

[SHOI2014]概率充电器

大难题,贺了

首先,对于两件事情 \(A,B\),发生概率分别为 \(P(A),P(B)\),那么至少发生 \(A,B\) 其中一种的概率为:

\[P(AB)=P(A)+P(B)-P(A)*P(B) \]

由于每个概率对期望的贡献都乘1,因此答案就是每个节点来电概率之和。

那么我们考虑分讨,某节点的来电情况:

  1. 自己来电
  2. 儿子给电
  3. 父亲给电

规定 \(dp_i\) 表示节点 \(i\) 来电的概率,\(p_i\) 表示节点 \(i\) 自己来电的的概率,\(p(i,j)\) 表示 \(i,j\) 之间导线导电的概率。

考虑树形 DP,进行两次 \(\tt{DFS}\),第一次将儿子的信息传到父亲,即 \(up\);第二次将父亲信息传到儿子,即 \(down\)

  • \(up\)

    • 处理第一种情况,令 \(dp_i\) 的初始值为 \(p_i\) 即可。
    • 处理第二种情况,考虑从儿子回溯时,加上儿子的贡献,即 \(dp_x=dp_x+dp_y\times p_{x,y}\),当然,此处的 \(+\) 应该是上文提到的 \(P(AB)=P(A)+P(B)-P(A)*P(B)\)
  • \(down\)

    • 处理第三种情况,首先求出父节点来电的概率 \(p_a\),我们发现由 \(up\) 可以得 \(p_a+p_b-p_a\times p_b=dp_x\),此时 \(p_b\) 仍为 \(dp_y\times p_{x,y}\),但是由于我们从上往下跑,此时 \(dp_x\) 已经包含了第三种情况,因此我们可以直接处理 \(p_a=\dfrac{dp_x-p_b}{1-p_b}\),然后 \(dp_y\) 就可以加上 \(p_a\times p_{x,y}\) 了,当然也是上文的方式。
  • 最后 \(\sum \limits _{i=1}^{n} dp_i\)

CODE
#include<bits/stdc++.h>
using namespace std;
#define read read()
#define pt puts("")
inline int read{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
const int N = 5e5+10;
int n;
struct EDGE{int next,to;double p;}e[N<<1];int head[N],tot;
void add(int u,int v,double p){e[++tot]={head[u],v,p};head[u]=tot;}
int p[N];
double dp[N];

double Plus(double pa,double pb){
    return pa+pb-pa*pb;
}

void dfs_up(int x,int fa){
    for(int i=head[x];i;i=e[i].next){
        int y=e[i].to;
        if(y==fa)  continue;
        dfs_up(y,x);
        dp[x]=Plus(dp[x],dp[y]*e[i].p);
    }
}

void dfs_down(int x,int fa){
    for(int i=head[x];i;i=e[i].next){
        int y=e[i].to;
        if(y==fa)  continue;
        double pb=dp[y]*e[i].p;
        if(pb==1.0){
            dfs_down(y,x);
            continue;
        }
        double pa=(dp[x]-pb)/(1-pb);
        dp[y]=Plus(dp[y],pa*e[i].p);
        dfs_down(y,x);
    }
}
double ans;
signed main()
{
    n=read;
    for(int a,b,c,i=1;i<n;i++){
        a=read,b=read,c=read;
        add(a,b,c*0.01),add(b,a,c*0.01);
    }
    for(int i=1;i<=n;i++)  p[i]=read,dp[i]=p[i]*1.0/100.0;
    dfs_up(1,0);
    dfs_down(1,0);
    for(int i=1;i<=n;i++){
        ans+=dp[i];
    }
    printf("%.6lf",ans);
    return 0;
}

数位DP

数位DP,大体打法有两种,即递推和记搜,我比较Cai,只会记搜

花神的数论题

要求 \(\prod \limits _{i=1}^{N} sum(i)\),考虑枚举 \(sum(i)\),即枚举数中1的个数 \(k\),求出满足 \(sum(i)=k\)\(i\) 的个数,然后快速幂求解即可。

这是板,考虑设计 \(dp_{i,j,k}\) 表示处理到第 \(i\) 位,当前已经有了 \(j\)\(1\),我们的目标是 \(k\)\(1\) 时的数的个数,然后进行记搜,为了确保比 \(n\) 小,我们再规定一个 \(limit\) 即可。

CODE
#include<bits/stdc++.h>
using namespace std;
#define mod 10000007
#define int long long
#define read read()
#define pt puts("")
inline int read{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
void write(int x){
    if(x<0)  putchar('-'),x=-x;
    if(x>9)  write(x/10);
    putchar(x%10+'0');
    return;
}
#define N 55
int n;
int len,a[55];
int ans=1ll;
int qpow(int x,int y){
    int res=1;
    while(y){
        if(y&1)  res=res*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return res;
}

int dp[N][N][N][2],sum[N];
int DP(int p,int t,int s,bool limit){
    if(t > s) return 0;
    if(p>len)  return t==s;
    if(dp[p][t][s][limit]!=-1)  return dp[p][t][s][limit];
    //if(!limit && dp[p][t][s]!=-1)  return dp[p][t][s];  这是等价的,但是要注意下面给它赋值时要在 !limit 时才能赋值。
    int up=limit?a[p]:1;
    int res=0;
    for(int i=0;i<=up;i++){
        res+=DP(p+1,t+(i==1),s,i==up&&limit);
    }
    dp[p][t][s][limit]=res;
    return res;
}
signed main(){
    n=read;
    for(len=0;n;n>>=1)  a[++len]=n&1;
    reverse(a+1,a+len+1);
    memset(dp,-1,sizeof(dp));
    for(int i=1;i<=50;i++){
        sum[i]+=DP(1,0,i,1);
    }
    for(int i=1;i<=50;i++){
        ans=ans*qpow(i,sum[i])%mod;
    }
    write(ans);
    return 0;
}

类似的题还有 [0和1的熟练],题意是区间 \([l,r]\) 中有多少数字的二进制表示(不含前导零)中0的个数不少于1的个数的数的个数。一样的思路,可以枚举1的个数,然后注意判前导0的个数就好了。

CODE
#include<bits/stdc++.h>
using namespace std;
#define read read()
#define pt puts("")
inline int read{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
void write(int x){
    if(x<0)  putchar('-'),x=-x;
    if(x>9)  write(x/10);
    putchar(x%10+'0');
    return;
}
#define N 35
int l,r;
int a[N],n;
int dp[N][N][N];
int DP(int p,int t,bool limit,int lead){
    if(!p)  return (n-t-lead>=t)?1:0;
    if(!limit && dp[p][t][lead]!=-1)  return dp[p][t][lead];
    int up=limit?a[p]:1;int res=0;
    for(int i=0;i<=up;i++)
        res+=DP(p-1,t+(i==1),limit&&(i==up),(lead==n-p)?lead+(i==0):lead);
    if(!limit) dp[p][t][lead]=res;
    return res;
}
int solve(int x){
    int res=0;
    memset(dp,-1,sizeof(dp));
    for(n=0;x;x>>=1) a[++n]=x&1;
    res+=DP(n,0,1,0);
    return res;
}
signed main(){
    l=read,r=read;
    write(solve(r)-solve(l-1));
    return 0;
}

haha数

我们发现,若设原数为 \(x\),每一位为 \(a_i\),使 \(\forall a_i | x\),等价于使 \(\mathrm{lcm}_{i=1}^n\ {a_i} | x\)
因此,我们要记录各位上数的最小公倍数。那么原数怎么记录,我们知道 \(1\) ~ \(9\) 的最小公倍数为 \(2520\),那么一定有 \(\mathrm{lcm}_{i=1}^n\ {a_i} | 2520\),所以我们只要记录原数关于 \(2520\) 的余数即 \(x\% 2520\) 的值。

所以我们设 \(dp_{i,x,lcm}\),考虑记录当前位,原数除于 \(2520\) 的余数,和各位的最小公倍数。要开 \(20\times 2520\times 2520\),会爆,又发现各位可能的最小公倍数只有 \(48\) 种,我们可以把最后一维压成 \(50\),建立一一映射关系,然后直接记搜即可。

对于记搜有一个注意点,一定要从第 \(n\) 位往第 \(1\) 位倒序跑,我一开始甚至把 \(a\) 数组翻转,然后从 \(1\)\(n\) 跑,这两种看似等价,实则不一样——考虑倒着跑是还剩 \(i\) 位,下一次可以直接继承以记搜,但是正跑就会 \(\text{WA}\),必须每次清空,就会 \(\text{T}\)。所以我们必须倒着跑,这也是最正确的记搜形式。

CODE
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define read read()
#define pt puts("")
inline ll read{
    ll x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
void write(ll x){
    if(x<0)  putchar('-'),x=-x;
    if(x>9)  write(x/10);
    putchar(x%10+'0');
    return;
}
#define mod 2520
int Gcd(int a,int b){return b?Gcd(b,a%b):a;}
int Lcm(int a,int b){return b?a/Gcd(a,b)*b:a;}
int mapp[2525],total;
ll l,r;
int a[20],n;
ll dp[20][2525][50];
ll DP(int p,int r,int lcm,int limit){
    if(!p)  return (r%lcm==0)?1:0;
    if(!limit&&dp[p][r][mapp[lcm]])  return dp[p][r][mapp[lcm]];
    int up=limit?a[p]:9;
    ll res=0;
    for(int i=0;i<=up;i++){
        res+=DP(p-1,(r*10+i)%mod,Lcm(lcm,i),limit&(i==up));
    }
    if(!limit)  dp[p][r][mapp[lcm]]=res;
    return res; 
}
ll solve(ll x){
    for(n=0;x;x/=10)  a[++n]=x%10;
    return DP(n,0,1,1);
}
signed main()
{
    #ifndef ONLINE_JUDGE
        freopen("lty.in","r",stdin);
        freopen("lty.out","w",stdout);
    #endif
    for(int i=1;i<=2520;i++)  if(mod%i==0)  mapp[i]=++total;
    for(int T=read;T;--T){
        l=read,r=read;
        ll ans=solve(r)-solve(l-1);
        write(ans);pt;
    }
    return 0;
}

[ZJOI2010] 数字计数

递推一波,考虑预处理出 \(dp_{i,j,k}\) 为具有 \(i\) 位,最高位(即第 \(i\) 位)为 \(j\),数 \(k\) 出现的次数。转移方程很好想,

\[dp_{i,j,k}=(\sum _{p=0}^9 dp_{i-1,p,k})+[j=k]\times 10^{i-1} \]

然后思索填数:

  • 考虑有前导 \(0\) 的数,这些数一定小于 \(x\),直接不用考虑上界,枚举前导 \(0\) 的个数:\(\sum \limits _{i=1}^{n-1} \sum \limits _{j=1}^9 dp_{i,j,k}\)
  • 考虑无前导 \(0\)
    • 最高位不卡上界,后面还是随便填:\(\sum \limits _{i=1}^{s_n-1} dp_{n,i,k}\)
    • 最高位卡上界,后面也要注意上界:\(\sum \limits _{i=1}^{n-1} \sum \limits _{j=1}^{s_i-1} dp_{i,j,k}\)
  • 我们发现这样还是有漏情况——当你处理第 \(i\) 位时,你只加上了后面的第 \(i-1\) 位的贡献,而它本身还有后面 \(i-1\) 位数的大小的贡献没有算,最后枚举每一位处理即可。
CODE
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define read read()
#define pt puts("")
inline int read{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
void write(int x){
    if(x<0)  putchar('-'),x=-x;
    if(x>9)  write(x/10);
    putchar(x%10+'0');
    return;
}
int a,b;
int dp[15][15][15];
int p10[15];
int ans1[11],ans2[11];
int s[15],n;
void solve(int x,int ans[]){
    int X=x;
    for(n=0;x;x/=10)  s[++n]=x%10;
    for(int k=0;k<=9;k++){
        for(int i=n-1;i>=1;i--)
            for(int j=1;j<=9;j++)
                ans[k]+=dp[i][j][k];
        for(int j=1;j<s[n];j++)  ans[k]+=dp[n][j][k];
        int num=0;
        for(int i=n-1;i>=1;--i)
            for(int j=0;j<s[i];j++)
                ans[k]+=dp[i][j][k];
    }
    for(int i=n;i>=1;--i)
        ans[s[i]]+=X%p10[i-1]+1;
}

signed main(){
    a=read,b=read;
    p10[0]=1;for(int i=1;i<=12;i++)  p10[i]=p10[i-1]*10;
    for(int i=0;i<=9;i++)  dp[1][i][i]=1;
    for(int i=2;i<=12;i++){
        for(int j=0;j<=9;j++){
            for(int k=0;k<=9;k++){
                for(int p=0;p<=9;p++)
                    dp[i][j][k]+=dp[i-1][p][k];
                if(j==k)  dp[i][j][k]+=p10[i-1];
            }
        }
    }
    solve(a-1,ans1);
    solve(b,ans2);
    for(int i=0;i<=9;i++)
        write(ans2[i]-ans1[i]);putchar(' ');
    return 0;
}

计数DP

有空再补

唉,多半是不补了,AFO了

闲话

\([2024.7.4]\) 中考出分了,以 \(663\) 分喜提全市 \(\tt{Rank} 2\),仅落后于 \(\tt{Rank} 1\) \(0.5\) 分。可是人们只会记得第一……

\([2024.7.4]\) 一下午加一晚上,虽然下午有个小体活,但是只打了8个题的博客还是太低效了……

\([2024.7.5]\) 学校OJ上的"一言"

posted @ 2024-07-05 08:08  lty_ylzsx  阅读(39)  评论(1编辑  收藏  举报