题意:给定
x
,
y
≤
1
0
9
x,y≤10^9
x,y≤109 求
∑
x
i
=
0
∑
y
j
=
[
i
=
0
]
[
i
&
j
=
0
]
[
l
o
g
2
(
i
+
j
)
+
1
]
\sum\limits_{x_i=0}\sum\limits_{y_j=[i=0]} [i\&j=0][log2(i+j)+1]
xi=0∑yj=[i=0]∑[i&j=0][log2(i+j)+1]。
这个题其实稍微一分析就感觉是数位dp,但是头一次遇到两个数一起dp的情况。
其实分析分析,不妨让 X > Y,就是求 i > j 且 i & j = 0 的情况下,i 的二进制位数之和。
数位dp,
f
(
p
o
s
,
l
1
,
l
2
)
f(pos, l_1, l_2)
f(pos,l1,l2) 表示在二进制下第 pos 个位置,i 和 j 是否顶到上限时,i & j 的数量。其实就是深搜,ok 表示当前状态的最高位是否为当前这个位置,之有是这个位置时才把答案加进去。而且
i
&
j
=
=
0
i\&j==0
i&j==0 意味着 i 和 j 不可以都是1。代码很好懂。
#include<iostream>#include<algorithm>#include<cstring>usingnamespace std;typedeflonglong ll;const ll mod =1e9+7;
ll f[50][2][2];int a[50], b[50];
ll ans;//pos是位置,l1、l2是 i 和 j 是否顶到上限,state 表示当前状态是否合法,ok表示当前状态的最高位是否为当前这个位置。只有为1时才能加到答案中去
ll dfs(int pos,bool l1,bool l2,int state,int ok){if(pos ==-1)return state ==0;if(state >0)return0;if(f[pos][l1][l2]!=-1)return f[pos][l1][l2];int up1 = l1 ? a[pos]:1;int up2 = l2 ? b[pos]:1;
ll cnt =0, res =0;for(int i =0; i <= up1; i++){for(int j =0; j <= up2; j++){
ll dx =dfs(pos -1, l1 && i == up1, l2 && j == up2, state +(i & j), ok ? ok :(i ^ j));if(!ok &&(i ^ j)) cnt =(cnt + dx)% mod;
res =(res + dx)% mod;}}
ans =(ans + cnt *((ll)pos +1))% mod;//printf("*** %lld %lld %d %d %d\n", ans, cnt, pos, up1, up2);return f[pos][l1][l2]= res;}intmain(){int T;scanf("%d",&T);while(T--){memset(f,-1,sizeof f);memset(a,0,sizeof a);memset(b,0,sizeof b);
ans =0;
ll X, Y;scanf("%lld%lld",&X,&Y);if(X < Y)swap(X, Y);int n =0, m =0;while(X){
a[n++]= X %2;
X /=2;}while(Y){
b[m++]= Y %2;
Y /=2;}dfs(n -1,1,1,0,0);printf("%lld\n", ans);}return0;}
B. Walker
题意:在长度为 n 的数轴上给出两个人的初始位置和速度,问使得每个位置至少被一个人走过的时间是多少
分成三种情况讨论就行,看注释
#include<cstring>#include<algorithm>#include<iostream>usingnamespace std;double n, p1, p2, v1, v2;doublef(double x){//看看p1或者p2先向哪边走时间最短double t1 =min((p1 + x)/ v1,(x - p1 + x)/ v1);double t2 =min((n - p2 + n - x)/ v2,(p2 - x + n - x)/ v2);returnmax(t1, t2);}intmain(){int T;scanf("%d",&T);while(T--){scanf("%lf%lf%lf%lf%lf",&n,&p1,&v1,&p2,&v2);//别想复杂了。就是把两个人的序号的换换而已。if(p1 > p2){swap(p1, p2);swap(v1, v2);}double ans;//第一种情况,一个人走完全程
ans =min((n - p1 + n)/ v1,(p1 + n)/ v1);
ans =min(ans,min((p2 + n)/ v2,(n - p2 + n)/ v2));//第二种情况,p1向右走, p2向左走
ans =min(ans,max((n - p1)/ v1, p2 / v2));//第三种情况,p1 与 p2 在 p1~p2中间某个位置汇集//printf("*** %f\n", ans);double l = p1, r = p2;for(int i =0; i <100; i++){double m1 =(l + r)/2;double m2 =(m1 + r)/2;if(f(m1)>f(m2)) l = m1;else r = m2;//printf("*** %f\n", f(l));}
ans =min(ans,f(l));printf("%.10f\n", ans);}return0;}/*
1
10 1 0.001 9 10000
*/
C. The Journey of Geor Autumn
对于给定的
n
,
k
≤
1
0
7
n,k≤10^7
n,k≤107,求有多少个满足这样条件的
1..
n
1..n
1..n 全排列:对于任意
i
>
k
i>k
i>k,
a
i
>
min
j
∈
[
i
−
k
,
i
−
1
]
a
j
.
a_i>\min\limits_{j∈[i−k,i-1]}a_j.
ai>j∈[i−k,i−1]minaj.
这个题一看就是dp啊,但是好难找他们之间的递推关系
我们不妨及这个数为
A
(
n
,
k
)
A(n, k)
A(n,k).
必须把最小的数字(应该就是1)放在 1~k 的位置上,不妨设放在了第 j 个位置上
对于前
j
−
1
j - 1
j−1 个位置,可以随便排,相当于从剩下
n
−
1
n - 1
n−1 中选择
j
−
1
j-1
j−1 个数,然后全排列:
C
n
−
1
j
−
1
∗
(
j
−
1
)
!
C_{n-1}^{j-1}*(j-1)!
Cn−1j−1∗(j−1)! 随便挑出来 j - 1 个数一定是不影响后面
n
−
j
n-j
n−j 个数字的排列的。因为后面
n
−
j
n-j
n−j 个数字一定比第 j 个数字大。
最后,看后面
n
−
j
n - j
n−j 个位置。对于
a
n
−
j
+
1
a_{n - j + 1}
an−j+1 到
a
n
−
j
+
k
a_{n-j+k}
an−j+k 这 k 个数字,都是比
a
j
a_j
aj 大;而
a
n
−
j
+
k
+
1
a_{n-j+k+1}
an−j+k+1 ~
a
n
a_{n}
an 中,取决于前面 k 个数字。因此,等价于
A
(
n
−
j
,
k
)
A(n-j,k)
A(n−j,k) 这么一个问题。方案数为
f
(
n
−
j
)
f(n-j)
f(n−j)
因此
f
(
i
)
=
∑
j
=
1
k
C
i
−
1
j
−
1
∗
(
j
−
1
)
!
∗
f
(
i
−
j
)
=
(
i
−
1
)
!
∑
j
=
1
k
f
(
i
−
j
)
(
i
−
j
)
!
f(i) = \sum\limits_{j=1}^{k}C_{i-1}^{j-1}*(j-1)!*f(i-j) = (i-1)!\sum\limits_{j=1}^{k}\frac{f(i-j)}{(i-j)!}
f(i)=j=1∑kCi−1j−1∗(j−1)!∗f(i−j)=(i−1)!j=1∑k(i−j)!f(i−j) (很简单的化简)
整理一下:
f
(
i
)
i
!
=
1
i
∑
j
=
1
m
i
n
(
i
,
k
)
f
(
i
−
j
)
(
i
−
j
)
!
\frac{f(i)}{i!} = \frac{1}{i}\sum\limits_{j=1}^{min(i, k)}\frac{f(i-j)}{(i-j)!}
i!f(i)=i1j=1∑min(i,k)(i−j)!f(i−j).
所以,我们可以直接求
f
(
i
)
i
!
\frac{f(i)}{i!}
i!f(i),然后最后答案乘
i
!
i!
i! 就行。
两个细节。一个是要求
f
(
i
)
i
!
\frac{f(i)}{i!}
i!f(i) 的前缀和。另一个是,求 1 ~ n 的逆元,这个要用线性的方法。n = 1e7的情况下,
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn) 会超时。
#include<iostream>#include<cstring>#include<algorithm>usingnamespace std;constint maxn =10000010;typedeflonglong ll;const ll mod =998244353;
ll f_i[maxn], fact[maxn], inv[maxn], sumf[maxn];//inv 表示 1~n 的逆元intmain(){
ll N, K;scanf("%lld%lld",&N,&K);
fact[0]= inv[1]=1;for(ll i =1; i <= N; i++){
fact[i]= fact[i -1]* i % mod;}for(ll i =2; i <= N; i++){
inv[i]=(ll)(mod - mod / i)* inv[mod % i]% mod;}for(int i =1; i <= K; i++){
f_i[i]=1;
sumf[i]=(f_i[i]+ sumf[i -1])% mod;}for(int i = K +1; i <= N; i++){
ll pre =(sumf[i -1]- sumf[i - K -1]+ mod)% mod;
f_i[i]= inv[i]* pre % mod;
sumf[i]=(f_i[i]+ sumf[i -1])% mod;}printf("%lld\n", f_i[N]* fact[N]% mod);return0;}
#include<iostream>#include<algorithm>#include<cstring>usingnamespace std;constint maxn =1010;int N, K, a[maxn], b[maxn], c[maxn];typedeflonglong ll;
ll ans;voidcal(int d){//先按照顺时针旋转计算答案for(int i =0; i < K; i++){
c[i]=(a[i]- b[(i + d)% K]+ N)% N;}//printf("*** %lld\n", ans);sort(c, c + K);//顺时针旋转最大值
ans =min(ans,(ll)c[K -1]);//逆时针旋转最大值
ans =min(ans,(ll)N - c[0]);for(int i =0; i < K -1; i++){/*
这个是第三种情况,先顺时针再逆时针(或者先逆时针再顺时针)
可以设想,c数组里面的元素,一定是有一个分界,前面部分是顺时针旋转距离较近,后面部分是逆时针旋转较近。
而且,在后面部分的那些元素中,越往前的元素,逆时针所需要的距离越远。
因此,先顺时针旋转到第一部分最后一个元素,再旋转到初始位置,再旋转到后面部分第一个元素。
顺势针旋转到第一部分最后一个元素时,第一部分所有元素都已旋转到;
而后面部分的第一个元素用逆时针旋转到的时候,后面所有元素均已旋转到;
ans的改变就是在分界线改变的。
*/
ans =min(ans,2LL* c[i]+(N - c[i +1]));}for(int i = K -1; i >0; i--){//先逆时针再顺时针
ans =min(ans,2LL*(N - c[i])+ c[i -1]);}}intmain(){int T;scanf("%d",&T);while(T--){scanf("%d%d",&N,&K);
ans =1e9;for(int i =0; i < K; i++){scanf("%d",&a[i]);}for(int i =0; i < K; i++){scanf("%d",&b[i]);}sort(a, a + K);sort(b, b + K);for(int i =0; i < K; i++){cal(i);}printf("%lld\n", ans);}return0;}
#include<iostream>#include<algorithm>#include<cstring>#include<cmath>usingnamespace std;constdouble PI =acos(-1);int N, M;intmain(){scanf("%d%d",&N,&M);//半个圆周上,theta >= 2点的为x2, < 2的点为x1int x1 =(PI -2.0)* M / PI +1, x2 = M - x1;//printf("*** %d %d\n", x1, x2);double res1 =0, res2 =0, res3 =0;//原点到圆上的点if(M !=1) res1 =2.0* M * N *(N +1.0)/2;//theta >= 2 的点
res2 =2.0*(double)M *(double)N *(2.0* x1 -1)*(double)N *((double)N +1.0)/2;for(int i =1; i <= N; i++){double M_ = M, x2_ = x2, i_ = i;// theta < 2 的点
res3 +=2* M_ *(PI * x2_ *(x2_ +1.0)* i_ *(i_ -1)/2/ M_ +((i_ -1)* i_ *(2* x2_ +1))-(2* x2_ +1)* i_ *(i_ -1)/2);
res3 += PI * i *(x2_ +1)* x2_;}//printf("### %f %f %f\n", res1, res2, res3);double ans = res1 + res2 + res3;printf("%.10f\n", ans);return0;}
F. Traveling in the Grid World
题意:给定一个
n
×
m
n×m
n×m 的网格图,你站在起始点
(
0
,
0
)
(0,0)
(0,0),每次移动可以选择一个点
(
x
,
y
)
(x,y)
(x,y) 走线段到达它且这条线段不能经过其他格点,可以做任意次移动,问到达
(
n
,
m
)
(n,m)
(n,m) 所需的移动距离总和最小为多少?
一个简单的结论:若
g
c
d
(
n
,
m
)
=
1
gcd(n,m)=1
gcd(n,m)=1,可以直接从
(
0
,
0
)
(0,0)
(0,0)走到
(
n
,
m
)
(n,m)
(n,m),否则只需要一次转折即可到达
(
n
,
m
)
(n,m)
(n,m),而且这个转折点就在对角线附近。假设转折点为
(
x
,
y
)
(x,y)
(x,y)那么需要满足
g
c
d
(
x
,
y
)
=
1
gcd(x,y)=1
gcd(x,y)=1且
g
c
d
(
n
−
x
,
m
−
y
)
=
1
gcd(n−x,m−y)=1
gcd(n−x,m−y)=1。暴力枚举
(
0
,
0
)
(0,0)
(0,0)和
(
n
,
m
)
(n,m)
(n,m)连线附近的点作为转折点计算距离取最小值即可。
#include<iostream>#include<algorithm>#include<cstring>#include<cmath>usingnamespace std;typedeflonglong ll;constdouble INF =1e9;
ll N, M;
ll gcd(ll a, ll b){if(b ==0)return a;returngcd(b, a % b);}doubledist(ll x1, ll y1, ll x2, ll y2){int dx = x1 - x2, dy = y1 - y2;returnsqrt(pow(dx,2)+pow(dy,2));}doublecal(ll x, ll y){if(gcd(x, y)!=1||gcd(M - x, N - y)!=1)return INF;if(x *(y - N)==(x - M)* y)return INF;returndist(0,0, x, y)+dist(x, y, M, N);}intmain(){int T;scanf("%d",&T);while(T--){scanf("%lld%lld",&N,&M);double ans = INF;if(gcd(M, N)==1){printf("%.15f\n",dist(0,0, M, N));continue;}for(ll x =0; x <= M; x++){
ll y = N * x / M;
ans =min(ans,cal(x, y));
ans =min(ans,cal(x, y +1));
ans =min(ans,cal(x, y -1));}printf("%.15f\n", ans);}return0;}