欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

NOIP2017提高组 模拟赛 27(总结)

程序员文章站 2022-03-14 19:40:08
...

NOIP2017提高组 模拟赛 27(总结)

第一题 回文数 (推公式+快速幂)

【题目描述】

【解题思路】

  长度为1的字符串,s[1]=9。
  长度为3的字符串,s[3]=10*9。
  长度为5的字符串,s[5]=10*10*9。
  长度为7的字符串,s[7]=10*10*10*9。
  ……
  所以,长度为2k+1的字符串,s[]=9*10^k
  先把9忽略掉。
  那么就是他们的系数就是

k 0 1 2 3 4 5 6 7 ……
系数x 1 3 5 7 9 11 13 15 ……
系数x(乘以10) 0 1 3 5 7 9 11 13 15

  最后
  sum=9nk=0x[k]10k
  
  cha=2(nk=010k)1
  根据等比数列求和:
  cha=2(10n+11)/91
  
  10sumsum=9sum=9n10n+19cha
  sum=n10n+1cha
  p=233333不是质数,用欧拉函数求出phi(p)
  9的逆元(%p)=9phi(p)1mod p
  或者是枚举i(1~p),9*i%p==1,那么i就是9的逆元(%p)。
  用快速幂随便搞搞……

【代码】

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>

using namespace std;

typedef long long ll;

int T,n;
const ll mods=233333;
ll s,ni;
char ch;
int st[50];

ll Pow(ll x,ll y)
{
    ll s=1ll;
    for(;y;y>>=1,x=x*x%mods)
    if(y&1) s=(s*x)%mods;
    return s;
}

/*int read()
{
    int yu=0;
    while((ch=getchar())!='\n')
    {
        yu=(yu<<3)+(yu<<1)+ch-'0';
    }
    return yu;
}

void print()
{
    int le=0;
    while(s)
    {
        st[++le]=s%10;
        s/=10;
    }
    for(int i=le;i>=1;i--) putchar('0'+st[i]);
    putchar('\n');
}*/

int main()
{
    freopen("2255.in","r",stdin);
    freopen("2255.out","w",stdout);
    scanf("%d",&T);
    //T=read();
    ni=Pow(9,232320-1);
    for(register int h=1;h<=T;++h)
    {
        scanf("%d",&n);
        //n=read();
        n=(n+1)>>1;
        ll ty=Pow(10,n);
        ll cy=ty*((n-1)<<1|1)%mods;
        ty=2*(ty-1+mods)%mods;
        ty=(ty*ni%mods-1+mods)%mods;
        s=(cy-ty+mods)%mods;
        //print();
        printf("%lld\n",s);
    }
    return 0;
}

第二题 购物 (DP)

【题目描述】

【解题思路】

  一开始,看错题了,额,把题目想复杂了,以为是行动的总和(第一次扫)的平方(也就是不必连续,其实是要求连续的)……
  首先,若一段区间A,完全包含区间B,那么区间B是完全没有的。
  那么,预处理出左端点为L,所能延伸的最长的R,即far[L]。
  所以最多也只会有n区间而已(n=5000)。
  预处理出前缀和sum[]
  假设,先选区间[3,5],再选区间[1,4],那么就是[1,2][3,5]。
  如果先选区间[1,4],再选区间[3,5],那就是[1,4][5]。
  因为happy值是非负的,所以,若存在区间[a,far[a]],那么就可以选区间[a,a]~[a,far[a]]。
  为什么?
  平方的和不大于和的平方。所以转移之后所得到的最优解中的某个断点(即这个点之前是一次购买,之后是另一次购买)一定是对应的两个区间中的某一个的端点。这样是一定可以找到一种符合要求的购买顺序的。

【代码】

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>

#define imax(a,b) ((a>b)?(a):(b))

using namespace std;

typedef long long ll;

const int N=6000;
int n,m,a[N],far[N];
ll sum[N],ans,f[N];

int main()
{
    freopen("2256.in","r",stdin);
    freopen("2256.out","w",stdout);
    scanf("%d%d",&n,&m); ans=0ll;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
    for(int i=1;i<=m;i++)
    {
        int al,ar;
        scanf("%d%d",&al,&ar);
        far[al]=imax(far[al],ar);
    }
    for(int i=1;i<=n;i++)
    if(far[i])
    {
        for(int j=i+1;j<=far[i];j++)
        if(far[j]<=far[i]) far[j]=0;
    }
    f[0]=0ll; int mx=0ll;
    for(int i=1;i<=n;i++)
    {
        f[i]=imax(f[i],f[i-1]);
        far[i]=imax(far[i],far[i-1]);
        for(int j=i;j<=far[i];j++)
        {
            ll yu=sum[j]-sum[i-1];
            f[j]=imax(f[j],f[i-1]+yu*yu);
        }
        ans=imax(f[i],ans);
    }
    /*for(int i=1;i<=n;i++)
    {
        ll mx=0ll; int mi=0;
        for(int j=1;j<=n;j++)
        {
            ll yu=sum[far[j]]-sum[j-1];
            if(yu>mx)
            {
                mx=yu;
                mi=j;
            }
        }
        ans+=mx*mx;
        for(int j=1;j<=n;j++)
        {
            if(j<mi && far[j]>=mi) far[j]=mi-1;
        }
        int fl=0;
        for(int j=1;j<=n;j++)
        if(j>mi && j<=far[mi])
        {
            fl=imax(fl,far[j]);
            far[j]=0;
        }
        far[far[mi]+1]=fl;
        far[mi]=0;
    }*/
    printf("%lld\n",ans);
    return 0;
}

