Wannafly模拟赛4 题解

A:

FST是一名可怜的小朋友,他很强,但是经常fst,所以rating一直低迷。

但是重点在于,他非常适合ACM!并在最近的区域赛中获得了不错的成绩。
拿到奖金后FST决定买一台新笔记本,但是FST发现,在价格能承受的范围内,笔记本的内存和速度是不可兼得的。

可是,有一些笔记本是被另外一些“完虐”的,也就是内存和速度都不高于另外某一个笔记本,现在FST想统计一下有多少笔记本被“完虐”。

解法:很老的题了,一维排序,二维BIT,不过要离散化一发。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
int n, ans=0, c[maxn];
typedef pair<int,int> pii;
pii node[maxn];
vector <int> v;
int getid(int x){
    return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
int lowbit(int x){
    return x&-x;
}
void update(int x){
    while(x<maxn){
        c[x]++;
        x+=lowbit(x);
    }
}
int query(int x){
    int ret=0;
    while(x>0){
        ret+=c[x];
        x-=lowbit(x);
    }
    return ret;
}
int main(){
    scanf("%d",&n);
    for(int i=0; i<n; i++){
        scanf("%d%d", &node[i].first,&node[i].second);
        v.push_back(node[i].second);
    }
    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());
    int sz = v.size();
    sort(node, node+n);
    for(int i=n-1;i>=0;i--){
        int id = getid(node[i].second);
        if(query(sz)-query(id)) ++ans;
        update(id);
    }
    return 0*printf("%d\n",ans);
}

B:

FST作为小朋友,经常会遇到和距离有关的问题,但是他已经厌倦了曼哈顿距离和欧几里德距离,所以FST就定义了一种FST距离。
这种距离并不用于空间或平面中,而运用于FST发明的一些神奇的算法中(唔... ...)。
设i号元素的特征值为Ai,则i和j的FST距离是 |i2 - j2|+|Ai2 - Aj2|。
为了实现某新的数据结构,FST想在一大堆元素中找出距离最大的一对元素,他不关心是哪一对元素,只想求出最大距离。

解法:假设i>j,因为(i,j哪个大不影响),然后对于A【i】和A【j】讨论一下,就可以知道维护i*i+A[i]*A[i]和i*i-A[i]*A[i]就好了。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
typedef long long LL;
int n;
LL a[maxn], mx1[maxn], mx2[maxn];
int main(){
    scanf("%d",&n);
    for(int i=1; i<=n; i++) scanf("%lld",&a[i]);
    for(int i=1; i<=n; i++) mx1[i] = 1LL*i*i+a[i]*a[i];
    for(int i=1; i<=n; i++) mx2[i] = 1LL*i*i-a[i]*a[i];
    sort(mx1+1, mx1+n+1);
    sort(mx2+1, mx2+n+1);
    LL ans = max(mx1[n]-mx1[1], mx2[n]-mx2[1]);
    return 0*printf("%lld\n", ans);
}

C:

考虑维护一个这样的问题:
(1) 给出一个数组A,标号为1~n
(2) 修改数组中的一个位置。
(3) 询问区间[l,r]中所有子集的位运算and之和mod(109+7)。
位运算and即为“pascal中的and”和“C/C++中的&”
我们定义集合S={ l , l+1 , ... , r-1 , r}
若集合T,T ∩ S = T,则称T为S的子集
设f(T)=AT1 and AT2 and ... and ATk  (设k为T集大小,若k=0则f(T)=0) 
所有子集的位运算and之和即为∑f(T)
那么,现在问题来了。

