因为昨天一直在改题太坑了,好不容易才改对,哭死了
好了开始补总结,今天的知识点分别是数位DP、树上二分、广搜,太刺激了,都不会。
上题目:
第1题 幸运数
【问题描述】
nyz!ysu!同学非常喜欢49,为什么呢?我也不知道。。。
nyz!ysu!同学认为49是幸运数字,同时含有49的数字也是幸运数字,比如1498就是幸运数字,但是419和94就不是,这两个数字中虽然含有4和9,但没含49。nyz!ysu!同学现在想知道1~N中有多少个幸运数字(N<10^20)。但是他很笨,只好来求助于你了。
【输入】
输入文件lucky.in
一行一个正整数N
【输出】
输出文件lucky.out
共一行,为1~N中的幸运数的个数。
<答案保证不超过2^63-1范围>
【输入样例】lucky.in
50
【输出样例】lucky.out
1
【数据说明】
20%的数据保证数据 N<=1000
除上述20%的数据外,另有30%的数据保证 N=10^k -1 (0
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 105
#define ll long long
using namespace std;
ll n,f[N][N],exp[25],ans;
int main()
{
exp[0]=1;
for(ll i=1;i<=20;i++)
{
exp[i]=exp[i-1]*10;
}
//预处理20位数
f[2][4]=1;
//代表当长度为2时,取49的次数为1
for(ll i=3;i<=20;i++)
//枚举每一位
//先把所有的答案预处理出来
{
for(ll j=0;j<=9;j++)
{
f[i][0]+=f[i-1][j];
//当长度为i时,不取数的值 = 当长度为i-1时,取0~9的每一个数的值 的和
}
for(ll j=1;j<=9;j++)
{
f[i][j]=f[i][0];
//长度为i时,取j = 不取数的值 因为当前第i位已经被确定
}
//处理当取到4的时候的特殊情况
f[i][4]+=exp[i-2];
//第i位取4的值 每100中必有一个49,先累加起来
f[i][4]-=f[i-1][0];
//第i位取4的值 - 第i-1位不取数 的值 因为第i-1位的值已经确定,不能再取了
}
char c[100];
ll a[1000];
scanf("%s",c);
ll lenc=strlen(c);
//读入优化
for(ll i=0;i<lenc;i++)
{
a[i]=c[i]-'0';
}
for(ll i=0;i<lenc;i++)
{
//逐位枚举
for(ll j=0;j<a[i];j++)
{
ans+=f[lenc-i][j];
//每一位只能取0~a[j]之间的数
}
if(i-1>=0 && a[i-1]==4 &&a[i]==9)
//如果出现了49,那么后面无论取什么数都不会都结果造成影响
{
ll t=1;
for(ll j=lenc-1;j>i;j--)
//枚举后面的每一位数
{
ans+=a[j]*t;
//直接累加,乘以a[j]是因为只能取区间内的数
t*=10;
}
++ans;
break;
}
}
cout<<ans;
//输出答案
//注意开long long
return 0;
}
第二题:
暴力也能得50分就是强行枚举,不过会超时(TLE是一定的,这辈子都不可能不会的),通常暴力的思想就是从根开始暴力,其实这很费时间,我们换个角度,如果从叶子节点开始暴力的话是不是会更好些,这样路径就唯一了,而且看这一题的数据都大于零,也就意味着从叶子到根这一段一定是一个不减函数,那么我们就可以二分了。
上代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int head[1000001],next[1000001],riv[1000001],ver[1000001],f[1000001],size=0,dist[1000001],vist[1000001],ans=0,k;
void check(int now)
{
int l=now,r=now;//左右都为叶子
while(r)//因为跟的父亲是0所以r为0意味着已经搜完根了
{
if(vist[r])//如果访问过了
break;
if(dist[r]<k)//总路径和小于k
break;
vist[r]=1;//打个标记
while(dist[r]-dist[l]<k)//如果二分出来的路径和小于k,就把l上移
l=f[l];
if(dist[r]-dist[l]==k)//如果等于的话答案++
ans++;
r=f[r];//r一步一步上移确保每种可能都有
}
}
int read()//read函数优化读入速度,也可以用cin
{
char ch;
while(ch=getchar(),ch<'0' || ch>'9');
int num=ch-'0';
while(ch=getchar(),ch>='0' && ch<='9')
num=num*10+ch-'0';
return num;
}
void add(int x,int y,int u)//边表的常规操作
{
size++;
next[size]=head[x];
head[x]=size;
riv[size]=y;
ver[size]=u;
}
void dfs(int x,int father)//搜一下
{
bool flag=1;//开个标记
for(int i=head[x],y;y=riv[i],i;i=next[i])
if(y!=father)//保证单向性(题目要求)
{
flag=0;//不是叶子
dist[y]=dist[x]+ver[i];//计算每个节点到根的权值
f[y]=x;//父亲打上去
dfs(y,x);//继续搜下一个
}
if(flag)//是叶子节点
check(x);//二分路径
return ;
}
int main()
{
int n,p,i,j,x,y,u;
scanf("%d %d %d",&n,&p,&k);
for(i=1;i<n;i++)//读入边
{
x=read();
y=read();
u=read();
add(x,y,u);//边表(或者叫邻接表)的储存
add(y,x,u);//要正向反向都存一遍,因为不知道数据是不是从父亲到儿子
}
dfs(p,0);//我们dfs一下,处理每个节点的父亲节点,并且找到叶子 ,第一个传的是当前要搜的,第二个是它的父亲,上面边表存了
//两遍为了保证单向性所以要传它的父亲来确认
cout <<ans;
return 0;
}
第三题:
第三题就比较有趣了,这一题要广搜(也算不上把,就是用了广搜的思想),我们用f[i][j],表示(i,j)这个点的水位,a[i][j]表示(i,j)这一个点的地方高度,答案就是f数组-a数组,这样我们就可以知道,f[i][j]=min(f[i][j],max(f[x][y],a[i][j]))表示对于每个点我们枚举它周围的四个点的每一个点,对于它我们可以知道它水位高度一定是周围四个点传给它的最小的,那么怎么传呢,传的就是周围的水位嘛,还有一个就是自己地面的高度,这么理解,对于一个点水位肯定是和周围一样嘛,然后还有一种情况自身高度大于周围水位,那么就只能是自己高度了表示没水,然后有一个细节,我们得到这个转移是在周围所有水位都知道的情况下,这个怎么办呢?我们可以用贪心,我们先去计算地方高度高的点这样就可以用它去更新更多的点,因为水往低处流,这样就AC了。
代码:
#include<queue>
#include<cstdio>
#include<iostream>
#define N 1000
using namespace std;
struct point//定义结构体
{
int x,y,z;
point(int _x=0,int _y=0,int _z=0)//结构体中的函数,表示每次将x,y,z初始化为0
{
x=_x;
y=_y;
z=_z;
}
};
priority_queue<point>heap;//优先队列,实现我刚刚说的贪心
bool operator < (const point &a,const point &b)//重载运算符
{
return a.z>b.z;
}
int read()//读入优化
{
char c;
while(c=getchar(),(c<'0') || (c>'9'));
int s=int(c)-'0';
while(c=getchar(),('0'<=c) && (c<='9'))
s=s*10+int(c)-48;
return s;
}
int a[N][N];
int f[N][N];
int vist[N][N];
int dx[5]={0,0,1,0,-1};//定义广搜方向,然而第0维没什么用
int dy[5]={0,1,0,-1,0};
int main()
{
int n,m,k;
scanf("%d%d%d",&n,&m,&k);//读入数据
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
{
a[i][j]=read();
f[i][j]=k;
}
int ex,ey;
scanf("%d%d",&ex,&ey);
f[ex][ey]=a[ex][ey];//初始化使出水口水面高度为自己的海拔高度
heap.push(point(ex,ey,a[ex][ey]));//优先队列了解一下
while(!heap.empty())//广搜
{
point u=heap.top();
heap.pop();
int x1=u.x;
int y1=u.y;
vist[x1][y1]=0;//打个标记
for(int i=1;i<=4;++i)//开始搜
{
int x2=x1+dx[i];
int y2=y1+dy[i];
if(1<=x2&&1<=y2&&x2<=n&&y2<=m)//没有超出去
{
int temp=max(f[x1][y1],a[x2][y2]);//算出广搜到的点可以传给自身的值
if(f[x2][y2]>temp)//如果小于自身更新
{
f[x2][y2]=temp;
if(!vist[x2][y2])
{
vist[x2][y2]=1;
heap.push(point(x2,y2,f[x2][y2]));
}
}
}
}
}
for(int i=1;i<=n;++i,cout<<"\n")//输出答案
for(int j=1;j<=m;++j)
printf("%d ",f[i][j]-a[i][j]);
fclose(stdin);
fclose(stdout);
}
终于写完了这篇总结,还有三篇加油,你们也是。