第三题 宗教 (SAM+LCA(RMQ优化))

【题目描述】

【解题思路】

  ①将s2串反过来,那么,设st是s1的子串且长度与s2相同,st的结尾串的位置为len(下标从0开始)。
  st与s2的相同字符数就是i<=leni=0[s1[i]==st[leni]]
  这显然是一个卷积的形式。
  那么,直接枚举26个字符,把等于当前枚举的字符的位置设为1,其他为0。做一次FFT,每一个len就对应着不同st串,累加相同的次数。
  时间复杂度:O(26*n log n)
  ②再次观察,k≤100。
  判断st与s2时,直接找k次的最长公共前缀。
  那么,如何快速找出最长的公共前缀呢?
  因为SAM的parents树就是反串的后缀树
  那么,只需要把s1的反串和s2的反串连起来,中间加个特殊符号。跑一次SAM。
  因此,s1[now1]和s2[now2]开始匹配。
  若s1[now1]!=s2[now2],则now1++,now2++(直到相同为止)
  用原串的位置所对应的在SAM的状态,找到parents树上的LCA,也就是最长的公共前缀,长度为dep[LCA]。
  下一次从s1[now1+dep[LCA]]和s2[now2+dep[LCA]]处开始匹配。
  至于LCA,可以用RMQ优化到O(1)。
  
  NOIP2017提高组 模拟赛 27(总结)
  遍历整棵树的每一条边。如上图,遍历的结果就是1 2 4 2 5 6 7 6 5 2 1 3
  边数为m,则最多遍历的点数为2m。
  假设w[x]<w[y](w[i]为第一次遍历到点i的时间戳)
  那么,点x和点y的LCA就是遍历结果中的w[x]~w[y]之间的dep最小的点。
  例如,x=4,y=6时。w[x]=3,w[y]=6。之间的点为2 5,点2的dep最小,因此,点2是x和y的LCA。
  因为w[x]~w[y]就是x走到y的路经+路径上的点的子树。路径上的点的子树是没用的(子树的dep必定大于路径上的点的dep),所以dep最小的点必定是x和y的LCA。
  也就是RMQ维护区间最小值。
  
  时间复杂度:O(n*100+n log n)
  Accept(额,加了register和inline才过)

【代码】

//SAM+LCA(RMQ优化)
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>

#define imax(a,b) ((a>b)?(a):(b))
#define imin(a,b) ((a<b)?(a):(b))

using namespace std;

typedef long long ll;

const int M=600600;
const int N=201000;
char s1[N],s2[N];
int n,m,kk,ans;
int Td,root,last;
int to[M],ne[M],h[M],tt;
int wei[M];
int tis[55],lg2[M<<1];
int rmq[M<<1][22],tim[M],dfstime;

int op[M],tail;

struct state { int par,val,go[27]; } T[M];

void Insert(int w,int yi)
{
    int p=last;
    T[++Td].val=T[p].val+1;
    int np=Td;
    last=wei[yi]=np;
    for(;p && T[p].go[w]==0;p=T[p].par) T[p].go[w]=np;
    if(p==0) T[np].par=root; else
    {
        int q=T[p].go[w];
        if(T[p].val+1==T[q].val) T[np].par=q; else
        {
            T[++Td].val=T[p].val+1;
            int nq=Td;
            memcpy(T[nq].go,T[q].go,sizeof(T[q].go));
            T[nq].par=T[q].par;
            T[q].par=nq;
            T[np].par=nq;
            for(;p && T[p].go[w]==q;p=T[p].par) T[p].go[w]=nq;
        }
    }
}

void addedge(int a,int b) { to[++tt]=b; ne[tt]=h[a]; h[a]=tt; }

/*void dfs(int x)
{
    tim[x]=++dfstime;
    rmq[dfstime][0]=x;
    for(int p=h[x];p;p=ne[p])
    {
        dfs(to[p]);
        rmq[++dfstime][0]=x;
    }
}*/

void dfs(int x)
{
    tail=1; op[1]=x;
    while(tail)
    {
        rmq[++dfstime][0]=op[tail];
        if(h[op[tail]])
        {
            int yt=op[tail];
            op[++tail]=to[h[yt]];
            h[yt]=ne[h[yt]];
        } else tail--;
    }
    for(int i=1;i<=dfstime;i++) 
    if(!tim[rmq[i][0]]) tim[rmq[i][0]]=i;
}

inline int ance(int x,int y) { return ((T[x].val<T[y].val)?(x):(y)); }

