SDOI2016 R1 day2 解题报告(bzoj4516,bzoj4517,bzoj4518)

感言什么的 之后补游记吧

只能说考场没AK,我是傻逼

生成魔咒

题意

给一个字符串,初始为空串,然后往字符串尾部依次添加字符,每添加一个字符询问当前串中本质不同的子串的个数。

数据范围

60%:n<=1000
100%:n<=100000,1<=字符集<=10^9

做过【bzoj3926】[Zjoi20150]诸神眷顾的幻想乡的,会发现这两个题神似,并且这个题还是诸神眷顾的幻想乡的弱化版。

然而数据范围中的字符集太大,貌似SAM不可取?

出题人faebdc给的做法是求反串的SA,然后在SA中的height数组中一个个删除后缀,删除时减去当前后缀的相邻后缀的lcp,再加上新的相邻一对的lcp,更新答案。

SAM的做法就很简单了…边数和点数竟然都是O(n)的,map能过!

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;

typedef long long LL;
const int SZ = 1000010;

struct node{
    map<int,node*> ch;
    node *par;
    int val;
}T[SZ], *last, *root;

int Tcnt = 0;

node* newnode(int x)
{
    node *k = T + (Tcnt ++);
    k -> val = x;
    k -> par = NULL;
    k -> ch.clear();
    return k;
}

LL ans = 0;

LL get_ans(node *p)
{
    return p -> val - p -> par -> val;
}

void insert(int x)
{
    node *p = last,*np = newnode(p -> val + 1);
    while(p && !p -> ch[x])
        p -> ch[x] = np,p = p -> par;
    if(!p)
        np -> par = root,ans += get_ans(np);
    else
    {
        node *q = p -> ch[x];
        if(q -> val == p -> val + 1)
            np -> par = q,ans += get_ans(np);
        else
        {
            node *nq = newnode(p -> val + 1);
            nq -> ch = q -> ch;

            nq -> par = q -> par; ans += get_ans(nq);
            np -> par = nq; ans += get_ans(np);
            ans -= get_ans(q); q -> par = nq; ans += get_ans(q);

            while(p && p -> ch[x] == q)
                p -> ch[x] = nq,p = p -> par;
        }
    }
    last = np;
}

void init()
{
    root = newnode(0);
    last = root;
}

int main()
{
    init();
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n;i ++)
    {
        int x;
        scanf("%d",&x);
        insert(x);
        printf("%lld\n",ans);
    }
    return 0;
}
/* 7 1 2 3 3 3 1 2 */

排列计数

题意

求n的排列中,有多少个排列满足恰好有m个 a[i]=i 。多组数据。

样例输入

5
1 0
1 1
5 2
100 50
10000 5000

样例输出

0
1
20
578028887
60695423

数据范围

60%:T<=1000,n,m<=1000
70%:T<=500000,n,m<=1000
100%:T<=500000,n,m<=1000000

全排列二十分。状压DP30分(然而这三十分好像都是打表)。出题人给的容斥原理是70分算法,就是求 Cin(1)i(ni)! 。然而预处理一下就A掉了。

不过我脸好知道错排…答案是 Cmnf(nm) ,其中f是错排公式 f(n)=(n1)(f(n1)+f(n2)) 。然后这题我开考半小时就水掉了…

下面是考场代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long LL;
const int SZ = 1000010;
const int mod = 1000000007;

int f[SZ],fac[SZ],c[1010][1010];

void exgcd(LL a,LL b,LL &x,LL &y)
{
    if(b == 0)
    {
        x = 1; y = 0; return ;
    }
    exgcd(b,a % b,x,y);
    LL t = x; x = y; y = t - a / b * y;
}

LL ni(LL a)
{
    LL x,y;
    exgcd(a,mod,x,y);
    return (x % mod + mod) % mod;
}

LL C(int n,int m)
{
    return (LL)fac[n] * ni((LL)fac[n - m] * fac[m] % mod) % mod;
}

