[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\),有 \(p_i\) 的概率增加 \(1\)。\(x2\) 同理。
那么就有:
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}}\)。
- 那么,剩下红灯的期望为:
-
考虑化简上式,我们主要利用了两个等式:
- \(\tbinom{n}{m}=\tbinom{n-1}{m}+\tbinom{n-1}{m-1}\)
- \(\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是申请成功的上界。申请是一回事,人家同意是另一回事...
接下来,就是 恶心的 转移方程:
为什么呢,由于这次不申请,那么此次必定在 \(c_i\) 上课,但是上一次可以申请也可以不申请,不申请则一定是从 \(c_{i-1}\) 出发,若申请则有 \(p_{i-1}\) 的概率从 \(d_{i-1}\) 出发,以及 \(1-p_{i-1}\) 的概率从 \(c_{i-1}\) 出发即可。
\(dp_{i,j,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;
}
#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\) 其中一种的概率为:
由于每个概率对期望的贡献都乘1,因此答案就是每个节点来电概率之和。
那么我们考虑分讨,某节点的来电情况:
- 自己来电
- 儿子给电
- 父亲给电
规定 \(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\) 出现的次数。转移方程很好想,
然后思索填数:
- 考虑有前导 \(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上的"一言"