2017暑假第二阶段第四场 总结

T1 果冻怪

时间限制 1s 空间限制 128MB

问题描述

小南和小开在三友路上养了很多只果冻怪。我们可以将三友路想象成一根长度无限的数 轴,在这上面生活着n只果冻怪。每经过一秒,一只果冻怪便会分裂成两只。具体来说,一 只坐标为x的果冻怪,会分裂成两只分别在(x − 1),(x + 1)上的果冻怪,并且原来在x上的果冻怪会消失。 由于生存空间有限,若一个位置上有不少于P只果冻怪,那么会立刻消失 P 只。经过测 定P = 10^9 + 7。 小南和小开想知道在第T秒末,位置w有多少只果冻怪。初始时刻是0秒初。

输入格式

第一行为三个整数,n,T,w。含义如题所述。
接下来 n 行,每行两个整数xi,ci,表示xi位置,有ci只果冻怪。注意xi可能有重复。

输出格式

输出一个非负整数,表示 T 秒末 w 位置上的果冻怪个数

数据范围

对于 100%的数据:1 ≤ n,T,ci ≤ 10 5 ,0 ≤ |w|,|xi| ≤ 10 5


考虑n个果冻怪,可以看成多个果冻怪的影响相加,那么首先考虑1个果冻怪的情形。画出1个果冻怪的分裂情况,则很容易看出是一个杨辉三角形的形式。结合w、x、T之间的关系不难得出结论。由于需要计算的组合数全部在杨辉三角的同一层,用O(n)的循环预处理出该层组合数即可。

当abs(w-x)与T奇偶性不同时不会增加果冻怪。当w > x+T或w < x-T时不会增加果冻怪。

注意数据规模,不能使用Lucas定理。在这样的规模下Lucas退化成了O(n)的暴力求组合数。容易因为习惯性思维和模质数的条件写成Lucas超时。

#include
#define ll long long
const ll mod=1e9+7;
ll Ans,N,T,W,X,Y,C[100005];

ll ksm(ll a,ll b)
{
    ll ans=1;a%=mod;
    while(b)
    {
        if(b&1)ans=ans*a%mod;
        a=a*a%mod;b>>=1;
    }
    return ans;
}

