D Small Multiple
任何数都可以从1通过以下操作得到:
1 x->x+1,花费为1
2 x->x*10,花费为0
可以发现这样操作得到一个k的倍数,那么答案就是操作的花费加1
我们把所有点都在mod k意义下表示,那么可以一张图,然后求出点1到点0的最短路径再加1就是答案,相当于从一开始走最少的花费走到一个k的倍数的点。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int N=1e5+5;
int n,tot,dis[N],head[N],nex[N<<1],to[N<<1],wi[N<<1];
void add(int u,int v,int w){to[++tot]=v;nex[tot]=head[u];head[u]=tot;wi[tot]=w;}
bool vis[N];
struct node
{
int x,v;
node(int x=0,int v=0):x(x),v(v){}
bool operator<(const node&o)const
{
return v>o.v;
}
};
int main()
{
scanf("%d",&n);
for(int i=1;i<n;i++) add(i%n,(i+1)%n,1),add(i%n,i*10%n,0);
priority_queue<node>q;
q.push(node(1,0));
memset(dis,inf,sizeof(dis));
dis[1]=0;
while(!q.empty())
{
int u=q.top().x;q.pop();
if(vis[u]) continue;
vis[u]=true;
for(int i=head[u];i;i=nex[i])
{
int v=to[i];
if(dis[v]>dis[u]+wi[i])
{
dis[v]=dis[u]+wi[i];
q.push(node(v,dis[v]));
}
}
}
printf("%d\n",dis[0]+1);
}
E Finite Encyclopedia of Integer Sequences
考虑采用打表观察法(表在代码的注释里)。
可以发现,当
k
m
o
d
2
=
=
0
{kmod2==0}
kmod2==0,答案为
k
/
2
,
k
,
k
,
k
.
.
.
{k/2,k,k,k...}
k/2,k,k,k...,因为
k
k
k和
n
−
k
n-k
n−k是一一对应的。
当
k
m
o
d
2
=
=
1
kmod2==1
kmod2==1,首先构建一个序列
B
=
k
/
2
+
1
,
k
/
2
+
1
,
k
/
2
+
1...
B={k/2+1,k/2+1,k/2+1...}
B=k/2+1,k/2+1,k/2+1...,然后从后面倒推
n
/
2
{n/2}
n/2字典序就是答案。
当然这样也是有理由的,可以发现
"
k
/
2
+
1
"
,
"
k
/
2
+
1
,
k
/
2
+
1
"
,
"
k
/
2
+
1
,
k
/
2
+
1
,
k
/
2
+
1
"
.
.
.
{"k/2+1","k/2+1,k/2+1","k/2+1,k/2+1,k/2+1"...}
"k/2+1","k/2+1,k/2+1","k/2+1,k/2+1,k/2+1"...这样的序列都排在序列
k
/
2
+
1
,
k
/
2
+
1
,
k
/
2
+
1...
{k/2+1,k/2+1,k/2+1...}
k/2+1,k/2+1,k/2+1...的前面,而对于其它的构造一个在其前面的序列,如当
k
=
5
{k=5}
k=5,构造一个
3
,
3
,
2
{3,3,2}
3,3,2,把这个序列替换成
k
+
1
−
3
,
k
+
1
−
3
,
k
+
1
−
2
{k+1-3,k+1-3,k+1-2}
k+1−3,k+1−3,k+1−2变成了
3
,
3
,
4
{3,3,4}
3,3,4,一定可以构造出一个唯一的在
B
{B}
B后面的序列,但是它的
n
−
1
n-1
n−1个前缀是特例,所以假设没有这
n
−
1
n-1
n−1个前缀,那么序列是中位序列,那么在其前面插上这
n
−
1
n-1
n−1,我们就需要把字典序向前推
n
/
2
n/2
n/2个。
比如
1
1
1个序列前加上
3
3
3个序列,那么这个序列变成了第
4
4
4个序列,向前推
2
2
2个才是中位序列,
1
1
1个序列前加上
4
4
4个序列,这个序列变成第
5
5
5个序列,向前推
2
2
2个才是中位序列。那为啥不直接跳到第
n
/
2
n/2
n/2就完事?因为它前面还有若干个序列,你不直到插入的序列具体在哪里,只知道往前跳
n
/
2
n/2
n/2个是答案。
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,k,a[N];
int main()
{
scanf("%d%d",&k,&n);
if(k&1)
{
int tot=n;
for(int i=1;i<=n;i++) a[i]=k/2+k%2;
for(int i=1;i<=n/2;i++)
{
if(tot==n)
{
a[tot]--;
if(a[tot]==0) tot--;
}
else
{
if(a[tot]==1) a[tot]--,tot--;
else
{
a[tot]--;
for(int j=tot+1;j<=n;j++) a[j]=k;
tot=n;
}
}
}
for(int i=1;i<=tot;i++)
printf(i==tot?"%d\n":"%d ",a[i]);
}
else
{
printf("%d",k/2);
for(int i=2;i<=n;i++)
printf(" %d",k);
putchar('\n');
}
}
/*
#include<bits/stdc++.h>
using namespace std;
vector<vector<int> >v;
vector<int>s;
void dfs(int u,int up,int mx)
{
if(u==up+1){v.push_back(s);return;}
for(int i=1;i<=mx;i++)
{
s.push_back(i);
dfs(u+1,up,mx);
s.pop_back();
}
}
int main()
{
for(int i=1;i<=8;i++)
{
dfs(1,i,5);
sort(v.begin(),v.end());
int x=v.size()/2+v.size()%2-1;
for(int i=0;i<v[x].size();i++)
printf(i==v[x].size()-1?"%d\n":"%d ",v[x][i]);
}
}
k=3
2
2 1 +1
2 2 1 +1
2 2 2 +2
2 2 2 2 +2
2 2 2 2 1 3 +3
2 2 2 2 2 1 3 +3
2 2 2 2 2 2 1 2 +4
k=2
1
1 2
1 2 2
1 2 2 2
1 2 2 2 2
1 2 2 2 2 2
1 2 2 2 2 2 2
1 2 2 2 2 2 2 2
k=4
2
2 4
2 4 4
2 4 4 4
2 4 4 4 4
2 4 4 4 4 4
2 4 4 4 4 4 4
2 4 4 4 4 4 4 4
k=5
3
3 2 +1
3 3 2 +1
3 3 3 1 +2
3 3 3 3 1 +2
3 3 3 3 3 +3
3 3 3 3 3 3 +3
*/
F XorShift
首先来介绍以下裴蜀定理,设
d
=
g
c
d
(
a
,
b
)
d=gcd(a,b)
d=gcd(a,b)那么有
d
∣
a
d|a
d∣a,
d
∣
b
d|b
d∣b,有
d
∣
a
x
d|ax
d∣ax,
d
∣
b
x
d|bx
d∣bx,有
d
∣
(
a
x
+
b
y
)
d|(ax+by)
d∣(ax+by)。
d
∣
a
d|a
d∣a表示
a
a
a
m
o
d
mod
mod
d
=
=
0
d==0
d==0。
我们把这个定理放在多项式里面去,设两个多项式
P
,
Q
P,Q
P,Q,多项式
D
D
D为
P
,
Q
P,Q
P,Q的
G
C
D
GCD
GCD,即
D
=
g
c
d
(
P
,
Q
)
D=gcd(P,Q)
D=gcd(P,Q),称
D
D
D为多项式
P
,
Q
P,Q
P,Q的最大公因式,那么也有
P
x
+
Q
y
Px+Qy
Px+Qy
m
o
d
mod
mod
D
=
=
0
D==0
D==0。
现在对于一个二进制数,我们把它表示成一个多项式
a
1
x
n
+
a
2
x
n
−
1
+
.
.
.
+
a
1
x
+
a
0
a_1x^n+a_2x^{n-1}+...+a_1x+a_0
a1xn+a2xn−1+...+a1x+a0,其中
a
i
=
0
a_i=0
ai=0
o
r
or
or
1
1
1。我们令多项式的系数都是在
m
o
d
mod
mod
2
2
2意义下的,那么我们有如下操作:
1.
1.
1. 将一个多项式乘以
x
x
x(对应操作
∗
2
*2
∗2)
2.
2.
2. 将两个多项式求和(对应操作异或)
那么发现这样的多项式求出来的一定是
P
x
+
Q
y
Px+Qy
Px+Qy的形式,再根据上述定理,我们可以求出所有多项式的
g
c
d
gcd
gcd
D
D
D。假设答案是
F
F
F,那么其满足:
1.
1.
1.
F
F
F是
D
D
D的倍数
2.
2.
2.
F
<
=
x
F<=x
F<=x
现在,假设多项式
F
F
F的度数为
s
s
s,多项式
D
D
D的度数为
t
t
t,假设
s
>
t
s>t
s>t,如果多项式
F
F
F,前
s
−
t
+
1
s-t+1
s−t+1项确定了,那么
F
F
F后面
t
−
1
t-1
t−1项被唯一确定(因为要使得
F
F
F
m
o
d
mod
mod
D
=
=
0
D==0
D==0,而后面
t
−
1
t-1
t−1项为余数,要是余数
0
0
0则可唯一确定)。那么如果前面
s
−
t
+
1
s-t+1
s−t+1项小于
x
x
x,直接计算其二进制位上的和,否则比较后面
t
−
1
t-1
t−1位是否小于等于
x
x
x。
关于多项式
g
c
d
gcd
gcd的求法,设两个多项式
P
,
Q
P,Q
P,Q,
P
P
P的项数位
s
s
s,
Q
Q
Q的项式为
t
t
t(我们认为
s
>
t
s>t
s>t,否则可以交换多个多项式),使得
P
=
P
+
(
Q
∗
x
s
−
t
)
P=P+(Q*x^{s-t})
P=P+(Q∗xs−t),这样
P
P
P的最高次幂会被消除,然后继续对
P
,
Q
P,Q
P,Q重复执行这样的操作,直到一个多项式变为
0
0
0,我们就可以得到一个多项式
D
D
D,使得
D
D
D是在系数
m
o
d
mod
mod
2
2
2意义下,
D
D
D是
P
,
Q
P,Q
P,Q的最大公因式。
更多细节见代码实现(为了便于大家理解我加入了一些注释)。
#include<bits/stdc++.h>
using namespace std;
const int N=4010;
typedef bitset<N>B;
typedef long long ll;
const int mod=998244353;
ll p[N];
void init()
{
p[0]=1;
for(int i=1;i<N;i++) p[i]=p[i-1]*2%mod;
}
int last(B x)
{
for(int i=N-1;i>=0;i--)
if(x[i]) return i;
return -1;
}
B gcd(B x,B y)//求多项式x,y在mod 2意义下的最大公因式
{
if(x.none()) return y;
if(y.none()) return x;
int a=last(x),b=last(y);
if(a<b) swap(x,y),swap(a,b);
return gcd(y,x^(y<<a-b));
}
int n;
B x,y,a[7];
int main()
{
init();
cin>>n>>x;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=1;i<n;i++) a[0]=gcd(a[0],a[i]);
ll ans=0;
int s=last(x),t=last(a[0]);
y.reset();
for(int i=s;i>=t;i--)
{
if(x[i]) ans=(ans+p[i-t])%mod;
if(x[i]!=y[i]) y^=a[0]<<(i-t);
//求多项式y使得y是D的倍数并且y的前s-t+1为与x相等
}
for(int i=s;i>=0;i--)//比较多项式y是否小于x
{
if(x[i]==y[i])
{
if(i==0) ans++;
break;
}
if(x[i]) ans++;
//如果x[i]为1,说明y[i]为0,则多项式y小于x
break;
}
printf("%lld\n",ans%mod);
}