解法:仔细想一想就知道&运算只要有一个0,这个子集这个位的贡献就是0,所以只要统计,一段区间中这个位的1的个数就可以了。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+10;
typedef long long LL;
const LL mod = 1e9+7;
struct node{
    int a[35];
}Tree[maxn*4];
void build(int l,int r,int rt){
    if(l==r){
        LL x;
        scanf("%lld",&x);
        for(int i=0; i<32; i++,x/=2){
            Tree[rt].a[i]=x%2;
        }
        return;
    }
    int mid=(l+r)/2;
    build(l,mid,rt*2);
    build(mid+1,r,rt*2+1);
    for(int i=0; i<=32; i++) Tree[rt].a[i]=Tree[rt*2].a[i]+Tree[rt*2+1].a[i];
}
void update(int pos, int val, int l, int r, int rt){
    if(l==r){
        for(int i=0; i<=32; i++, val/=2){
            Tree[rt].a[i]=val%2;
        }
        return;
    }
    int mid=(l+r)/2;
    if(pos<=mid) update(pos,val,l,mid,rt*2);
    else update(pos,val,mid+1,r,rt*2+1);
    for(int i=0; i<=32; i++) Tree[rt].a[i]=Tree[rt*2].a[i]+Tree[rt*2+1].a[i];
}
node query(int L,int R,int l,int r,int rt){
    if(L<=l&&r<=R) return Tree[rt];
    int mid=(l+r)/2;
    node p1,p2,p3;
    memset(p1.a,0,sizeof(p1.a));
    memset(p2.a,0,sizeof(p2.a));
    if(L<=mid) p1=query(L,R,l,mid,rt*2);
    if(R>mid) p2=query(L,R,mid+1,r,rt*2+1);
    for(int i=0;i<=32;i++) p3.a[i]=p1.a[i]+p2.a[i];
    return p3;
}
LL b[maxn];
int main(){
    b[0]=1;
    for(int i=1; i<maxn; i++) b[i]=b[i-1]*2%mod;
    int n,q;
    scanf("%d",&n);
    build(1,n,1);
    scanf("%d",&q);
    while(q--){
        int op,l,r;
        scanf("%d%d%d",&op,&l,&r);
        if(op==1) update(l,r,1,n,1);
        else{
            node res = query(l,r,1,n,1);
            LL ans=0;
            for(LL i=0;i<=32;i++)
                ans=(ans+b[i]*(b[res.a[i]]-1)%mod)%mod;
            printf("%lld\n", ans);
        }
    }
    return 0;
}

D:

FST是一名可怜的小朋友,他很强,但是经常fst,所以rating一直低迷。
但是重点在于,他真的很强!他发明了一种奇特的加密方式,这种加密方式只有OIer才能破解。
这种加密方式是这样的:对于一个01串,他会构造另一个01串,使得原串是在新串中没有出现过的最短的串。
现在FST已经加密好了一个串,但是他的加密方式有些BUG,导致没出现过的最短的串不止一个,他感觉非常懊恼,所以他希望计算出没出现过的最短的串的长度。

解法:因为字符串的长度最长不超过1e5,所以01串的长度时很小的,最多大概在13。

所以就可以以字符串的每一位开始,建立长度不超过13的字典树。

然后对于字典树进行BFS,找到第一个没有完整子节点的节点为止,答案就是那个节点的深度

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+7;
struct Tree{
    struct Node{
        int Next[2];
    }node[maxn*4];
    int sz;
    void init(){
        for(int i=0;i<maxn*4;i++){
            memset(node[i].Next,0,sizeof(node[i].Next));
        }
        sz=1;
    }
    void insert(char *s){
        int now=0;
        for(int i=0;i<13;i++){
            if(s[i]=='\0') return;
            int x=s[i]-'0';
            if(!node[now].Next[x]) node[now].Next[x]=sz++;
            now=node[now].Next[x];
        }
    }
    int query(){
        queue<pair<int,int> >q;
        q.push(make_pair(0,1));
        while(!q.empty()){
            pair<int,int>u = q.front();
            int x=u.first,y=u.second;
            q.pop();
            for(int i=0;i<2;i++){
                if(node[x].Next[i]) q.push(make_pair(node[x].Next[i],y+1));
                else return y;
            }
        }
        return 13;
    }
}Tree;
char s[maxn];
int main(){
    Tree.init();
    scanf("%s", s);
    int len = strlen(s);
    for(int i=0;i<len;i++) Tree.insert(s+i);
    printf("%d\n", Tree.query());
    return 0;
}

E:

FST经常会遇到和阶乘有关的问题,但是一个数的阶乘末尾总是会有很多0,FST认为这很不美观,但是FST觉得如果0的个数是偶数的话,还是可以接受的。
所以就有这样一个问题,FST想知道0!,1!,2!... ... (n-1)!,n!中有多少数的末尾0个数是偶数。(注意0!是1,0算偶数)

解法:题解看的这里,超级强啊 http://blog.csdn.net/qianguch/article/details/77525047


#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL n, mi[100], ans, num[100][2], tmp;
void prework(){
    mi[0]=1;
    for(int i=1; i<=25; i++) mi[i]=mi[i-1]*5;
    num[0][0]=1;//k=0
    for(int i=1; i<=25; i++){
        if(i&1){
            num[i][0]=num[i-1][0]*5;
            num[i][1]=num[i-1][1]*5;
        }
        else{
            num[i][0]=3*num[i-1][0]+2*num[i-1][1];
            num[i][1]=3*num[i-1][1]+2*num[i-1][0];
        }
    }
}
int main(){
    prework();
    LL n;
    while(~scanf("%lld", &n)){
        if(n==-1) break;
        ans = 0;
        tmp = 0;
        for(int i=25; i>=0; i--){//相当于在拆成五进制数,从高位做起
            for(int j=1; j<=(n/mi[i]); j++){
                ans+=num[i][tmp];
                if(i&1) tmp^=1;
            }
            n%=mi[i];
        }
        ans += num[0][tmp];
        printf("%lld\n", ans);
    }
    return 0;
}

