$[JZOJ$ $Contest$ $2471]$ $TG_{(18.8.14)}^B$

\(\#A\) \([JZOJ5185]Sequence\)


给出一个长为\(N\)的数列\(A_1,A_2,...,A_N\),求出长度\(\ge M\)的两段\((\)可相交或包含\()\),使得第一段内的数二进制或值最大,第二段内的数二进制与值最大,输出这两个最大值。

  • \(N\in [1,10^6]\)\(M\in [1,N]\)\(A_i\in [0,2^{31}]\)
  • 首先对于或值最大问题,显然多选不会更劣,所以答案即为所有数取二进制或。
  • 对于第二问,考试时想到的方法是线段树,维护区间与值,对\(N-M+1\)个位置查询向后长为\(M\)的区间与值取\(Max\)即为答案,总复杂度\(\Theta((N-M)logN)\)可过。
#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define N 1000010
#define inf ((1<<32)-1)
#define Rg register
#define gc getchar
#define mid ((l+r)>>1)
using namespace std;
typedef long long ll;

ll n,m,a[N],ans1,ans2;

struct segment{
    
    ll root,ptr;
    segment(){root=ptr=0;}
    
    struct node{
        ll sum,ls,rs;
        node(){sum=ls=rs=0;}
    }c[N<<1];
    
    inline ll newnode(){return ++ptr;}
    
    void build(ll &rt,ll l,ll r){
        rt=newnode();
        if(l==r){
            c[rt].sum=a[l];
            return; 
        }
        build(c[rt].ls,l,mid);
        build(c[rt].rs,mid+1,r);
        c[rt].sum=(c[c[rt].ls].sum&c[c[rt].rs].sum);
    }
    
    ll query(ll rt,ll l,ll r,ll L,ll R){
        ll res=inf;
        if(l>=L&&r<=R){return c[rt].sum;}
        if(L<=mid) res&=query(c[rt].ls,l,mid,L,R);
        if(R>mid) res&=query(c[rt].rs,mid+1,r,L,R);
        return res;
    }
}tree;

inline ll rd(){
    ll x=0; bool f=0; char c=gc();
    while(!isdigit(c)){
        if(c=='-')f=1;c=gc();
    }
    while(isdigit(c)){
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return f?-x:x;
}

int main(){
    n=rd(); m=rd();
    for(Rg ll i=1;i<=n;++i) ans1|=(a[i]=rd());
    printf("%d ",ans1);
    tree.build(tree.root,1,n);
    for(Rg ll i=1;i<=n-m+1;++i)
        ans2=max(ans2,tree.query(tree.root,1,n,i,i+m-1));
    printf("%d\n",ans2);
    return 0;
}
  • 其实第二问还有更快的\(\Theta(N)\)做法,对二进制每一位开一个桶,从左往右扫描,每遇到一个数,将其二进制\(1\)对应的位置桶\(+1\),当长度超过\(M\)时,去掉当前计数内的第一个数对桶的影响,更新答案。
  • 与运算的性质是,只有求与的所有数这一位上都为\(1\)答案才会为\(1\),所以只有当一个桶内个数为\(M\)时,当前区间的答案对应位才为\(1\),确定最高位后扫描所有桶即可。
#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 1000010
#define R register
#define gc getchar
using namespace std;
typedef long long ll;

ll n,m,ans1,ans2,a[N];

struct num2{
    ll num[33];
    num2(){memset(num,0,sizeof(num));}
    inline void print(){
        for(R int i=32;~i;--i) printf("%lld ",num[i]);
        puts("");
    }
}now;

inline ll rd(){
    ll x=0; bool f=0; char c=gc();
    while(!isdigit(c)){if(c=='-')f=1;c=gc();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
    return f?-x:x; 
}

int main(){
    n=rd(); m=rd();
    for(R ll i=1,x;i<=n;++i) ans1|=(a[i]=rd());
    for(R ll i=1,temp,t;i<=m;++i){
        temp=a[i]; t=0;
        while(temp){
            if(temp&1) ++now.num[t];
            ++t; temp>>=1;
        }
    }
    ll temp=0,t=0;
    for(R ll j=32;~j;--j) if(now.num[j]>=m){t=j;break;}
    for(R ll j=t;~j;--j) temp=((temp<<1)|(now.num[j]>=m));
    ans2=max(ans2,temp);
    for(R ll i=m+1,temp,t;i<=n;++i){
        temp=a[i]; t=0;
        while(temp){
            if(temp&1) ++now.num[t];
            ++t; temp>>=1;
        }
        temp=a[i-m]; t=0;
        while(temp){
            if(temp&1) --now.num[t];
            ++t; temp>>=1;
        }
        temp=0; t=0;
        for(R ll j=32;~j;--j) if(now.num[j]==m){t=j;break;}
        for(R ll j=t;~j;--j) temp=((temp<<1)|(now.num[j]==m));
        ans2=max(ans2,temp);
    }
    printf("%lld %lld",ans1,ans2);
    return 0;
} 

\(\\\)

\(\#B\) \([JZOJ5184]Gift\)


给出\(N\)个物品的价值\(V_1,V_2,...,V_N\),求有\(M\)元钱时,有多少种不同购买的方案使得,钱数足够买这些物品,并且剩余的钱不够购买剩下未购买的任何一件物品,方案数对\(10^7+7\)取模。

  • \(N\in [1,10^3]\)\(M\in [1,10^3]\)\(V_i\in [0,M]\)
  • 如果所有物品价值之和\(\le M\),则去掉任何一个物品都不合法,所以特判只有一个方案。
  • 考虑先求出\(f[i][j]\)表示前\(i\)个物品,正好剩余\(j\)元的方案数,转移分为选不选当前物品讨论,为\(f[i][j]=f[i-1][j]+f[j-1][j+v[i]]\),可以省略掉第一维,转移为\(f[j]+=f[j+a[i]]\),边界显然为\(f[M]=1\),注意对于每一个物品,\(j\)应从小到大枚举。
  • 考虑做到每一个物品时,如何确定合法答案区间,为了避免价值大小不均难以讨论,先将所有价值从小到大排序,并对价值求前缀和。这样倒序枚举物品时,可以保证前面的物品价值都比当前小,这样即可定义合法为不买的物品中价值最小的是当前物品。
  • 对于一个合法的\(j\),其上限小于\(Sum_i\)代表当前剩余的钱不够购买当前物品,下限大于等于\(Sum_{i-1}\)代表前面的物品都必买\((\)因为价值都比当前小\()\),枚举到每一个物品时对答案累加合法区间内的方案数。
  • 注意,\(DP\)之后是已选当前物品的答案,所以统计不选择当前物品的答案应该在\(DP\)之前;对于第一次选择,显然剩余钱数只能是\(M\),并且前面已经特判掉所有物品价值和小于总钱数的情况,所以只要前\(N-1\)个物品价值和\(\le M\)即可正常累计,合法性可以在循环中体现。
#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define R register
#define gc getchar
#define N 1010
#define mod 10000007
using namespace std;

inline int rd(){
    int x=0; bool f=0; char c=gc();
    while(!isdigit(c)){
        if(c=='-')f=1;c=gc();
    }
    while(isdigit(c)){
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return f?-x:x;
}

int n,m,res,v[N],sum[N],f[N];

inline bool cmp(int x,int y){return x<y;}

int main(){
    n=rd(); m=rd();
    for(R int i=1;i<=n;++i) v[i]=rd();
    sort(v+1,v+1+n,cmp);
    for(R int i=1;i<=n;++i) sum[i]=sum[i-1]+v[i];
    if(sum[n]<=m){puts("1");return 0;}
    f[m]=1;
    for(R int i=n,lim;i;--i){
        lim=min(m+1,sum[i]);
        for(R int j=sum[i-1];j<lim;++j) (res+=f[j])%=mod;   
        for(R int j=0;j<=m-v[i];++j) (f[j]+=f[j+v[i]])%=mod;
    }
    printf("%d\n",res);
    return 0;
}

\(\\\)

\(\#C\) \([JZOJ4732]Ihp\)


定义一个函数\(F\),其定义域和值域均为正整数,计算公式为\(\begin{align}F(N)=\sum_{d=1,d|N}^NF(d)\end{align}\)求给出的\(N\)个数\(A_1,A_2,...,A_N\)的函数值之和。

  • \(N\in [1,10^5]\)\(A_i\in [1,10^7]\)
  • 由题目名提示知学习欧拉函数的时候知道,欧拉函数有一个特殊的性质为,一个数等于其所有因子欧拉函数值之和,由于题中函数无其他限制,所以题中函数可为\(\varphi\)函数。

  • 题目有三个特殊点不符合数据范围,给出了具体数据:

    • \(Part\ 1:N=3\times 10^7,\forall A_i=7\) 全都读进来都会\(T\)飞,直接特判输出\(18\times10^7\)

    • \(Part 2:N=5,\{A\}=\{162614600673829,1125897758834689,18004502351257601,2001600073928249,222915410844073\}\)

    • \(Part 3:N=3,\{A\}=\{18014398241046527,18014398777917439,489133282872437279\}\)

  • 对于后两个特殊点,我们回到欧拉函数的定义公式,对\(N\)分解质因数:\(\\\)\(\begin{align}N=p_1^{q_1}\times p_2^{q_2}\times ......\times p_m^{q_m}\end{align}\)\(\\\)\(\begin{align}\varphi(N)=N\times(1-\frac{1}{p_1})\times(1-\frac{1}{p_2})\times ......\times(1-\frac{1}{p_m})\end{align}\)\(\Theta(\sqrt N)\)求出\(N\)的质因数分解,直接计算即可。

    scanf("%lld",&n); 
    ans=n; temp=sqrt(n);
    for(R int i=2;i<=temp;++i)
      if(n%i==0){
      (ans/=i)*=i-1;
      while(n%i==0) n/=i;
      }
    if(n>1) (ans/=n)*=n-1;
    printf("%lld\n",ans);
  • 剩余数据即为求\(N\)个数欧拉函数值之和,线性筛预处理出\(10^7\)范围内的函数值,\(\Theta(N)\)求和即可。

#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 10000010
#define R register 
#define gc getchar
using namespace std;
typedef long long ll;

int n,m,prm[N],phi[N]={0,1};

inline int rd(){
    int x=0; bool f=0; char c=gc();
    while(!isdigit(c)){if(c=='-')f=1;c=gc();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
    return f?-x:x; 
}

inline void work(){
    for(R int i=2;i<N;++i){
        if(!phi[i]) prm[++prm[0]]=i,phi[i]=i-1;
        for(R int j=1,k;j<=prm[0]&&((k=i*prm[j])<N);++j){
            if(i%prm[j]==0){phi[k]=phi[i]*prm[j];break;}
            phi[k]=phi[i]*phi[prm[j]];
        }
    }
}

int main(){
    n=rd();
    if(n==30000000){printf("%lld\n",n*6);return 0;}
    if(n==5){printf("%lld\n",21517525747423580);return 0;}
    if(n==3){printf("%lld\n",525162079891401242);return 0;}
    work();
    ll ans=0;
    for(R int i=1;i<=n;++i) ans+=(ll)phi[rd()];
    printf("%lld\n",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/SGCollin/p/9478054.html