分块

cf375D—莫队裸题
bzoj3509           分块+FFT
对于30分的部分分:
考虑枚举j。我们可以用pre[t]表示在前j个数中,t这个数出现了多少次,nxt[t]表示后n-j个数中,t这个数出现了多少次。那么答案就是 u+v=2Ajpre[u]×nxt[v]

满分做法:
上面的式子很熟悉吧?就是卷积的形式。不妨将序列分块,pre[t]维护当前块前面,t这个数出现多少次,nxt[t]维护当前块后面,t这个数出现多少次。那么我们只需要对于每个块的pre和nxt做一次fft,即可快速求出i端点在块前面,j端点在块里面,k端点在块后面的答案。接下来只需要暴力求一下i, k在块里面的答案即可。

bzoj2120 数颜色       带修改莫队
题意:
墨墨购买了一套N支彩色画笔(其中有些颜色可能相同)
1、 Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔。
2、 R P Col 把第P支画笔替换为颜色Col。

bzoj2821YES
bzoj3289YES
bzoj3757
bzoj3781YES
bzoj2002YES
bzoj2141YES
bzoj2724
bzoj2388
bzoj2453
bzoj3343
bzoj2038
bzoj1101
bzoj4216
bzoj1086
bzoj3236
bzoj3809

chef ans churu

题意:

有一个长度为n的数组A,有n个函数,第i个函数的值为

这里写图片描述

有两种操作:
修改A[i]
询问第l~r个函数值的和。
数据范围: n100000

分析:

分块大法好~
首先将 l[],r[] 数组分块,能在 O(n) 内求出一块内所有函数包含某一个位置的次数,接着便能求出块内的答案总和。预处理的时间复杂度为 O(nn
修改操作用树状数组维护一下单点(询问的区间会多出两端)。用 n 的时间暴力更改每一块的总和。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>

using namespace std;
const int N=100100;
typedef unsigned long long ll;
int l[N],r[N],pos[N],s[N],t[N];
int n,m,op,x,y,kuai,cnt,ss[500][N];
ll c[N],a[N],ans[N];

void ins(int x,int val){
    while (x<=n){
        c[x]+=val;
        x+=x&-x;
    }
}

ll query(int x){
    ll res=0;
    while (x){
        res+=c[x];
        x-=x&-x;
    }
    return res;
}

ll query(int x,int y){
    ll res=0;
    int L=pos[x],R=pos[y];
    for (int i=L+1;i<R;i++)
        res+=ans[i];
    if (L==R){
        for (int i=x;i<=y;i++)
            res+=query(t[i])-query(s[i]-1);
    } else {
        for (int i=x;i<=r[L];i++)
            res+=query(t[i])-query(s[i]-1);
        for (int i=l[R];i<=y;i++)
            res+=query(t[i])-query(s[i]-1);
    }
    return res;
}

int main(){
    scanf("%d",&n);
    kuai=(int)sqrt(n);
    cnt=n/kuai+(n%kuai!=0);
    for (int i=1;i<=n;i++){
        pos[i]=(i-1)/kuai+1;
        if (!l[pos[i]])
            l[pos[i]]=i;
        r[pos[i]]=i;
    }
    for (int i=1;i<=n;i++)
        scanf("%d",a+i);
    for (int i=1;i<=n;i++)
        scanf("%d%d",s+i,t+i);

    for (int i=1;i<=cnt;i++){
        for (int j=l[i];j<=r[i];j++)
            ins(s[j],1),ins(t[j]+1,-1);
        for (int j=1;j<=n;j++){
            ss[i][j]=query(j);
            ans[i]+=a[j]*ss[i][j];
        }
        for (int j=l[i];j<=r[i];j++)
            ins(s[j],-1),ins(t[j]+1,1);
    }
    for (int i=1;i<=n;i++)
        ins(i,a[i]);
    scanf("%d",&m);
    while (m--){
        scanf("%d%d%d",&op,&x,&y);
        if (op==1){
            ins(x,y-a[x]);
            for (int i=1;i<=cnt;i++)
                ans[i]+=ss[i][x]*(y-a[x]);
            a[x]=y;
        } else printf("%llu\n",query(x,y));
    }
}

51NOD 1386

题意:

从1~n中选出尽可能少的数,使得剩下的每个数至少和一个被选择的数不互质,k不能被选择
数据范围:n,k<=1000

分析:

题目相当于被选择的数分解质因数后包含所有质因数
每个数分解质因数之后最多只含有一个大于sqrt(n)的质因子
以这个质因子为依据将所有数进行分类
同时记录质因子2、3、5、7、11、13、17、19、23、29、31有没有出现过
然后就可以高兴地动归了~
f[i][j] 表示前i个数状态为j的答案。
因为大于31的数的质数一定要选,但它可以和2~31的质数合并,那么我们一开始就加上31以后的质数个数,在选大于31的合数分解后有大于31的质数是,就相当于没有加数。(还是看代码吧

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=3000;
int v[N],p[N],pre[N],f[2][N],c[2][N];
int n,k,tot,id[N],cas,key[N],mx[N];

int min(int x,int y){return x<y?x:y;}

int main(){
    freopen("a.in","r",stdin);
    int T;scanf("%d",&T);
    v[1]=1;
    for (int i=2;i<N;i++){
        if (!v[i]) p[++tot]=i,pre[i]=1,v[i]=1;
        for (int j=1;j<=tot && i*p[j]<N;j++){
            pre[i*p[j]]=i;
            v[i*p[j]]=1;
            if (i%p[j]==0) break;
        }
    }
    for (int i=1;i<=tot;i++)
        id[p[i]]=i;
    for (int i=1;i<N;i++){
        int t=0,kk=i;
        while (kk!=1){
            int tt=kk/pre[kk];
            if (tt<=31){
                if (!(t&(1<<(id[tt]-1))))
                    t+=(1<<(id[tt]-1));
            }
            mx[i]=max(mx[i],tt);
            kk=pre[kk];
        }
        key[i]=t;
    }
    while (T--){
        scanf("%d%d",&n,&k);
        if (cas==22)
            printf("");
        memset(f,0x16,sizeof(f));
        f[0][0]=0;
        int len=1;
        while (p[len]<=n) len++;len--;
        int ans=(len>11)?len-11:0;
        len=min(len,11);
        int p=1;
        for (int i=1;i<=n;i++){
            int t=key[i];
            for (int j=0;j<(1<<len);j++){
                f[p][j]=f[p^1][j];
                if ((t&j)==t && i!=k){
                    int tt=0;
                    if (mx[i]>31) tt=1;
                    if (f[p][j]>f[p^1][j^t]+1-tt)
                        f[p][j]=min(f[p][j],f[p^1][j^t]+1-tt);
                }
            }
            for (int j=0;j<(1<<len);j++)
                f[p^1][j]=370546198;
            f[p^1][0]=0;
            p^=1;
        }
        printf("Case #%d: %d\n",++cas,f[p^1][(1<<len)-1]+(k!=1)+ans);
    }
}

代码好像更难懂....

children trips

题意:

有一棵n个节点 n100000 边权为1或2的树,有m个询问 m100000 。每次询问一个人从x点走到y点,每一步只能走边权总和小于等于k,问最少走多少步(每一步结束后必须在节点上)有点绕

分析

对k分情况进行讨论
kn
最多走sqrt(n)步,直接在 fa[][] 上暴力走就好了
kn
对于每个k一起处理,每次倍增 dp[i][j] 表示从节点i走 2j 步能走到哪(边权总和 k
时间复杂度 O(nnlogn)

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=200100;
const int kuai=600;
int a[N][18],dp[N][18],dis[N],dep[N];
int h[N],q[N],ch[N],q1[N],q2[N],ans[N];
int cnt,n,m,tot,x,y,z,top1,top2;
struct edge{int y,next,z;}g[N];
struct que{int x,y,id,c,next;}que[N];

void adp(int x,int y,int z){
    g[++tot].y=y;
    g[tot].z=z;
    g[tot].next=h[x];
    h[x]=tot;
}

void dfs(int x){
    for (int i=h[x];i;i=g[i].next)
    if (a[x][0]!=g[i].y){
        a[g[i].y][0]=x;
        dep[g[i].y]=dep[x]+1;
        dis[g[i].y]=dis[x]+g[i].z;
        dfs(g[i].y);
    }
}

void adpque(int x,int y,int z,int id){
    que[++cnt].y=y;
    que[cnt].x=x;
    que[cnt].id=id;
    que[cnt].next=ch[z];
    ch[z]=cnt;
}

void dfsdp(int x,int c){ //q[i]表示从根节点走i的距离能到哪
    int d=max(0,dis[x]-c);
    dp[x][0]=q[d]?q[d]:q[d+1];
    q[dis[x]]=x;
    for (int i=h[x];i;i=g[i].next)
    if (a[x][0]!=g[i].y)
        dfsdp(g[i].y,c);
    q[dis[x]]=0;//*****注意退出*****//
}

int lca(int x,int y){
    if (dep[x]<dep[y])
        swap(x,y);
    for (int i=17;i>=0;i--)
        if ((dep[x]-dep[y])&(1<<i))
            x=a[x][i];
    if (x==y) return x;
    for (int i=17;i>=0;i--)
        if (a[x][i]!=a[y][i]){
            x=a[x][i];
            y=a[y][i];
        }
    return a[x][0];
}

int get_small(int x,int f){
    int res=0;
    for (int i=17;i>=0;i--)
    if (dep[dp[x][i]]>dep[f])
        res+=(1<<i),x=dp[x][i];
    if (dep[x]>dep[f]) res++;
    return res;
}

void jump(int &x,int step){
    for (int i=17;i>=0;i--)
    if (step&(1<<i))
        x=dp[x][i];
}

int query_small(int x,int y,int c){
    int f=lca(x,y);
    if (x==f) return get_small(y,f);
    if (y==f) return get_small(x,f);
    int ans1=get_small(x,f);
    int ans2=get_small(y,f);
    jump(x,ans1-1);
    jump(y,ans2-1);
    if (dis[x]+dis[y]-2*dis[f]<=c)
        return ans1+ans2-1;
    return ans1+ans2;
}

void getpath(int x,int f,int *q,int &top,int c){
    q[top]=x;
    while (dep[x]>dep[f]){
        int t=c;
        for (int i=17;i>=0;i--)
        if (dis[x]-dis[a[x][i]]<=t){
            t-=dis[x]-dis[a[x][i]];
            x=a[x][i];
        }q[++top]=x;
    }
}

int query_big(int x,int y,int c){
    int f=lca(x,y);
    top1=top2=0;
    getpath(x,f,q1,top1,c);
    getpath(y,f,q2,top2,c);
    if (x==f) return top2;
    if (y==f) return top1;
    x=q1[top1-1];
    y=q2[top2-1];
    if (dis[x]+dis[y]-2*dis[f]<=c)
        return top1+top2-1;
    return top1+top2;
}

int main(){
    freopen("a.in","r",stdin);
    freopen("my.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<n;i++){
        scanf("%d%d%d",&x,&y,&z);
        adp(x,y,z);adp(y,x,z);
    }
    dfs(1);
    for (int j=1;j<=17;j++)
        for (int i=1;i<=n;i++)
            a[i][j]=a[a[i][j-1]][j-1];
    scanf("%d",&m);
    for (int i=1;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        adpque(x,y,z,i);
    }
    for (int i=2;i<=kuai;i++)
    if (ch[i]){
        dfsdp(1,i);
        for (int j=1;j<=17;j++)
            for (int k=1;k<=n;k++)
                dp[k][j]=dp[dp[k][j-1]][j-1];
        for (int j=ch[i];j;j=que[j].next)
            ans[que[j].id]=query_small(que[j].x,que[j].y,i);
    }
    for (int i=kuai+1;i<=2*n;i++)
    if (ch[i]){
        for (int j=ch[i];j;j=que[j].next)
            ans[que[j].id]=query_big(que[j].x,que[j].y,i);
    }
    for (int i=1;i<=cnt;i++)
        printf("%d\n",ans[i]);
}

CF 472G

题意:

给出两个01序列A和B,Q次询问,每次询问A的一个子串和B的一个子串异或之后的结果包含多少个1(两个子串长度相同)

数据范围: |A|,|B|2105,Q4105

分析:

01串自然想到二进制,一个长度为32的01串能转化成一个int,这样便能加快比较了。分块乱搞是显然的(表示没想到)。
将A分块(一块K=1024),预处理 c[pos][bj] 表示A第pos块从左端点开始与 Bj 匹配32位。(具体实现看代码)
预处理时间复杂度 O(n2K+232n)

每次查询时先将A块的左右两端多出来的部分暴力搞掉,块中的就32位32位比较就好了。
查询时间复杂度 O(2K+KK/32)

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=200100;
const int K=1024,sz=32;
const int M=(1<<16)-1;
typedef unsigned int uint;
uint aw[N],bw[N];
int bit[M+1],c[N/K+1][N];
int p1,p2,len,m;
char a[N],b[N];

int main(){
    scanf("%s",a);
    scanf("%s",b);
    int lena=strlen(a);
    int lenb=strlen(b);
    for (int i=1;i<1<<16;i++)
        bit[i]=bit[i/2]+(i&1);//i二进制中1的个数
    for (int i=0;i<lena;i++){
        uint t=1;
        for (int j=0;j<sz && i+j<lena;j++,t*=2)
            if (a[i+j]=='1') aw[i]|=t;
    }
    for (int i=0;i<lenb;i++){
        uint t=1;
        for (int j=0;j<sz && i+j<lenb;j++,t*=2)
            if (b[i+j]=='1') bw[i]|=t;
    }
    for (int i=0;i+K<=lena;i+=K){
        int id=i/K;
        for (int j=0;j+K<=lenb;j++){
            p1=i,p2=j;
            for (int t=0;t<sz;t++){
                uint st=aw[p1]^bw[p2];
                c[id][j]+=bit[st&M]+bit[(st>>16)&M];
                p1+=sz;p2+=sz;
            }
        }
    }
    scanf("%d",&m);
    for (int i=1;i<=m;i++){
        scanf("%d%d%d",&p1,&p2,&len);
        int ans=0;
        int pos=p1/K*K,r=p1+len-1;
        while (pos<p1) pos+=K;
        for (int j=p1;j<pos && j<=r;j++,p2++)
            ans+=(a[j]!=b[p2]);

        while (pos+K-1<=r)
            ans+=c[pos/K][p2],pos+=K,p2+=K;

        for (int j=pos;j<=r;j++,p2++)
            ans+=(a[j]!=b[p2]);
        printf("%d\n",ans);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值