void scan(int &n)
{
    n = 0;
    char a = getchar();
    bool flag = 0;
    while(a < '0' || a > '9') { if(a == '-') flag = 1; a = getchar(); }
    while(a >= '0' && a <= '9') { n = n * 10 + a - '0'; a = getchar(); }
    if(flag) n = -n;
}

int main()
{
    freopen("permutation.in","r",stdin);
    freopen("permutation.out","w",stdout);          
    f[0] = 1; f[1] = 0;
    for(int i = 2;i <= 1000000;i ++)
        f[i] = (LL)(i - 1) * ((LL)f[i - 1] + f[i - 2]) % mod;
    fac[0] = fac[1] = 1;
    for(int i = 2;i <= 1000000;i ++)
        fac[i] = (LL)i * fac[i - 1] % mod;
    c[0][0] = 1;
    for(int i = 1;i <= 1000;i ++)
    {
        c[i][0] = 1;
        for(int j = 1;j <= i;j ++)
            c[i][j] = (LL)(c[i - 1][j - 1] + c[i - 1][j]) % mod;
    }

    int T;
    scan(T);
    while(T --)
    {
        int n,m;
        scan(n);  scan(m);
        if(n < m) puts("0");
        else
        {
            if(n <= 1000 && m <= 1000)
                printf("%I64d\n",(LL)c[n][m] * f[n - m] % mod);
            else
                printf("%I64d\n",(LL)C(n,m) * f[n - m] % mod);
        }
    }
    fclose(stdin); fclose(stdout);
    return 0;
}
/* 5 1 0 1 1 5 2 100 50 10000 5000 */

征途

题意

把n个数的数列分成m段,每段元素必须连续。每一段的权值是这一段中所有数之和,求最小化方差v。输出 vm2 ,这个数必定为整数。

数据范围

60%:1<=m<=n<=100
100%:1<=m<=n<=3000, ai>0ai<=30000

考场上我傻X,写的DP是三维的,40分。

化简一下目标函数。其实是要求每段的平方和最小。

f[i][j]为当前走到第i个,当前是第j段的最小平方和,很容易写出方程:

f[i][j]=min{f[k][j1]+(s[i]s[k])2}

这就60分。

发现可以斜率优化。固定j之后,发现满足这个关系:

(fq,j1+S2q)(fw,j1+S2w)SqSw<2Si

其中q优于w且 q>w

然后因为二维的,需要记录上一层的函数值,被这个坑的调了半天……

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;

typedef long long LL;
const int SZ = 3010;
const LL INF = 10000000000000010ll;
LL f[SZ],a[SZ],s[SZ];

LL pf(LL x)
{
    return x * x;
}

struct haha{
    LL s,x;
}q[SZ];

int t = 0,w = 0;

double xl(haha q,haha w)
{
    return (q.s - w.s * 1.0) / (q.x - w.x * 1.0);
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i ++)
    {
        scanf("%lld",&a[i]);
        s[i] = s[i - 1] + a[i];
    }

    for(int i = 1;i <= n;i ++)
        f[i] = pf(s[i]);
    for(int j = 2;j <= m;j ++)
    {   
        t = 1; w = 0;
        for(int i = 1;i <= n;i ++)
        {
            haha np = (haha){f[i] + pf(s[i]),s[i]};
            while(t < w && xl(np,q[w]) < xl(q[w],q[w - 1])) w --;
            q[++ w] = np;

            while(t < w && xl(q[t + 1],q[t]) < 2 * s[i]) t ++;

            f[i]=s[i]*s[i]+q[t].s-2*q[t].x*s[i];
        }
    }
    printf("%lld",f[n] * m - pf(s[n]));
    return 0;
}

最后插一句,这个题骗分在bzoj能A掉…考场上也能90分……

你可能感兴趣的:(SDOI2016 R1 day2 解题报告(bzoj4516,bzoj4517,bzoj4518))