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);
}