2017.9.20

 Z 学数学(math)

要说小 Z 最不擅长的学科,那一定就是数学了。这不,他最近正在学习加法运算。老师为了考核小 Z,给他出了一个问题。

给定一个操作序列,每个操作形如 t a 。如果 t 是 0,那么意味着加上 a;

如果 t 是 1,那么意味着改成 a。那么问题来了,给定一开始有一个数字 0,按照从左到右的顺序执行操作序列中[l,r]段的操作,最后得到的数字是什么?

为了确定小 Z 已经掌握了加法运算,老师给了他许多询问。小 Z 这下可慌了,因为他昨天晚上都在 dota,没有学习,自然也就回答不出问题。他准备向你求助,你能告诉他每个询问的答案吗?

由于数据过大,所以小 Z 告诉了你数据的生成方式,并且你只要告诉他所有答案按照指定方式加密后的结果就行了。

[输入格式]

从 math.in 中读取数据。

一行三个数字 n,m,seed 表示操作数量和询问数量,以及用于生成接下来所需要的数据的变量。

生成数据的模板在下发文件 math.cpp 中给出。

[输出格式]

假设 m 个询问按照输入的顺序分别是 a1,a2,…,am,你需要输出一个整数,它等于

  =1 233 ∗ 对 998244353 取膜的结果。

[样例输入]

5 5 23333

[样例输出]

50333483

[样例解释]

获得的数据分别是

0 880345150

1 787931255

0 2943464295

1 876592220

0 2063957658

3 3

1 4

2 5

2 4

3 5

询问的答案是 2943464295,876592220,2940549878, 876592220, 2940549878

输出 50333483

[数据范围与约定]

对于 30%的数据 n,m<=5000

对于 100%的数据 n,m<=10^7 0<=seed<=10^9

数据生成代码如下:

#include<iostream>
#include<cstdio>
using namespace std;
unsigned int seed,a[10000005];
int n,m,l[10000005],r[10000005],ans=0;
bool type[10000005];
unsigned int GetNext()
{
    seed^=(seed<<7);
    seed^=(seed>>8);
    seed^=(seed<<13);
    return seed;
}
int main()
{
    scanf("%d%d%u",&n,&m,&seed);
    for(int i=1;i<=n;++i) type[i]=GetNext()%2,a[i]=GetNext();  //获得n个操作的t和a 
    for(int i=1;i<=m;++i)  //获得m个询问的左右端点 
    {
        l[i]=GetNext()%n+1,r[i]=GetNext()%n+1;
        int tmp;
        if(l[i]>r[i]) tmp=l[i],l[i]=r[i],r[i]=tmp;
        long long res = 0;
    //    blablabla...
    }
    cout<<ans;
    return 0;
}

 

 

题解:显然赋值操作是会覆盖掉加操作的,所以只要求右端点之前的最后一个赋值操作,然后答案就是赋的值加上后面一段区间的和啦。复杂度 O(n) 。注意爆 longlong 。。。

#include<iostream>
#include<cstdio>
#define MN 10000005
using namespace std;
const int mod=998244353;
unsigned int seed,a[MN];
unsigned long long sum[MN],ans[MN];
int n,m,l[MN],r[MN],z[MN];
bool type[MN];
unsigned int GetNext(){
    seed^=(seed<<7);
    seed^=(seed>>8);
    seed^=(seed<<13);
    return seed;
}
int main(){
    freopen("math.in","r",stdin);
    freopen("math.out","w",stdout);
    scanf("%d%d%u",&n,&m,&seed);
    for(int i=1;i<=n;++i){
        type[i]=GetNext()%2,a[i]=GetNext();
        if(type[i]) z[i]=i;else z[i]=z[i-1];
        sum[i]=sum[i-1]+a[i];
    }
    for(int i=1;i<=m;++i){
        l[i]=GetNext()%n+1,r[i]=GetNext()%n+1;
        int tmp;
        if(l[i]>r[i])tmp=l[i],l[i]=r[i],r[i]=tmp;
        ans[i]=sum[r[i]]-sum[max(l[i],z[r[i]])-1];
    }
    long long res=233,nn=0;
    for(int i=1;i<=m;i++,res=res*233%mod) nn=(nn+res*(ans[i]%mod)%mod)%mod;
    cout<<nn;
}

 

 

小 Z 学英语(english)

好不容易混过了数学课,英语课又快开始了。小 Z 十分紧张,毕竟他还没有背好要考的单词,所以他想争取在英语课之前背完单词。小 Z 要背的单词有 n

个,并且他们有着相同的长度。为了节约时间,小 Z 准备将一些循环同构的单词一起背。

我们称两个单词循环同构,当且仅当满足至少一个下面的条件。

