\(\#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;
}