暑假考试第十三场第一题W
题目描述
给定正整数n,将其表示为
n
=
a
1
+
a
2
+
…
…
+
a
m
n=a_1+a_2+……+a_m
n=a1+a2+……+am,其中的
a
i
a_i
ai绝对值为2的非负整数幂(即
a
i
=
−
1
,
1
,
−
2
,
2
,
−
4
,
4
…
…
a_i=-1,1,-2,2,-4,4……
ai=−1,1,−2,2,−4,4……)。求最小的m。
输入一个二进制数,表示n
输出m,即题目描述中的m
思路
考试的时候不知道为什么推了一个脑残的递推式我都不想说什么了。看到这个输入我们就可以知道肯定是转换成二进制搞,因为储存不下我们可以试着用dp解决这道题。我们可以知道的是对于每一个点,如果它是1,只有两种情况,一个就是它是正着加上去的,又或者是从更大的数减下来的。我们设
d
p
n
{dp_n}
dpn为从结尾到第n个点的最小贡献,则
d
p
i
dp_i
dpi=min(
d
p
j
dp_j
dpj+
c
n
t
1
[
i
]
[
j
]
cnt1[i][j]
cnt1[i][j],
d
p
j
dp_j
dpj+
c
n
t
0
[
i
]
[
j
]
cnt0[i][j]
cnt0[i][j] + 2)第一个的意思就是i到j中每一个1都自力更生,自己搞上去,第二个的意思就是每个数都靠它爸爸,把自己需要的1都拿了,但爸爸和去凑把这个序列全都变为1的1要加上去,所以式子就长成这个样子了。但是
O
(
n
2
)
O(n^2)
O(n2)显然是过不了的,我们当然不用什么单调队列优化因为忘了怎么打的了我们直接统计一下最小值就可以了,然后每次更新一下,时间复杂度就优化到了
O
(
n
)
O(n)
O(n)了,不知道我考试的时候怎么想的,md
代码
#include <set>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define Int register int
#define int long long
#define MAXN 1000005
#define INF 0x7f7f7f7f7f
string s;
int tot;
int dp[MAXN],pre[MAXN][2];
void read (int &x)
{
x = 0;char c = getchar();int f = 1;
while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}
while (c >= '0' && c <= '9'){x = (x << 3) + (x << 1) + c - '0';c = getchar();}
x *= f;return ;
}
void write (int x)
{
if (x < 0){x = -x;putchar ('-');}
if (x > 9) write (x / 10);
putchar (x % 10 + '0');
}
signed main()
{
cin >> s;
for (Int i = 1;i <= s.length();++ i)
{
pre[i][1] = pre[i - 1][1] + (s[i - 1] - '0');
pre[i][0] = pre[i - 1][0] + !(s[i - 1] - '0');
}
int Value0 = 0,Value1 = 0;
for (Int i = s.length();i >= 1;-- i)
{
dp[i] = min(Value0 + pre[s.length()][1] - pre[i - 1][1],Value1 + pre[s.length()][0] - pre[i - 1][0] + 2);
Value0 = min(Value0,dp[i] - pre[s.length()][1] + pre[i - 1][1]);
Value1 = min(Value1,dp[i] - pre[s.length()][0] + pre[i - 1][0]);
}
write (dp[1]),putchar ('\n');
return 0;
}
暑假考试第十三场第二题inint
题目描述
从起点1开始,每次选择当前数的任意一位上加上去,问得到n的最小步数以及方案数。多组数据。
思路
如果我们考虑20分的做法就是通过
O
(
n
)
O(n)
O(n)的递推就可以了然而我只有TMD的10分我们想一下剩余80分的做法,我们实际上可以通过预处理来完成,但思路完全不一样。我们可以设一个dp数组,
d
p
[
d
]
[
S
]
[
i
]
[
j
]
dp[d][S][i][j]
dp[d][S][i][j],先不要管它是什么意思。我们可以根据RMQ的思想,把一个数倍增十增十增的分,然后dp数组中d就表示的是这是第几块,S就表示前面出现的数的集合,我们可以用状压来完成,因为
2
9
2^9
29好像也不是特别大。i、j表示我们要从i转移到j。然后这个里面储存的就是完成这个操作的最小步数,我们实际上可以把这个搞成pair数组,就可以直接储存方案数了。我们现在考虑如何转移它。这里有一个特JB麻烦的一点在于它这个有进位操作,我们实际上在d+1位进位,我们d位就需要跳十次,
f
(
d
)
f(d)
f(d)也可以通过
f
(
d
−
1
)
f(d-1)
f(d−1)转移过来,至于具体怎么操作,还是看代码吧因为我也不知道查询答案的话直接从高位搞到地位,然后由于进位这种操作,就修正一下个位就可以了
然后代码我自己还没有打,1是没有理解透彻,2是没有时间,算了,就这样吧
代码(老师的代码,自己还没有打)
#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<cstring>
#include<map>
#include<set>
#include<vector>
typedef long long LL;
typedef unsigned long long ULL;
const int INF=0x3f3f3f3f;
const LL INFLL=0x3f3f3f3f3f3f3f3fll;
LL getint(){
LL ret=0;bool f=0;char c;
while((c=getchar())<'0'||c>'9')if(c=='-')f^=1;
while(c>='0'&&c<='9'){ret=ret*10+c-'0';c=getchar();}
return f?-ret:ret;
}
using namespace std;
const int MAXS=512,mod=1000000007;
LL f[MAXS+10][15][12][12],fs[MAXS+10][15][12][12];
LL tmp[15][15],tmps[15][15];
bool vis[20];
void get1(int S){
for(int i=1;i<=9;++i,S>>=1)
vis[i]=S&1;
}
void init(){
memset(f,0x3f,sizeof f);
for(int S=0;S<512;++S){
for(int i=9;i>=0;--i){
memset(vis,0,sizeof vis);
get1(S);
vis[i]=1;
for(int j=0;j<=8;++j){
if(vis[j+10-i])f[S][0][i][j]=1,fs[S][0][i][j]=1;
else{
for(int k=i+1;k<=9;++k)
if(vis[k-i]){
if(f[S][0][k][j]+1<f[S][0][i][j])
f[S][0][i][j]=f[S][0][k][j]+1,fs[S][0][i][j]=0;
if(f[S][0][k][j]+1==f[S][0][i][j])
fs[S][0][i][j]+=fs[S][0][k][j];
}
}
}
}
}
for(int i=1;i<=11;++i)
for(int S=0;S<512;++S)
for(int s=0;s<=8;++s){
memset(tmp,0x3f,sizeof tmp);
tmp[0][s]=0;tmps[0][s]=1;
for(int j=0;j<=9;++j)
for(int a=0;a<=8;++a)
for(int b=0;b<=8;++b){
int nS=j?S|(1<<(j-1)):S;
if(tmp[j+1][b]>tmp[j][a]+f[nS][i-1][a][b])
tmp[j+1][b]=tmp[j][a]+f[nS][i-1][a][b],tmps[j+1][b]=0;
if(tmp[j+1][b]==tmp[j][a]+f[nS][i-1][a][b])
tmps[j+1][b]=(tmps[j+1][b]+(LL)tmps[j][a]*fs[nS][i-1][a][b])%mod;
}
for(int t=0;t<=8;++t)
f[S][i][s][t]=tmp[10][t],fs[S][i][s][t]=tmps[10][t];
}
}
int main(){
freopen("inint.in","r",stdin);
freopen("inint.out","w",stdout);
init();
int T=getint();
while(T--){
LL n=getint();
memset(tmp,0x3f,sizeof tmp);
//if(n==1000000000){printf("119046515 472921725\n");continue;}
int t=0;
tmp[0][1]=0;tmps[0][1]=1;
int S=0;
for(LL i=1000000000000ll,ti=11;i>=10;i/=10,ti--){
for(int j=0;j<(n/i)%10;++j){
t=!t;
memset(tmp[t],0x3f,sizeof tmp[t]);
int nS=j?S|(1<<(j-1)):S;
for(int a=0;a<=8;++a)
for(int b=0;b<=8;++b){
if(tmp[t][b]>tmp[!t][a]+f[nS][ti][a][b])
tmp[t][b]=tmp[!t][a]+f[nS][ti][a][b],tmps[t][b]=0;
if(tmp[t][b]==tmp[!t][a]+f[nS][ti][a][b])
tmps[t][b]=(tmps[t][b]+(LL)tmps[!t][a]*fs[nS][ti][a][b])%mod;
}
}
if((n/i)%10)S|=1<<((n/i)%10-1);
}
for(int i=0;i<=9;++i){
memset(vis,0,sizeof vis);
get1(S);vis[i]=1;
for(int a=1;a<=9;++a)
if(vis[a]&&i+a<=9){
int j=i+a;
if(tmp[t][j]>tmp[t][i]+1)
tmp[t][j]=tmp[t][i]+1,tmps[t][j]=0;
if(tmp[t][j]==tmp[t][i]+1)
tmps[t][j]=(tmps[t][j]+tmps[t][i])%mod;
}
}
if(tmp[t][n%10]==INFLL)printf("IMPOSSIBLE\n");
else printf("%I64d %I64d\n",tmp[t][n%10],tmps[t][n%10]);
}
}
暑假考试第十三场第三题seq
题目描述
有数列{ a n a_n an}满足
a 1 = 3 2 − 1 a_1=\sqrt{\frac{3}2}-1 a1=23−1
a n + 1 = ( 1 − 4 ∗ n ∗ a n ) / ( 2 ∗ n − 1 ) a_{n+1}=(1-4*n*a_n)/(2*n-1) an+1=(1−4∗n∗an)/(2∗n−1)
前置知识
我为了这道题学习了半个小时,自己推了两个小时。这道题要用泰勒展开。我首先来说泰勒展开吧。在说之前我们要搞懂求导,所谓求导,就是把一个函数表示成跟它长得很像的一个函数,对于一个幂函数,我们设它长成这个样子
f
(
x
)
=
x
r
f(x)=x^r
f(x)=xr,则
f
′
(
x
)
=
r
∗
x
r
−
1
f'(x)=r*x^{r-1}
f′(x)=r∗xr−1,至于为什么,我们可以取一个跟
x
x
x相差
x
0
x0
x0的一个值,我们根据物理里面的瞬时速度可以算出它一刻的斜率,在这里就是
f
′
(
x
)
=
f
(
x
)
−
f
(
x
−
x
0
)
x
0
f'(x)=\frac{f(x)-f(x-x0)}{x0}
f′(x)=x0f(x)−f(x−x0),我们用幂函数表示出来,即
f
′
(
x
)
=
x
r
−
(
x
−
x
0
)
r
x
0
=
r
∗
x
r
−
1
f'(x)=\frac{x^r-(x-x0)^r}{x0}=r*x^{r-1}
f′(x)=x0xr−(x−x0)r=r∗xr−1,根据二次项定理我们就可以算出来这个东西,因为
x
0
x0
x0是无限逼近于0的,所以我们就可以算出这个东西。很简单吧接着就要引出我们的泰勒展开了,它表示为
g
(
x
)
=
∑
i
=
0
+
∞
1
i
!
f
(
i
)
(
x
)
(
x
−
x
0
)
i
g(x)=\sum_{i=0}^{+\infty}\frac{1}{i!}f^{(i)}(x)(x-x_0)^i
g(x)=∑i=0+∞i!1f(i)(x)(x−x0)i,其中
f
(
i
)
(
x
)
f^{(i)}(x)
f(i)(x),表示x在求导i次之后的值,为什么呢?因为我们是在构造一个函数取尽量逼近一个貌似不可求的函数值其实确实不可求所以我们要使得它们值尽量相等,所以就构造出这个东西不要问我怎么构造出来的,因为我也记不得了。好,我们这道题的前置知识就到这里了
思路
我们看到这个首项
a
1
=
3
2
−
1
a_1=\sqrt{\frac{3}2}-1
a1=23−1,我们试着用泰勒展开求出一个多项式的值,我们先求一下
1
+
x
\sqrt{1+x}
1+x的版本,
1
+
x
=
(
1
+
x
)
1
2
\sqrt{1+x}={(1+x)}^{\frac{1}2}
1+x=(1+x)21,所以
f
(
1
)
(
1
+
x
)
=
1
2
∗
(
1
+
x
)
−
1
2
f^{(1)}(1+x)=\frac{1}2*(1+x)^{-\frac{1}{2}}
f(1)(1+x)=21∗(1+x)−21,后面的
f
′
{f'}
f′都以此类推是可以求出的,所以我们就可以把
3
2
\sqrt{\frac{3}2}
23用泰勒展开表示为
∑
i
=
0
+
∞
1
i
!
∗
f
(
i
)
(
3
2
)
∗
(
1
2
)
i
\sum_{i=0}^{+\infty}\frac{1}{i!}*f^{(i)}(\frac{3}2)*(\frac{1}{2})^i
∑i=0+∞i!1∗f(i)(23)∗(21)i,然后再把这个值减去1就是
a
1
a_1
a1的值,如果哪个同学喜欢看具体的值,可以自己手动枚举累死概不负责,我们将递推公式进行一下改变,变为
a
n
+
1
=
4
∗
n
∗
a
n
1
−
2
∗
n
+
1
2
∗
n
−
1
a_{n+1}=\frac{4*n*a_n}{1-2*n}+\frac{1}{2*n-1}
an+1=1−2∗n4∗n∗an+2∗n−11,然后在我们通过暴力枚举前三项之后我们可以发现每个的第一项都可以被消掉你不信你就暴力枚举一下,然后我们通过观察可以发现这个泰勒式子它是有规律的,
a
n
=
∑
i
=
0
+
∞
π
j
=
2
∗
n
−
3
2
n
+
2
∗
j
−
3
,
j
+
=
2
∗
1
n
4
i
−
1
∗
1
π
i
=
n
+
1
+
∞
∗
(
−
1
)
i
a_n=\frac{\sum_{i=0}^{+\infty}\pi_{j=2*n-3}^{2n+2*j-3,j+=2}*\frac{1}n}{4^{i-1}}*\frac{1}{\pi_{i=n+1}^{+\infty}}*(-1)^i
an=4i−1∑i=0+∞πj=2∗n−32n+2∗j−3,j+=2∗n1∗πi=n+1+∞1∗(−1)i,对于这个式子我是很没有信心相信它是对的,因为我们是求的一个逼近正确答案的一个值怎么感觉跟玄学一样了,跟模拟退火一个样,所以我们大概循环个500到1000次左右应该就是正确答案,反正错了也跟我没有关系
代码理论AC
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define Int register int
int n;
void read (int &x)
{
x = 0;char c = getchar();int f = 1;
while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}
while (c >= '0' && c <= '9'){x = (x << 3) + (x << 1) + c - '0';c = getchar();}
x *= f;return ;
}
void write (int x)
{
if (x < 0){x = -x;putchar ('-');}
if (x > 9) write (x / 10);
putchar (x % 10 + '0');
}
signed main()
{
read (n);
double ans = 0,now = 1.0 / (n * 4.0);
for (Int i = 1;i <= 1000;++ i)
{
ans += now;
now *= (-1) * (n * 2.0 + 2.0 * i - 3.0) / (4.0 * (n + i));
}
printf ("%.8lf\n",ans);
return 0;
}