inline int len(int x,int y)
{
    x=tim[wei[n-x]];
    y=tim[wei[n+m-y]];
    if(x>y) swap(x,y);
    int ly=lg2[y-x];
    return (T[ance(rmq[x][ly],rmq[y-tis[ly]+1][ly])].val);
}

void ready()
{
    scanf("%s",s1); Td=1; tt=1;
    T[1].val=0; root=1; last=1;
    n=strlen(s1);
    for(int i=n-1;i>=0;i--) Insert(s1[i]-'a',n-i);
    scanf("%s",s2);
    m=strlen(s2);
    Insert(26,0);
    for(int i=m-1;i>=0;i--) Insert(s2[i]-'a',n+m-i);
    scanf("%d",&kk);
    for(int i=2;i<=Td;i++) addedge(T[i].par,i);
    dfstime=0; dfs(1);
    tis[0]=1;
    for(int i=1;i<=25;i++) tis[i]=tis[i-1]<<1;
    lg2[1]=0;
    for(int i=2;i<=dfstime;i++)
    if(tis[lg2[i-1]+1]<=i) lg2[i]=lg2[i-1]+1; else lg2[i]=lg2[i-1];
    for(int j=1;j<=21;j++)
    for(int i=1;i<=dfstime;i++)
    {
        rmq[i][j]=rmq[i][j-1];
        if(i+tis[j-1]<=dfstime) rmq[i][j]=ance(rmq[i][j-1],rmq[i+tis[j-1]][j-1]);
    }
}

int main()
{
    freopen("2257.in","r",stdin);
    freopen("2257.out","w",stdout);
    ready();
    for(register int i=0;i<=n-m;i++)
    {
        int now1=i,now2=0,df=0;
        while(now2<m)
        {
            while(s1[now1]!=s2[now2] && now2<m)
            {
                df++;
                if(df>kk) break;
                now1++; now2++;
            }
            if(df>kk || now2==m) break;
            int go=len(now1,now2);
            now1+=go; now2+=go;
        }
        if(df<=kk) ans++;
    }
    printf("%d\n",ans);
    return 0;
}
//FFT 常数太大 TLE
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>

#define imax(a,b) ((a>b)?(a):(b))
#define imin(a,b) ((a<b)?(a):(b))

using namespace std;

typedef long long ll;

const ll mods=1000000007;
const int N=201000;
const double pi=acos(-1.0);
char st[N];
int d[N],w[N],n,m,kk,k,th[N<<1],sum;

struct Complex
{
    double real,image;
    Complex() {}
    Complex(double _real,double _image)
    {
        real=_real; image=_image;
    }
    friend Complex operator + (Complex A,Complex B) { return Complex(A.real+B.real,A.image+B.image); }
    friend Complex operator - (Complex A,Complex B) { return Complex(A.real-B.real,A.image-B.image); }
    friend Complex operator * (Complex A,Complex B) { return Complex(A.real*B.real-A.image*B.image,A.image*B.real+A.real*B.image); }
} A[N<<2],B[N<<2];
int rev[N<<2];

void FFT(Complex *A,int n,int DFT)
{
    for(int i=0;i<n;i++) if(i<rev[i]) swap(A[i],A[rev[i]]);
    for(int s=1;(1<<s)<=n;s++)
    {
        int mi=(1<<s);
        Complex wn=Complex(cos(2*pi/mi),DFT*sin(2*pi/mi));
        for(int t=0;t<n;t+=mi)
        {
            Complex w=Complex(1,0);
            for(int j=0;j<(mi>>1);j++)
            {
                Complex u=A[t+j],v=w*A[t+j+(mi>>1)];
                A[t+j]=u+v; A[t+j+(mi>>1)]=u-v;
                w=w*wn;
            }
        }
    }
    if(DFT==-1) for(int i=0;i<n;i++) A[i].real/=n,A[i].image/=n;
}

int main()
{
    freopen("2257.in","r",stdin);
    freopen("2257.out","w",stdout);
    scanf("%s",st);
    n=strlen(st);
    for(int i=0;i<n;i++) d[i]=st[i]-'a'+1;
    scanf("%s",st);
    m=strlen(st);
    for(int i=0;i<m;i++) w[i]=st[i]-'a'+1;
    scanf("%d",&kk);
    for(int i=1;i<=(m>>1);i++) swap(w[i-1],w[m-i]);
    n--; m--;
    int yn=n+m,nn; k=0;
    for(nn=1;nn<=yn;nn<<=1) k++;
    for(int i=0;i<nn;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(k-1));

    for(int h=1;h<=26;h++)
    {
        for(int i=0;i<=nn;i++)
        {
            A[i].real=(d[i]==h)?1:0;
            B[i].real=(w[i]==h)?1:0;
            A[i].image=B[i].image=0;
        }
        FFT(A,nn,1); FFT(B,nn,1);
        for(int i=0;i<=nn;i++) A[i]=A[i]*B[i];
        FFT(A,nn,-1);
        for(int i=m;i<=n;i++)
        th[i]+=(int)(A[i].real+0.4);
    }
    for(int i=m;i<=n;i++)
    if(th[i]>=(m+1-kk)) sum++;
    printf("%d\n",sum);
    return 0;
}