这个写的很好,有适用范围 与 算法思想
\text{ \ \ \ \ }
时间复杂度证明:
左端点都在同一个 n \sqrt{n} n内
左端点最多移 O ( m n ) O(m\sqrt{n}) O(mn), 右端点升序排序,对于每一块最多移到底,即 O ( n n ) O(n\sqrt{n}) O(nn)
总复杂度 O ( ( m + n ) n + m l o g 2 m ) O((m+n)\sqrt{n} + mlog_{2}{m}) O((m+n)n+mlog2m)
对于每一道题,随要求改变统计的数据即可
\text{ \ \ \ \ }
\text{ \ \ \ \ }
1.分块按照 n \sqrt{n} n
2.不必按块枚举,询问区间属于哪一块的信息记录在结构体内排序
3.排序先按块升序,再按右端点升序
3. l = 1 , r = 0 l=1,r=0 l=1,r=0 防止(1,1)无法记录,所以r=0
\text{ \ \ \ \ }
\text{ \ \ \ \ }
\text{ \ \ \ \ }
统计 t [ a [ i ] ] 2 \text{\ \ }t[a[i]]^{2}\text{\ \ } t[a[i]]2 ,由 t [ a [ i ] ] 2 \text{\ \ }t[a[i]]^{2}\text{\ \ } t[a[i]]2 到 ( t [ a [ i ] ] + 1 ) 2 (t[a[i]]+1)^{2} (t[a[i]]+1)2,需要加减 2 × t [ a [ i ] ] + 1 2\times t[a[i]]+1 2×t[a[i]]+1
#include
#include
#include
#define LL long long
using namespace std;
int a[51000],t[51000];//t[i]数i出现次数
struct node{int x,y,i,d;}st[51000]; //区间左右范围 ;属于哪个块 最大50000^2 25亿->long long
bool cmp(node a,node b)
{
if(a.d==b.d) return a.y<b.y;
return a.d<b.d;
}LL ans=0,an[51000];
int main()
{
int n,m,k;
scanf("%d %d %d",&n,&m,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int q=sqrt(n);
for(int i=1;i<=m;i++){
scanf("%d %d",&st[i].x,&st[i].y);
st[i].i=i, st[i].d= (st[i].x-1)/q + 1; //分块
}
sort(st+1,st+1+m,cmp);
int l=1,r=0; //防止1 1无法记录,所以r=0
for(int i=1;i<=m;i++)
{//直接左右端点移动即可,已经按排最坏n*sqrt(n)顺序
//左端点最多 m* sqrt(n) ,右端点每块最多移到低(即n) sqrt(n)*n
while(l>st[i].x) l--, t[a[l]]++, ans+=2*t[a[l]]-1; //(x)^2-(x-1)^2 =2*x-1
while(r<st[i].y) r++, t[a[r]]++, ans+=2*t[a[r]]-1;
while(l<st[i].x) t[a[l]]--, ans-=2*t[a[l]]+1, l++;
while(r>st[i].y) t[a[r]]--, ans-=2*t[a[r]]+1, r--;
an[st[i].i]=ans;
}for(int i=1;i<=m;i++) printf("%lld\n",an[i]);
return 0;
}
统计 C t [ i ] 2 = t [ i ] × t [ i − 1 ] / 2 C^{2}_{t[i]}=t[i]\times t[i-1] /2 Ct[i]2=t[i]×t[i−1]/2
每多一只同色袜子 t ∗ ( t − 1 ) / 2 − ( t − 1 ) ∗ ( t − 2 ) / 2 = 1 / 2 ∗ ( t − 1 ) ∗ ( 2 ) = t − 1 t*(t-1)/2 - (t-1)*(t-2)/2 =1/2 * (t-1) *(2) =t-1 t∗(t−1)/2−(t−1)∗(t−2)/2=1/2∗(t−1)∗(2)=t−1
#include
#include
#include
#define LL long long
using namespace std;
int a[51000],t[51000];//t[i]数i出现次数
struct node{int x,y,i,d;}st[51000];
bool cmp(node a,node b)
{
if(a.d==b.d) return a.y<b.y;
return a.d<b.d;
}LL ans=0,anx[51000],any[51000];
LL gcd(LL a,LL b){
if(b==0) return a;
return gcd(b,a%b);
}
int main()
{
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int q=sqrt(n);
for(int i=1;i<=m;i++){
scanf("%d %d",&st[i].x,&st[i].y) ;
st[i].i=i,st[i].d=(st[i].x-1)/q + 1; //分块
}
sort(st+1,st+1+m,cmp);
int l=1,r=0; //防止1 1无法记录,所以r=0
for(int i=1;i<=m;i++)
{
LL L=st[i].y-st[i].x+1;//C(L,2); t*(t-1)/2 - (t-1)*(t-2)/2 = 1/2 * (t-1) *(2) =t-1 多一只袜子
if(st[i].x==st[i].y) {anx[st[i].i]=0;any[st[i].i]=1; continue;}
while(l>st[i].x) l--, t[a[l]]++, ans+=t[a[l]]-1;
while(r<st[i].y) r++, t[a[r]]++, ans+=t[a[r]]-1;
while(l<st[i].x) ans-=t[a[l]]-1, t[a[l]]-- ,l++;
while(r>st[i].y) ans-=t[a[r]]-1, t[a[r]]-- ,r--;
anx[st[i].i]=ans;any[st[i].i]=L*(L-1)/2;
}
for(int i=1;i<=m;i++){
LL g=gcd(anx[i],any[i]);
printf("%lld/%lld\n",anx[i]/g,any[i]/g);
}
return 0;
}
tot记录有几种数超过1
某一种数重复了 t o t + = ( t [ a [ l ] ] = = 2 ) tot+=(t[a[l]]==2) tot+=(t[a[l]]==2)
某一种数不再重复了 t o t − = ( t [ a [ l ] ] = = 1 ) tot-=(t[a[l]]==1) tot−=(t[a[l]]==1)
#include
#include
#include
#define LL long long
using namespace std;
int a[100100],t[100100];//t[i]数i出现次数
struct node{int x,y,i,d;}st[100100];
bool cmp(node a,node b)
{
if(a.d==b.d) return a.y<b.y;
return a.d<b.d;
}bool an[100100];
int main()
{
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int q=sqrt(n);
for(int i=1;i<=m;i++){
scanf("%d %d",&st[i].x,&st[i].y) ;
st[i].i=i,st[i].d=(st[i].x-1)/q + 1; //分块
}
sort(st+1,st+1+m,cmp);
int l=1,r=0,tot=0; //防止1 1无法记录,所以r=0
for(int i=1;i<=m;i++)
{//tot记录有几种数超过1
while(l>st[i].x) l--, t[a[l]]++, tot+=(t[a[l]]==2)?1:0;
while(r<st[i].y) r++, t[a[r]]++, tot+=(t[a[r]]==2)?1:0;
while(l<st[i].x) t[a[l]]-- ,tot-=(t[a[l]]==1)?1:0 ,l++;
while(r>st[i].y) t[a[r]]-- ,tot-=(t[a[r]]==1)?1:0 ,r--;
if(tot==0) an[st[i].i]=true;
else an[st[i].i]=false;
}
for(int i=1;i<=m;i++) if(an[i]) printf("Yes\n");else printf("No\n");
return 0;
}
\text{ \ \ \ \ }
\text{ \ \ \ \ }
\text{ \ \ \ \ }
\text{ \ \ \ \ }
1.分块按照 n 2 3 n^{ \frac{2}{3}} n32
int q=pow(n,2.0/3.0);
2.排序按照左端点所在块升序、右端点所在块升序,时间(第几次修改之后)升序
return block[l]==block[b.l]?(block[r]==block[b.r]?t<b.t:r<b.r):l<b.l;
\text{ \ \ \ \ }
\text{ \ \ \ \ }
\text{ \ \ \ \ }
修改也当做一维,当前是修改了n次以后的,要求修改k次以后的,那就n-k次朴素改点再统计
这道题卡常极其严重。。。
#include
#include
#include
#define LL long long
using namespace std;
const int N=140010,M=1000100;
int q;
int a[N],block[N],t[M];//t[i]数i出现次数
struct node{
int l,r,i,t;
bool operator < (node &b){
return block[l]==block[b.l]?(block[r]==block[b.r]?t<b.t:r<b.r):l<b.l;
}
}st[N];
/*//不想打在结构体里也可以
bool cmp(node a,node b){
return block[a.l]==block[b.l]?(block[a.r]==block[b.r]?a.t
struct nod{int p,col;}ch[N];
int tot=0;
inline void add(int x){
if(t[x]++==0) ++tot;
}
inline void del(int x){
if(--t[x]==0) --tot;
}
inline int read(){
int x=0;char c=getchar();
while(c<48) c=getchar();
while(c>47) x=x*10+c-'0',c=getchar();
return x;
}
inline void modify(int x,int ti)
{//这里仅执行退一步
if(ch[ti].p>=st[x].l && ch[ti].p<=st[x].r){
del(a[ch[ti].p]);
add(ch[ti].col);
}
swap(a[ch[ti].p],ch[ti].col); //下次执行时必定是回退这次操作,直接互换就可以了
}
int an[N];
int main()
{
int n=read(),m=read();q=pow(n,2.0/3.0);
for(register int i=1;i<=n;i++)
a[i]=read(),block[i]=(i-1)/q+1;
int qc=0,cc=0;char p[5];
for(register int i=1;i<=m;i++)
{
scanf("%s",p);
if(p[0]=='Q'){
++qc;
st[qc].l=read(),st[qc].r=read();
st[qc].i=qc;st[qc].t=cc;
} //记录这次询问是在第几次修改之后(时间)
else
cc++,ch[cc].p=read(),ch[cc].col=read();
}//如果当前修改数比询问的修改数少就把没修改的进行修改,反之回退。
sort(st+1,st+1+qc);
int l=1,r=0,now=0; //防止1 1无法记录,所以r=0
for(register int i=1;i<=qc;i++)
{//tot记录有几种数超过1
//printf("id=%d ",st[i].i);
while(l>st[i].l) add(a[--l]);
while(r<st[i].r) add(a[++r]);
while(l<st[i].l) del(a[l++]);
while(r>st[i].r) del(a[r--]);
while(now<st[i].t) modify(i,++now);
while(now>st[i].t) modify(i,now--);
an[st[i].i]=tot;
}
for(register int i=1;i<=qc;i++) printf("%d\n",an[i]);
return 0;
}
\text{ \ \ \ \ }
\text{ \ \ \ \ }
\text{ \ \ \ }
给你一棵树,每个点有个颜色
每次询问你一条路径求
v a l 表 示 该 颜 色 的 价 值 , c n t 表 示 其 出 现 的 次 数 , w o e t h i val表示该颜色的价值,cnt表示其出现的次数,woeth_i val表示该颜色的价值,cnt表示其出现的次数,woethi表示第 i i i 次出现的价值
带修改
\text{ \ \ \ }
先求出dfs序把树变成序列
考虑向右扩展一个点,这个贡献我们是可以O(1)算出来的
假设扩展出的点是的颜色是c,那么 Δ = v a l c × w o r t h c n t c + 1 \Delta=val_c\times worth_{cnt_{c+1}} Δ=valc×worthcntc+1
具有莫队性质
\text{ \ \ \ }
但是直接用dfs序去扩展的话显然会出问题
因为他会先去扫完起点的子树,产生多余的贡献
这样的话扫的过程中起点的子树里的点肯定会被扫两次(一进一出)
为了连续做两次之后贡献为0,我们可以想到异或
即开一个 v i s vis vis数组,每次访问就一个点u,就 v i s u vis_u visu ^ = 1 =1 =1
考虑样例的括号序1 3 4 4 2 2 3 1
询问 4 → 2 4 \to 2 4→2那么我们得到的区间是[3,5]
发现 3 没有被算进来 ,这个要特判
当然如果起点就是lca就不需要管了
同样是上面那个例子
我们可以看到 4 的贡献被算两次抵消掉了
所以这种情况也要特判
#include
#include
#include
#define LL long long
using namespace std;
const int N=100100;
int read(){
int x=0;char c=getchar();
while(c<48) c=getchar();
while(c>47) x=x*10+c-'0',c=getchar();
return x;
}
struct bian{int y,gg;}b[N<<1];
int first[N],len=0;
void ins(int x,int y){
b[++len].y=y;
b[len].gg=first[x];
first[x]=len;
}
//==============================================================
int c[N],w[N],now[N],v[N],tot[N];
int fa[N][18],dep[N];
int f[N],g[N],id[N<<1],cnt=0;
int block[N<<1];
void dfs(int x)
{
f[x]=++cnt; id[cnt]=x;//括号序
dep[x]=dep[fa[x][0]]+1;
for(int i=1;i<=17;i++) fa[x][i]=fa[fa[x][i-1]][i-1];//lca
for(int i=first[x];i>0;i=b[i].gg){
int y=b[i].y;
if(y!=fa[x][0]){
fa[y][0]=x;
dfs(y);
}
}
g[x]=++cnt;id[cnt]=x;//括号序
}
int lca(int x,int y)
{
if(dep[x]>dep[y]) swap(x,y);
for(int i=17;i>=0;i--)
if(dep[fa[y][i]]>=dep[x]) y=fa[y][i];
if(x==y) return x;
for(int i=17;i>=0;i--)
if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}//==============================================================
struct node{
int l,r,i,t;
bool operator < (node &b){
return block[l]==block[b.l]?(block[r]==block[b.r]?t<b.t:r<b.r):l<b.l;
}
}st[N];
struct nod{int p,col,last;}ch[N];
LL sum=0,ans[N];
bool vis[N];
//===================================================================
void modify(int x)
{
if(vis[x]) sum-=1ll * v[c[x]] * w[ tot[c[x]]-- ];
else sum+=1ll * v[c[x]] * w[ ++tot[c[x]] ];
vis[x]^=1;
}
void change(int x,int y){
if(vis[x]) {modify(x);c[x]=y;modify(x);}
else c[x]=y;
}
int main()
{
int n=read(),m=read(),q=read();
for(int i=1;i<=m;++i) v[i]=read();
for(int i=1;i<=n;++i) w[i]=read();
for(int i=1;i<n;++i){
int x=read(),y=read();
ins(x,y);ins(y,x);
}
for(int i=1;i<=n;++i) now[i]=c[i]=read();
dfs(1);
int B=pow(n,2.0/3.0);
for(int i=1;i<=cnt;i++) block[i]=(i-1)/B+1;
int qc=0,cc=0;
for(int i=1;i<=q;i++)
{
int p=read();
if(p){
int l=read(),r=read();
if(f[l]>f[r]) swap(l,r);//括号序从左到右
st[++qc].r=f[r]; //取靠左的
st[qc].t=cc;st[qc].i=qc;
st[qc].l=(lca(l,r)==l)?f[l]:g[l];
//1 3 4 4 2 2 3 1 3只有一个f[3]=2 4要取靠右的g[4]=4,还要记得特判补上3
}
else{
int p=read(),col=read();
ch[++cc].p=p;ch[cc].last=now[p];
now[p]=ch[cc].col=col;
}
}
sort(st+1,st+1+qc);
int l=1,r=0,t=1;
for(int i=1;i<=qc;i++)
{
while(t<=st[i].t) change(ch[t].p,ch[t].col),t++;
while(t>st[i].t) change(ch[t].p,ch[t].last),t--;
while(l>st[i].l) modify(id[--l]);
while(r<st[i].r) modify(id[++r]);
while(l<st[i].l) modify(id[l++]);
while(r>st[i].r) modify(id[r--]);
int x=id[l] , y=id[r], tmp=lca(x,y);
if(x!=tmp && y!=tmp) { modify(tmp); ans[st[i].i]=sum; modify(tmp);}//特判补lca
else ans[st[i].i]=sum;
}
for(int i=1;i<=qc;++i)
printf("%lld\n",ans[i]);
return 0;
}
\text{ \ \ \ }
\text{ \ \ \ }
前置 寻找分块方式
简洁题意:如何分块,使得满足每块大小在 [B,3B],块内每个点到核心点(省会)路径上的所有点都在块内呢?
1.我们 d f s dfs dfs 整棵树,处理每个节点时,将其一部分子节点分块,将未被分块的子节点返回到上一层。
2.先记录初始栈 s t a \text{ }sta\text{ } sta 栈顶高度 t \text{ }t t , 枚举 x \text{ x } x 的每个子节点 y \text{ y} y,递归处理子树后,将每个子节点返回的未被分块的节点 累叠在栈 sta \text{ sta } sta 上 , 一旦 新累加的点数 top-t>=B \text{ top-t>=B } top-t>=B 就把 栈 作为 top-t到top \text{ top-t到top } top-t到top 的点作为一个新的块并将x作为省会,然后清空。
你会发现:对于最先遍历的一条链, t 都为0 , 而 t 的改变只能是横向的 ,这将保证两两子树之间互不影响,且都能对根做贡献
3.处理完所有子树后,将x也加入到栈中,栈的大小最大为B-1 在加上x节点 所以: 栈大小最大为B
4.对于上一层的子树,最多对 栈 增加 B B B个节点 ( t o p − t > = B 就 弹 出 了 ) (top-t>=B就弹出了) (top−t>=B就弹出了), S 最 多 为 2 B S最多为2B S最多为2B
5.在dfs结束后,S大小最大为B, 随便并入之前的一个块(一般最后一个以根为省会的块),最大 2 B + B 2B+B 2B+B
——改自洛谷Siyuan 与 ouuan
#include
#include
using namespace std;
struct bian{int y,gg;}b[2010];
int first[1010],len=0;
void ins(int x,int y){
b[++len].y=y;
b[len].gg=first[x];
first[x]=len;
}
int read(){
int x=0;char c=getchar();
while(c<48) c=getchar();
while(c>47) x=x*10+c-'0',c=getchar();
return x;
}
int sta[1010],top=0,n,B,tot=0;
int fa[1010],bel[1010],rt[1010];
void dfs(int x){
int t=top;
for(int i=first[x];i>0;i=b[i].gg){
int y=b[i].y;
if(y!=fa[x])
{
fa[y]=x;dfs(y);
if(top-t>=B)
{
++tot;rt[tot]=x;
while(top>t) bel[sta[top--]]=tot;
}
}
}sta[++top]=x;
}
int main()
{
n=read(),B=read();int x,y;
for(int i=1;i<n;i++) x=read(),y=read(),ins(x,y),ins(y,x);
dfs(1);
while(top) bel[sta[top--]]=tot;
printf("%d\n",tot);
for(int i=1;i<=n;i++) printf("%d ",bel[i]);
printf("\n");
for(int i=1;i<=tot;i++) printf("%d ",rt[i]);
return 0;
}
\text{ \ }
\text{ \ }
我能不打了吗,【哭】。。。
粘个代码。。。
UOJ58 【WC2013】糖果公园