AtCoder Regular Contest 084

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 nk是一一对应的。
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+13,k+13,k+12变成了 3 , 3 , 4 {3,3,4} 3,3,4,一定可以构造出一个唯一的在 B {B} B后面的序列,但是它的 n − 1 n-1 n1个前缀是特例,所以假设没有这 n − 1 n-1 n1个前缀,那么序列是中位序列,那么在其前面插上这 n − 1 n-1 n1,我们就需要把字典序向前推 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 da d ∣ b d|b db,有 d ∣ a x d|ax dax d ∣ b x d|bx dbx,有 d ∣ ( a x + b y ) d|(ax+by) d(ax+by) d ∣ a d|a da表示 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+a2xn1+...+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 st+1项确定了,那么 F F F后面 t − 1 t-1 t1项被唯一确定(因为要使得 F F F m o d mod mod D = = 0 D==0 D==0,而后面 t − 1 t-1 t1项为余数,要是余数 0 0 0则可唯一确定)。那么如果前面 s − t + 1 s-t+1 st+1项小于 x x x,直接计算其二进制位上的和,否则比较后面 t − 1 t-1 t1位是否小于等于 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+(Qxst),这样 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);
}

猜你喜欢

转载自blog.csdn.net/Huah_2018/article/details/104615771