F:

FST的脑洞非常大,经常幻想出一些奇怪的东西。
某一天,FST幻想出了一棵没有边际的二叉树,脑补着在那棵二叉树上行走的场景。
FST一开始在二叉树的根,然后FST写下了一个由‘L’‘R’两种种字符构成的串,他称这个串为初始串,按照这个串的顺序,碰到一个‘L’就移动到左儿子,碰到一个‘R’就移动到右儿子。FST最后的位置就是他的起点。
然后FST有写下一个串,他称这个串为操作串,由‘U’‘L’‘R’三种字符构成,‘U’表示移动到当前点的父亲(特殊地,我们定义根节点的父亲为根自己),‘L’‘R’同上。
但是FST觉得直接按照操作串一步一步走十分无聊,所以FST会跳过一些操作(心情不好的时候也可能跳过所有操作,或不跳过),现在FST想知道他会走到多少种可能的终点。
由于答案可能很大,所以只需输出答案mod (109+7)的值。

解法:

原文链接:http://blog.csdn.net/g19zwk/article/details/77528351

算法一: 构树 30%
我们可以用某种方式把树构出来,然后枚举子序列在树上遍历。
表示出来的树的大小不会超过 220

算法二: 构一部分树 50%
因为在树上遍历的范围只和操作串有关,所以构树时深度只要保留 10 层就行了。

算法三: 特殊点讨论 70%
操作没有 U,也就是只会往下走,那么我们可以 DP
状态有 4 种,没有去过左儿子的点的个数,没有去过右儿子的点的个数,左右儿
子都没去过的点的个数,左右儿子都去过的点的个数。
转移根据这个状态定义应该显然。

算法四: 100%
对于 U 操作,我们只需做一些小修改。
因为 LR操作会和 U 操作抵消,所以我们的实际操作时的使用序列一定是这样的:
UU…UUULRLLRLRLRLLRL…….
也就是 U 操作只会被用在开头,那么处于这个性质,我们可以对初始串(起点)维护一个栈(维护 LR 方向) ,那么每次遇到 U 操作就取出栈顶,判断需要加入哪种新点。
更简单的理解就是,因为可以跳过点,所以每个能走到的点都可以是终点,所以把它们都涂黑。这样,有新操作的时候,就把它们的左/右儿子也涂黑就行(不用建树,dp就行,见代码);而对于找父亲的操作,因为下面的点都是由父亲走到的,所以只用关心最上面的那个点还有没有父亲,可不可以更新(见代码)。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=1e5+5,mod=1e9+7;
int n,m;
char t[maxn];
LL ans,f[4];
//f[1]表示只走过左儿子的点的个数,f[2]表示只走过右儿子的点的个数;
// f[0]表示两个儿子都没走过的点的个数,f[3]表示两个儿子都走过的点的个数;
char s1[maxn],s2[maxn];
int main(){
    scanf("%s", s1);
    int len1 = strlen(s1);
    for(int i=0; i<len1; i++) t[++m]=s1[i];
    scanf("%s", s2);
    int len2 = strlen(s2);
    f[0]=1;
    for(int i=0; i<len2; i++){
        if(s2[i]=='U'){//读到走父亲
            if(m){
                char cc=t[m--];
                if(cc=='L') f[1]++;
                else f[2]++;
            }
        }
        else if(s2[i]=='L'){//读到走左儿子
            f[1]=(f[1]+f[0])%mod;//所有没有走过儿子的点更新成只走了左儿子的点
            f[3]=(f[3]+f[2])%mod;//只走了右儿子的点更新成所有儿子都走了的点
            f[0]=(f[0]+f[2])%mod;//没有走过儿子的点更新成之前没有走过儿子的点左儿子的个数加上只走了右儿子的点左儿子个数
            f[2]=0;//只走过右儿子的点就没了
        }
        else if(s2[i]=='R'){
            f[2]=(f[2]+f[0])%mod;
            f[3]=(f[3]+f[1])%mod;
            f[0]=(f[0]+f[1])%mod;
            f[1]=0;
        }
    }
    for(int i=0;i<4;i++) ans=(ans+f[i])%mod;
    return 0*printf("%lld\n", ans);
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值