1) 两个单词相同

2) 把其中一个单词的最后一位接到最前面(例如”abc”变成”cab”),得到的单词和另一个单词循环同构。

例如”abc”和”abc”,”cab”,”bca”循环同构。

小 Z 现在想知道这些单词中有多少对<i,j>(i<j),满足第 i 个单词和第 j 个单词循环同构?

[输入格式]

从 english.in 中读取数据。

第一行两个数 n,m,表示有 n 个单词,每个单词的长度是 m 。

接下来 n 行,每行读入一个长度为 m 的单词,单词只包含小写字母。

[输出格式]

输出一个数字表示答案。

[样例输入]

4 4

abcd

acbd

bcda

dabc

[样例输出]

3

[样例解释]

abcd、bcda、dabc 循环同构,答案是 3

[数据范围与约定]

本题采用子任务制,只有你通过一个 subtask 内所有的数据点才能得到这个数据点对应的分数。

Subtask1 包含 30Points,满足 1<=n,m<=300

Subtask2 包含 30Points,满足 m<=5

Subtask3 包含 40Points 没有特殊限制条件

对于所有数据,满足 1<=n,m<=10^6 n*m<=10^6

 

题解:我们发现字符串循环同构虽然长的不一样,但是它的最小表示一定一样。所以我们可以每次 O(n)求一下最小表示,然后通过哈希计算答案。复杂度是O(nm+nlogn)。

#include<iostream>
#include<cstdio>
#define MN 1000000
using namespace std;
char st[MN*2+5];long long ans=0;
int n,m,val[MN+5],c[MN+5][26],cnt=1;
int Solve(){
    int i=1,j=2;
    while(i<=m&&j<=m){
        int k=0;
        while(st[i+k]==st[j+k]&&k+1<m) ++k;
        if(st[i+k]>st[j+k]) i=i+k+1;
        else j=j+k+1;
        if(i==j) ++j;
    }
    return i<j?i:j;
}
int main(){
    freopen("english.in","r",stdin);
    freopen("english.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i){
        scanf("%s",st+1);
        for(int j=1;j<=m;++j) st[j+m]=st[j];
        int pos=Solve(),x=1;
        for(int i=0;i<m;++i){
            if(!c[x][st[pos+i]-'a']) c[x][st[pos+i]-'a']=++cnt;
            x=c[x][st[pos+i]-'a'];
        }
        ans+=val[x]++;
    }
    cout<<ans;
}

 

(WPSpdf转Word要钱。。。)

题解:首先答案肯定是那 m 个数字中的一个。我们考虑从大到小考虑每个数字,合法就输出。确定要判断的数字之后,就只剩下了两种数字,分别大等于或者小于要判断的数字。这里认为大等于的数字是合法的。我们用 f[i]表示 i 的能量值大等于要判断的数字最少需要填入几个合法的数字,那么对于确定的叶子结点,如果他合法,f[i]=0,否则 f[i]=INF。对于不确定的,f[i]=1。然后就有了转移,f[i]就等同于它的三个儿子中比较小的两个 f 的和。权值越小,f[]越小,合法的数字越多,显然可以用二分解决这个问题。

#include<algorithm>
#include<iostream>
#include<cstdio>
#define MN 1000000 
using namespace std;
int s[MN+5],n,m,c[MN+5][3],cnt=0,res=0,mark[MN+5],L[MN+5],mid;
int Dp(int x){
    if(c[x][0]==-1) return mark[x]?1:(s[x]>=mid?0:1e9);
    else{
        int s1=Dp(c[x][0]),s2=Dp(c[x][1]),s3=Dp(c[x][2]);
        if(s1>s2) swap(s2,s1);if(s2>s3) swap(s2,s3);if(s1>s2) swap(s1,s2);
        return min(n+1,s1+s2);
    }
}
int main(){
    freopen("chemistry.in","r",stdin);
    freopen("chemistry.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        if(scanf("%d",&c[i][0])==-1) scanf("%d",&s[i]);
        else scanf("%d%d",&c[i][1],&c[i][2]);
    for(int i=1;i<=m;++i){
        int x;scanf("%d",&x);
        mark[x]=1;
    }
    for(int i=1;i<=n;++i) if(mark[i]) L[++cnt]=s[i];
    sort(L+1,L+cnt+1);int l=1,r=1e9;
    while(l<=r){
        mid=l+r>>1;
        if(Dp(1)<=cnt-(lower_bound(L+1,L+cnt+1,mid)-L)+1) res=mid,l=mid+1;
        else r=mid-1;
    }
    printf("%d\n",res);
    return 0;
}

 

posted @ 2017-09-25 14:09  Vanity  阅读(85)  评论(0)    收藏  举报