int main()
{
    int i;
    ll tmp;

    scanf("%lld%lld%lld",&N,&T,&W);

    C[0]=1;C[1]=T;
    for(i=2;i<=T;i++)C[i]=C[i-1]*(T-i+1)%mod*ksm(i,mod-2)%mod;//预处理
    for(i=1;i<=N;i++)
    {
        scanf("%lld%lld",&X,&Y);
        if(W>X)tmp=W-X;
        else tmp=X-W;
        if((tmp&1)!=(T&1))continue;
        if(W>X+T||Wcontinue;
        Ans=(Ans+C[T+1-(((T+2)>>1)+(tmp>>1))]*Y%mod)%mod;
    }
    printf("%lld",Ans);
}

T2 解密游戏

时间限制 2s 空间限制 384MB

问题描述

小南和小开特别喜欢玩解密游戏,轮到小南加密的时候,由于他的加密方式过于丧心病 狂,所以小开怎么也不能解密成功,于是她来找你帮忙。 密文是一个长度为 n 的数字串,只由 0~9 之间的数字组成。每个小写字母对应 0~9 之 间的一个数字。小南和小开共同拥有一本字典,字典中有 m 个单词,每个单词长度不超过 50。 明文是一个数字,表示最少用多少个单词首尾拼接在一起,使得拼接而成的这个字符串 可以表示密文(也即相同位置的字符串中字母对应数字跟密文相同)。单词可以重复使用。
输出明文,如果无解的话明文为-1。

输入格式

第一行两个正整数 n,m。
第二行有 26 个数字,每个数字是 0~9 之间的数,分别表示字母 a~z 对应的数字。
第三行是长度为 n 的数字串,表示密文。 接下来 m 行,每行一个小写字母串,表示字典中的一个单词。

输出格式

输出一个整数,表示明文

数据范围

对于 30%的数据:1 ≤ n,m ≤ 1000。
对于 100%的数据:1 ≤ n,m ≤ 10 5


注意到本题的空间限制比较大,是其他题目的三倍,再考虑到本题一定会涉及字符串的匹配,那么字典树是比较理想的选择。

显然可以看成背包动规。令f[i]表示解密完前i个字母时的最小单词数,那么显然得到状态转移方程:

f[i]=min(f[j]+1)j<ij+1i

那么我们从密文的第一位开始,将i~n之间的字符串在字典树中进行匹配,一边匹配一边更新f数组即可。由于单词长度不超过50,那么字典树的深度最多也是50,所以最多计算50*10 5 次。

#include
#include
#define Min(x,y) ((x
int N,M,Hash[500];
int f[100005];

int rt=1,tot=1,Son[5000005][10];
bool num[5000005];
//trie
char s[100],ask[100005];

void Insert()
{
    int len,i,p=rt,t;

    scanf("%s",s);len=strlen(s);
    for(i=0;iif(!Son[p][t])Son[p][t]=++tot;
    }
    num[p]=true;
}

void Find(int l)
{
    int p=rt,i,t;
    for(i=l;i48;
        if(num[Son[p][t]])f[i+1]=Min(f[i+1],f[l]+1);
    }
}

int main()
{
    int i;

    scanf("%d%d",&N,&M);
    for(i=0;i<26;i++)scanf("%d",&Hash[i+'a']);
    scanf("%s",ask);
    for(i=1;i<=M;i++)Insert();

    memset(f,60,sizeof(f));
    f[0]=0;
    for(i=1;i<=N;i++)Find(i-1);

    if(f[N]==f[100001])puts("-1");
    else printf("%d",f[N]);
}

T3 分割财产

2017暑假第二阶段第四场 总结_第1张图片

遇到的构造类第一题。比赛时完全没有思路,就写了个比较精巧的骗分,竟然还骗到了90分。下面还是简要介绍一下骗分算法:

首先构造出这样的数列:
2017暑假第二阶段第四场 总结_第2张图片

再将原数列排序后,用对应位置的差去填充上面的空位即可。这样的骗分可以保证出现 n2 个不同的元素,而且很好写,所以是比较划得来的。

90分骗分代码:

#include
#include
using namespace std;
int N,data[5005],Hash[5005],Ans[5005][2];

int main()
{
    int i,j=-1;

    scanf("%d",&N);
    for(i=1;i<=N;i++)scanf("%d",&data[i]),Hash[i]=data[i];
    sort(Hash+1,Hash+N+1);
    for(i=1;i<=N;i++)
    {
        if(i&1)j++;
        Ans[i][i&1]=Hash[i]-j;
        Ans[i][i-1&1]=j;
    }

    puts("YES");
    for(i=1;i<=N;i++)
    {
        j=lower_bound(Hash+1,Hash+N+1,data[i])-Hash;
        printf("%d ",Ans[j][0]);
    }
    putchar('\n');
    for(i=1;i<=N;i++)
    {
        j=lower_bound(Hash+1,Hash+N+1,data[i])-Hash;
        printf("%d ",Ans[j][1]);
    }
}

正解

将原数列按照大小排序并重新编号,之后分成长度为 n3 的三段开始构造:

设编号为i。

第一段:将i-1赋给ai,将si-ai赋给bi。
第二段:将i-1赋给bi,将si-bi赋给ai。
第三段:将n-i赋给bi,将si-bi赋给ai。

可以看出,ai的第一段与第三段的值是不会重复的,bi的第二段与第三段的值也是不会重复的( n3 -1< si-( 2n3 -1)),这样就满足了要求。

由此也可以看出,这道题无论如何都有解。因此只输出”NO”连一分也不能骗到。

#include
#include
using namespace std;
int N,S[5005],Hash[5005],A[5005],B[5005];

int main()
{
    int i,j,K;

    scanf("%d",&N);
    for(i=1;i<=N;i++)scanf("%d",&S[i]),Hash[i]=S[i];
    sort(Hash+1,Hash+N+1);

    K=(N+2)/3;
    for(i=1;i<=K;i++)
    {
        A[i]=i-1;
        B[i]=Hash[i]-A[i];
    }
    for(;i<=K*2;i++)
    {
        B[i]=i-1;
        A[i]=Hash[i]-B[i];
    }
    for(;i<=N;i++)
    {
        B[i]=N-i;
        A[i]=Hash[i]-B[i];
    }

    puts("YES");
    for(i=1;i<=N;i++)
    {
        j=lower_bound(Hash+1,Hash+N+1,S[i])-Hash;
        printf("%d ",A[j]);
    }
    putchar('\n');
    for(i=1;i<=N;i++)
    {
        j=lower_bound(Hash+1,Hash+N+1,S[i])-Hash;
        printf("%d ",B[j]);
    }
}

总结

T1是简单的组合数公式;T2是trie+DP,T3是构造。

T1思想江化了,看到组合数模一个质数就想到了Lucas定理,却忽略了时间复杂度导致只得了80分;T2做得不错,只是一开始由于字符数组编号0开始存导致了一些编写上的问题,拖慢了速度;T3的骗分想起来还是比较机智的,以后拿到没见过的题型也要尽量得分。

你可能感兴趣的:(一些总结,数学,trie,DP,构造)