题
cf375D—莫队裸题
bzoj3509 分块+FFT
对于30分的部分分:
考虑枚举j。我们可以用pre[t]表示在前j个数中,t这个数出现了多少次,nxt[t]表示后n-j个数中,t这个数出现了多少次。那么答案就是
∑u+v=2∗Ajpre[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个函数值的和。
数据范围:
n≤100000
分析:
分块大法好~
首先将
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个节点
(n≤100000)
边权为1或2的树,有m个询问
(m≤100000)
。每次询问一个人从x点走到y点,每一步只能走边权总和小于等于k,问最少走多少步(每一步结束后必须在节点上)有点绕
分析
对k分情况进行讨论
k≥n√
最多走sqrt(n)步,直接在
fa[][]
上暴力走就好了
k≤n√
对于每个k一起处理,每次倍增
dp[i][j]
表示从节点i走
2j
步能走到哪(边权总和
≤k
)
时间复杂度
O(nn√logn)
#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|≤2∗105,Q≤4∗105
分析:
01串自然想到二进制,一个长度为32的01串能转化成一个int,这样便能加快比较了。分块乱搞是显然的(表示没想到)。
将A分块(一块K=1024),预处理
c[pos][bj]
表示A第pos块从左端点开始与
Bj
匹配32位。(具体实现看代码)
预处理时间复杂度
O(n2K+2∗32∗n)
每次查询时先将A块的左右两端多出来的部分暴力搞掉,块中的就32位32位比较就好了。
查询时间复杂度
O(2∗K+K∗K/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);
}
}