Atcoder 234


ABC 234

E.Divide a Sequence

题意

一个正的10进制数称为是算数数,满足相邻两位上的数字的差(可以是负数也可以是正数)相等。

比如\(234\)就是一个算数数,因为\(3-2=4-3\)

\(86420\)也是,因为\(6-8=4-6=2-4=0-2\)

现在要你求不小于\(X\)的最小的算数数(\(1\le X \le 10^{17}\)

Sol

考虑先求出所有算数数,因为算数数个数很少

1.第一位有\(9\)种选择(\(1-9\))

2.公差有\(18\)种选择(\(-9-8\))

3.位数有18种选择(\(1-17\))

因此总的算数数的个数就有\(9\times18\times18=2916\),可以暴力求出来

然后二分找就可以了

#include 
#define ll long long
#define inf 0x7f7f7f7fll
using namespace std;

template  void rd (T &x)
{
    x=0;int f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-') f=-1;s=getchar();}
    while(s>='0'&&s<='9') x=x*10+(s^48),s=getchar();
    x*=f;
}
template  T chkMax(T  x,T y){return x>y?x:y;}
template  T chkMin(T x,T y){return xres;

void get()
{
    for(int i=1;i<=9;i++)
       for(int d=-9;d<=8;d++)
       {
          string s;
          int digit=i;
          for(int bit=1;bit<=18;bit++)
          {
              s.push_back(digit+'0');
              res.insert(stoll(s)); //stoll可以将字符串转化为long long型的整数
              digit+=d;
              if(digit<0||digit>9) break;
          }
       }
}
int main()
{
    get();
    ll k;
    rd(k);
    printf("%lld\n",*res.lower_bound(k));
    return 0;
}

F.Reordering

题意

给定一个字符串\(S\),问这个字符串可以得到有多少不同的序列,不要求连续和顺序。(\(1\le S.size()\le5000\))

比如\(aab\)有8个:\(a,aa,b,ab,ba,aba,aab,baa\)

Sol

主要考虑两个维度:

1.当前用了几种字母

2.当前序列的长度

定义\(dp[i][j]\)为当前可以用前\(i\)种字母,且序列长度为\(j\)的序列总数

比如\(S\)\(aab\)

\(dp[2][1]\)表示可以用前2种字母即\(a,b\),长度为1的序列总数,很容易知道为2

\(dp[2][2]\)表示可以用前2种字母即\(a,b\),长度为\(2\)的序列总数,容易知道有\(aa,ab,ba\),所以\(dp[2][2]=3\)

那么状态转移就是

\(dp[i+1][j]=\sum_{k=0}^{min(j,cnt[i])}dp[i][j-k]C_j^{k}\),其中\(cnt[i]\)为第\(i\)种字母在\(S\)中出现的次数,转移时的第二维就是枚举当前种字母可以用多少个

于是\(dp[2][2]=dp[1][1]C_2^1+dp[1][2]C_2^0\)

具体就是:\(ab,ba\)都可以由\(a\)转移过来,那么由于当前长度为2,所以b所在的位置有\(C_2^1\)种可能

? \(aa\)可以直接有\(aa\)转移过来

#include 
#define ll long long
#define inf 0x7f7f7f7fll
using namespace std;

template  void rd (T &x)
{
    x=0;int f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-') f=-1;s=getchar();}
    while(s>='0'&&s<='9') x=x*10+(s^48),s=getchar();
    x*=f;
}
template  T chkMax(T  x,T y){return x>y?x:y;}
template  T chkMin(T x,T y){return x>s;
    int n=s.size();
 
    for(int i=0;i

G - Divide a Sequence

题意

给定一个长度为\(N\)的序列\(A=\{A_1,A_2,...,A_n\}\),记\(A\)的一种划分方式为\(B=\{B_1,B_2,..,B_m\}\),其中\(B_1=(A_1,A_2,..,A_{k1}),B_2=(A_{k1+1},A_{k_1+2},..,A_{k_2}),...,B_n=(A_{k_{m-1}+1,A_{k_{m-1}+2}...,A_n})\)

记一种划分\(B\)的贡献为\(\prod_{i=1}^m(maxB_i-minB_i)\),求\(A\)的所有可能划分的贡献和,最后答案对\(998244353\)取模

\(1\le N \le 3\times10^5\) \(1\le A_i \le 10^9\)

Sol

定义\(dp[i]\)表示前\(i\)个数字组成的序列的所有可能划分的贡献和,\(dp[0]=1\)

那么\(dp[i]=\sum_{j=0}^{i-1}dp[i-1]\{max(A_{j+1},A_{j+2},...,A_i)-min(A_{j+1},A_{j+2},...,A_i)\}\)

暴力复杂度太高,考虑优化:

\(dp[i]=\sum_{j=0}^{i-1}dp[i-1]max(A_{j+1},A_{j+2},...,A_i)-\sum_{j=0}^{i-1}dp[i-1]min(A_{j+1},A_{j+2},...,A_i)\)

前后两部分可以分开计算,利用单调栈可以快速求出,有点类似滑动窗口的思想,由于\(i\)是单调递增的,而\((A_{j+1},A_{j+2},...,A_i)\)每次不断缩小的,所以用单调栈维护最大值,除去最小值,因为最小值后面是一定不会用到的,同时维护一个\(Max\_v\)栈,在窗口缩小的时候如果最大值不在窗口中的时候减去这个最大值的贡献,更新最新最大值。最小值同理。

由于每个数只会进出栈一次,所以时间复杂度为\(O(n)\)

#include 
#define ll long long
#define inf 0x7f7f7f7fll
#define fi first
#define se second
#define mod 998244353
using namespace std;

template  void rd (T &x)
{
    x=0;int f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-') f=-1;s=getchar();}
    while(s>='0'&&s<='9') x=x*10+(s^48),s=getchar();
    x*=f;
}
template  void chkMax(T &x, T y) { if (y > x) x = y; }
template  void chkMin(T &x, T y) { if (y < x) x = y; }

const int N=3e5+10;
ll dp[N],A[N];
stackMax,Min,Max_v,Min_v;
ll Max_sum=0,Min_sum=0;
int main()
{
    int n;
    rd(n);
    for(int i=1;i<=n;i++)rd(A[i]);
        dp[0]=1;
    /*暴力方法
    for(int i=1;i<=n;i++)
        for(int j=0;jA[i])
            {
                Min_sum=(Min_sum-Min_v.top()*Min.top()+mod)%mod;
                sum=(sum+Min_v.top())%mod;
                Min.pop();
                Min_v.pop();
            }
            Min_sum=(Min_sum+sum*A[i])%mod;
            Min.push(A[i]);
            Min_v.push(sum);
        }
        dp[i]=(Max_sum-Min_sum+mod)%mod;

    }
    printf("%lld\n",dp[n]);
    return 0;
}

Ex.Enumerate Pairs (计算几何)

题意

给定\(N\)个点和一个整数\(K\),问有多少个点(\(P_x,P_y\))对满足\(\sqrt{(P_x.x-P_y.x)^2+(P_x.y-P_y.y)^2}\le K\)

\(1\le N\le 2\times10^5\) \(1\le K \le 1.5\times10^9\) \(1\le x,y\le 10^9\)

答案保证点对不会超过\(4\times 10^5\)

Sol

本题只给出结论:(太菜了,不会证明)

1.将每个点\((x,y)\)划分到块(\(\lfloor\frac{x}{K}\rfloor\),\(\lfloor\frac{y}{K}\rfloor\))

2.那么对于每个点,假设所在的块为(\(X\),\(Y\)),那么与它配对的点所在的块一定(\(X+dx\),\(Y+dy\))中,其中\(-1\le dx,dy\le 1\)

暴力枚举,可以证明(我不会)时间复杂度为\(O(N+M)\) (\(M\le 4\times10^5\))

实现细节:注意块的编号的离散化

#include 
#define ll long long
#define inf 0x7f7f7f7fll
#define fi first
#define se second
using namespace std;

template  void rd (T &x)
{
    x=0;int f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-') f=-1;s=getchar();}
    while(s>='0'&&s<='9') x=x*10+(s^48),s=getchar();
    x*=f;
}
template  T chkMax(T  x,T y){return x>y?x:y;}
template  T chkMin(T x,T y){return x>blk;
vector>res;
ll dlt[]={-delta+1,-delta,-delta-1,1,0,-1,delta+1,delta,delta-1};
//块编号离散化x/k*delta+y/k
//偏移量离散化(x/k+dx)*delta+y/k+dy=>(dx*delta,dy)
int main()
{
    int n,k;
    rd(n),rd(k);
    for(int i=1;i<=n;i++)
    {
        rd(p[i].x),rd(p[i].y);
        ll id=p[i].x/k*delta+p[i].y/k; //将块的编号离散化
        blk[id].push_back(i);
    }
    for(int i=1;i<=n;i++)
    {
        ll id=p[i].x/k*delta+p[i].y/k;
        for(int j=0;j<9;j++)
        {
            ll cur=id+dlt[j];
            for(auto ni:blk[cur])
            {
                if(i>=ni) continue;
                ll dis=dist(p[i],p[ni]);
                if(dis<=1ll*k*k) res.push_back({i,ni});
            }
        }

    }
    sort(res.begin(),res.end());
    printf("%d\n",res.size());
    for(auto t:res)
        printf("%d %d\n",t.fi,t.se);
    return 0;
}

相关