sdoi2016 day 2

4 篇文章 0 订阅
2 篇文章 0 订阅

好久没写题解了,随便写一发SDOI的题解吧,你问我为什么身为JL的选手不写JLOI,因为我太弱了不会啊QWQ

T1 生成魔咒:http://www.lydsy.com/JudgeOnline/problem.php?id=4516

题解

后缀自动机模板题,在加入一个值时直接得出已经加入自动机的字符的子串个数,后缀自动机用map来维护

#include <bits/stdc++.h>
#define N 200010
using namespace std;
typedef long long ll;
int n;

struct sam
{
    int cnt,last,p,q,np,nq;
    ll ans;
    int len[N],fa[N];
    ll size[N];
    map<int,int>son[N];
    map<int,int>::iterator it;

    sam(){last=cnt=1,size[1]=1;}

    void Insert(int x)
    {
        p=last,last=np=++cnt,len[np]=len[p]+1;
        while(p&&!son[p][x])son[p][x]=np,size[np]+=size[p],p=fa[p];
        ans+=size[np];
        if(!p)fa[np]=1;
        else{
            q=son[p][x];
            if(len[q]==len[p]+1)fa[np]=q;
            else{
                nq=++cnt;
                len[nq]=len[p]+1;
                fa[nq]=fa[q];
                for(it=son[q].begin();it!=son[q].end();it++)
                    son[nq][it->first]=it->second;
                fa[q]=fa[np]=nq;
                while(son[p][x]==q)son[p][x]=nq,size[q]-=size[p],size[nq]+=size[p],p=fa[p];
            }
        }
    }

    void build()
    {
        for(int x,i=1;i<=n;i++)
        {
            scanf("%d",&x);
            Insert(x);
            printf("%lld\n",ans);
        }
    }
}sam;

int main()
{
//  freopen("tt.in","r",stdin);
    scanf("%d",&n);
    sam.build();
    return 0;
}

T2 排列计数:http://www.lydsy.com/JudgeOnline/problem.php?id=4517

题解

简单排列组合+dp xjk教我了一个据说他初中学的公式(orz)f[i]表示有i个数且a[j]!=j(0< j=i),f[i]=(f[i-1]+f[i-2])*(i-1)
预处理出这个式子之后,对于一组询问(n,m)即为 Cmnf[nm]
用卢卡斯定理随便求一下组合数,即可求得答案

#include <bits/stdc++.h>
#define LEN 1<<16
char getc()
{
    static char *S,*T,buf[LEN];
    if(S==T)
    {
        T=(S=buf)+fread(buf,1,LEN,stdin);
        if(S==T)return EOF;
    }
    return *S++;
}
int read()
{
    static char ch;
    static int D,F;
    F=1;
    while(!isdigit(ch=getc())&&ch!='-');
    if(ch=='-')F=-1,ch=getc();
    for(D=ch-'0';isdigit(ch=getc());)
        D=D*10+ch-'0';
    return D*F;
}
#define N 1000000
#define mod 1000000007
using namespace std;
typedef long long ll;
int n,m;
int f[N+10];
int a[N+10];

void init()
{
    f[0]=1,f[2]=1,f[3]=2;
    for(int i=4;i<=N;i++)
        f[i]=(ll)(f[i-1]+f[i-2])*(i-1)%mod;
    a[0]=a[1]=1;
    for(int i=2;i<=N;i++)
        a[i]=(ll)a[i-1]*i%mod;
}

int qpow(int x,int y)
{
    int ans=1;
    while(y)
    {
        if(y&1)ans=(ll)ans*x%mod;
        x=(ll)x*x%mod;
        y>>=1;
    }
    return ans;
}

int lucas(int x,int y)
{
    if(y==0)return 1;
    return (ll)a[x]*qpow(a[y],mod-2)%mod*qpow(a[x-y],mod-2)%mod*lucas(x/mod,y/mod)%mod;
}

int main()
{
//  freopen("tt.in","r",stdin);
    init();
    int TOT=read();
    while(TOT--)
    {
        n=read(),m=read();
        printf("%d\n",(ll)lucas(n,m)*f[n-m]%mod);
    }
    return 0;
}

T3 征途:http://www.lydsy.com/JudgeOnline/problem.php?id=4518
题意是将n个数分成m组,求出m组数的方差
设第i组数的和为 yi
mi=1yi=ni=1xi

M=mi=1yim=ni=1xim

ans=mi=1(yiM)2m=mM22Mmi=1yi+mi=1y2im=mM22Mni=1xi+mi=1y2im

这道题就变为求 mi=1y2i 的最小值,其它元素可以直接处理出来
这里用一个简单的dp就能求出来:f[i][j]表示第i天在第j个休息站休息,sum[i]表示从1到第i个休息站的路程
f[i][j]=min(f[i-1][k]f[i-1][k]+(sum[j]-sum[k])*(sum[j]-sum[k]))(0< k<=j)
但这是 O(n3) 的复杂度,还需要用斜率优化将复杂度降到 O(n2)

#include <bits/stdc++.h>
#define N 3010
using namespace std;
typedef long long ll;
int n,m;
int a[N],sum[N];
ll f[N][N];
int que[N];

double calc(int s,int x,int y)
{
    return (f[s][y]+sum[y]*sum[y]-f[s][x]-sum[x]*sum[x])/(2*(sum[y]-sum[x]));
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
    ll ans=sum[n]*sum[n]-2*sum[n]*sum[n];
    memset(f,-1,sizeof(f));
    f[0][0]=0;
    for(int i=1;i<=m;i++)
    {
        int head=1,tail=0;
        que[++tail]=i-1;
        for(int j=i;j<=n;j++)
        {
            while(head<tail&&calc(i-1,que[head],que[head+1])<sum[j])
                head++;
            int x=que[head];
            f[i][j]=f[i-1][x]+sum[j]*sum[j]+sum[x]*sum[x]-2*sum[j]*sum[x];
            if(f[i-1][j]==-1)continue;
            while(head<tail&&calc(i-1,que[tail],j)<calc(i-1,que[tail-1],que[tail]))
                tail--;
            que[++tail]=j;
        }
    }
    cout<<ans+f[m][n]*m<<endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值