hdu4918 (树分治的维护)

题意:

一棵树,每个点有点权w,q次操作

1.将点x的点权修改为y 

2.问离x距离不超过d的点权和

 

思路:

将树分治中的计算部分需要的树状数组提出来,维护这个树状数组,每次查询修改均为O(log2n)

 

#include<bits/stdc++.h>
using namespace std;
const int N=100005;
vector<int>link[N];
struct MSG
{
    int id1,id2;
    int deep;
}msg[N][17];
int w[N];
int c[N*17];
int idn,L[N*2],R[N*2];
int maxfloor[N];
void add(int l,int r,int x,int y)
{
    for (int i=x;i<r-l;i+=(i&-i)) c[l+i]+=y;
}
int get(int l,int r,int x)
{
    if (x<1) return 0;
    if (x>=r-l) x=r-l-1;
    int ret=0;
    for (int i=x;i;i-=(i&-i)) ret+=c[l+i];
    return ret;
}
bool vis[N];
int cent,mxsz;
int size[N];
void findcent(int u,int fa,int n)
{
	size[u]=1;
	int mx=0;
	for (int v:link[u])
	{
		if (vis[v]||v==fa) continue;
		findcent(v,u,n);
		size[u]+=size[v];
		mx=max(mx,size[v]);
	}
	mx=max(mx,n-size[u]);
	if (mx<mxsz) cent=u,mxsz=mx;
}
int getmaxdeep(int u,int fa)
{
    int ret=1;
    for (int v:link[u])
        if (v!=fa&&!vis[v]) ret=max(ret,1+getmaxdeep(v,u));
    return ret;
}
void dfs(int u,int fa,int deep,int idn,int floor,int tp)
{
    if (tp==0) msg[u][floor].id1=idn;
    else msg[u][floor].id2=idn;
    msg[u][floor].deep=deep;
    add(L[idn],R[idn],deep,w[u]);
    for (int v:link[u])
        if (!vis[v]&&v!=fa) dfs(v,u,deep+1,idn,floor,tp);
}
void build(int u,int n,int floor)
{
    cent=u; mxsz=n;
    findcent(u,-1,n);
    u=cent; maxfloor[u]=floor;
    idn++; L[idn]=R[idn-1];
    R[idn]=L[idn]+getmaxdeep(u,-1)+1;

    dfs(u,-1,1,idn,floor,0);

    msg[u][floor].id2=-1;
    for (int v:link[u])
        if (!vis[v])
        {
            idn++; L[idn]=R[idn-1];
            R[idn]=L[idn]+getmaxdeep(v,u)+2;
            dfs(v,u,2,idn,floor,1);
        }

    vis[u]=1;
    for (int v:link[u])
        if (!vis[v])
            if (size[v]<size[u]) build(v,size[v],floor+1);
            else build(v,n-size[u],floor+1);

}
int main()
{
    int n,q;
    while(~scanf("%d%d",&n,&q))
    {
        for (int i=1;i<=n;i++) scanf("%d",&w[i]);
        for (int i=1;i<n;i++)
        {
            int x,y; scanf("%d%d",&x,&y);
            link[x].push_back(y);
            link[y].push_back(x);
        }
        build(1,n,0);
        while(q--)
        {
            char s[2]; int x,y;
            scanf("%s%d%d",s,&x,&y);
            if (s[0]=='?')
            {
                int ans=0;
                for (int floor=0;floor<=maxfloor[x];floor++)
                {
                    int id1=msg[x][floor].id1;
                    int id2=msg[x][floor].id2;
                    int deep=msg[x][floor].deep;
                    ans+=get(L[id1],R[id1],y+2-deep);
                    if (id2!=-1) ans-=get(L[id2],R[id2],y+2-deep);
                }
                printf("%d\n",ans);
            }
            else
            {
                for (int floor=0;floor<=maxfloor[x];floor++)
                {
                    int id1=msg[x][floor].id1;
                    int id2=msg[x][floor].id2;
                    int deep=msg[x][floor].deep;
                    add(L[id1],R[id1],deep,y-w[x]);
                    if (id2!=-1) add(L[id2],R[id2],deep,y-w[x]);
                }
                w[x]=y;
            }
        }
        memset(vis,0,sizeof(vis));
        memset(msg,0,sizeof(msg));
        memset(w,0,sizeof(w));
        memset(c,0,sizeof(c));
        idn=0;
        for (int i=1;i<=n;i++) link[i].clear();
    }
}

(Educational Codeforces Round 8) E Zbazi in Zeydabad (树状数组优化暴力)

题目链接:

http://codeforces.com/contest/628/problem/E

 

题意:

问n*m (1<=n,m<=3000) 的矩阵中有多少个由z组成的Z。

 

做法:

先求出每个z向左,向右,向做下延伸的最大距离。

 

然后就是以每个z为右上角,设当前的z的坐标是(X,Y),设len=min(l[X][Y],ld[X][Y]),那么就是要查询从(X,Y)到(X+len-1,Y-len+1)的路径上有多少个坐标(x',y')满足y'+r[x'][y']-1>=Y。

 

思路其实很简单,暴力找的话总复杂度是O(n^3)。我们可以用树状数组优化查询的部分。

 

设路径上的z坐标为(x',y'),我们在(x',y'+r[x'][y']-1)这个位置涂上颜色,那么其实会发现满足条件的z对应的涂色的地方都在一个矩阵里。这个矩阵的x坐标属于【X,X+len-1】,矩阵里被涂色的方块个数就是我们要查询的答案。

 

可以用二维树状数组来求矩阵里的和。不过发现如果从左下角开始一直往右上角依次查询的话,当前点左下部分那些涂色的地方之后就不会用到了,所以建立x坐标的树状数组,把那些没用的点去掉,就可以了。

 

cf的题目质量真的高。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=3005;
int n,m;
long long ans;
int l[N][N],r[N][N],ld[N][N];//左,右,左下延伸的距离
int c[N];//x坐标的树状数组
char s[N][N];
vector<int>vec[N]; //用来去掉已经没用的涂色方块
void add(int x,int y)
{
	for (int i=x;i<N;i+=i&-i) c[i]+=y;
}
int get(int x)
{
	int tmp=0;
	for (int i=x;i;i-=i&-i) tmp+=c[i];
	return tmp;
}
void cal(int x,int y)
{
	for (int i=0;i<N;i++) c[i]=0,vec[i].clear();
	for (;x>=1&&y<=m;x--,y++)
	{
		if (s[x][y]=='z')
		{
			add(x,1);//(x,y+r[x][y]-1)的位置被涂色
			vec[y+r[x][y]-1].push_back(x);// 在涂色的那一列存下x坐标,等待删除
			ans+=get(x+min(l[x][y],ld[x][y])-1)-get(x-1);//统计查询的矩阵里的涂色块
		}
		for (int i=0;i<vec[y].size();i++) add(vec[y][i],-1); //去掉y所在那一列没用的涂色块
		vec[y].clear();
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) scanf("%s",s[i]+1);
	for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=m;j++)
			if (s[i][j]=='z') l[i][j]=l[i][j-1]+1;
		for (int j=m;j>=1;j--)
			if (s[i][j]=='z') r[i][j]=r[i][j+1]+1;
	}
	for (int i=n;i>=1;i--)
		for (int j=1;j<=m;j++)
		 	if (s[i][j]=='z') ld[i][j]=ld[i+1][j-1]+1;
	for (int i=1;i<=n;i++) cal(i,1); //从边界开始往右上角统计
	for (int j=2;j<=m;j++) cal(n,j); //从边界开始往右上角统计
	printf("%I64d\n",ans);
}

 

hdu4912-Paths on the tree(2014 Multi-University Training Contest 5)

题意:给你一棵树,和m条链,问最多能取多少条没有公共点的链。

这道题的加强版出现在15年的多校第1场,见hdu5293,加强版每条链还有权值。

因为我是先做了加强版(当时坑了一天),看到这道题,于是直接树形dp了。。。。(标算是贪心。。。)

每个节点u维护两个值:1.f[u]表示u所在子树最多有几条链   2.sum[u]表示u的子节点的f值的和

sum[u]很好搞,f[u]的转移方法如下:

假设有一条链x,y|lca(x,y)==u

f[u]=max(sum[u],sigma(sum[v])-sigma(f[v])+1)

其中v是链上的点

简单的说就是:

1.不要这条链,f[u]就是sum[u]

2.要这条链,那么f[u]=这条链下面的所有子树能够选择的链的个数+1

链上求和的话可以用树状数组或者线段树维护dfs序,每个点记录根到该点路径上的和,每修改一个点就是把其子树里点的值都加上f[u]或sum[u].

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100005;
int base[N],vec[2*N],pre[2*N],tot;

struct pair{
	int x,y;
}to[N];
int head[N],nxt[N],total;

int fa[N][25],deep[N];
int dfn[N],time,l[N],r[N];
int cf[N],cs[N];
int f[N],sum[N];
int n,m,x,y,i;
void addf(int x,int k)
{
    for (int i=x;i<=n;i+=i&-i) cf[i]+=k;
}
void addsum(int x,int k)
{
    for (int i=x;i<=n;i+=i&-i) cs[i]+=k;
}
int getf(int x)
{
    int tmp=0;
    for (int i=x;i;i-=i&-i) tmp+=cf[i];
    return tmp;
}
int getsum(int x)
{
    int tmp=0;
    for (int i=x;i;i-=i&-i) tmp+=cs[i];
    return tmp;
}
void push(int u,int x,int y)
{
	to[++total]={x,y}; nxt[total]=head[u];  head[u]=total;
}
void link(int x,int y)
{
	vec[++tot]=y;  pre[tot]=base[x];  base[x]=tot;
}
int lca(int x,int y)
{
	if (deep[x]<deep[y]) swap(x,y);
	if (deep[x]>deep[y])
		{
			for (int j=20;j>=0;j--)
				if (deep[fa[x][j]]>deep[y]) x=fa[x][j];
			x=fa[x][0];
		}
	if (x==y) return x;
	for (int j=20;j>=0;j--)
		if (fa[x][j]!=fa[y][j]) x=fa[x][j],y=fa[y][j];
	return fa[x][0];
}
void dfs1(int u)
{
	dfn[u]=++time;
	l[u]=time;
	for (int now=base[u];now;now=pre[now])
	{
		int v=vec[now];
		if (v!=fa[u][0])
		{
			fa[v][0]=u;
			deep[v]=deep[u]+1;
			dfs1(v);
		}
	}
	r[u]=time;
}
void dfs2(int u)
{
	sum[u]=0;
	for (int now=base[u];now;now=pre[now])
	{
		int v=vec[now];
		if (v!=fa[u][0])
		{
			dfs2(v);
			sum[u]+=f[v];
		}
	}
	f[u]=sum[u];
	for (int i=head[u];i;i=nxt[i])
	{
		int x=dfn[to[i].x],y=dfn[to[i].y];
		f[u]=max(f[u],getsum(x)+getsum(y)+sum[u]-getf(x)-getf(y)+1);
	}
	addf(l[u],f[u]);
	addf(r[u]+1,-f[u]);
	addsum(l[u],sum[u]);
	addsum(r[u]+1,-sum[u]);
}
int main()
{
	while(~scanf("%d%d",&n,&m))
	{
		time=0;
		tot=0;  total=0;
		memset(head,0,sizeof(head));
		memset(base,0,sizeof(base));
		memset(cf,0,sizeof(cf));
		memset(cs,0,sizeof(cs));
		for (i=1;i<n;i++) scanf("%d%d",&x,&y),link(x,y),link(y,x);
		fa[1][0]=1;  deep[1]=1;
		dfs1(1);
		for (int j=1;j<=20;j++)
			for (i=1;i<=n;i++) fa[i][j]=fa[fa[i][j-1]][j-1];
		for (i=1;i<=m;i++)
		{
			scanf("%d%d",&x,&y);
			push(lca(x,y),x,y);
		}
		dfs2(1);
		printf("%d\n",f[1]);
	}
}

贪心做法:

将链按lca的深度排序,从深度大的开始,能取就取,取完后把该链下的子树全部标记,因此每次判断能否取就是O(1)的.(竟然跑的比上面的方法慢?!)

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100005;
int base[N],vec[2*N],pre[2*N],tot;

struct chain{
	int x,y,u;
}a[N];
int fa[N][25],deep[N];
bool vis[N];
bool cmp(chain a,chain b) {return deep[a.u]>deep[b.u];}
int n,m,x,y,i;
void link(int x,int y)
{
	vec[++tot]=y;  pre[tot]=base[x];  base[x]=tot;
}
int lca(int x,int y)
{
	if (deep[x]<deep[y]) swap(x,y);
	if (deep[x]>deep[y])
		{
			for (int j=20;j>=0;j--)
				if (deep[fa[x][j]]>deep[y]) x=fa[x][j];
			x=fa[x][0];
		}
	if (x==y) return x;
	for (int j=20;j>=0;j--)
		if (fa[x][j]!=fa[y][j]) x=fa[x][j],y=fa[y][j];
	return fa[x][0];
}
void dfs(int u)
{
	for (int now=base[u];now;now=pre[now])
	{
		int v=vec[now];
		if (v!=fa[u][0])
		{
			fa[v][0]=u;
			deep[v]=deep[u]+1;
			dfs(v);
		}
	}
}
void clean(int u)
{
	if (vis[u]) return;
	vis[u]=1;
	for (int now=base[u];now;now=pre[now])
	{
		int v=vec[now];
		if (v!=fa[u][0]) clean(v);
	}
}
int main()
{
	while(~scanf("%d%d",&n,&m))
	{
		tot=0;
		memset(base,0,sizeof(base));
		memset(vis,0,sizeof(vis));
		for (i=1;i<n;i++) scanf("%d%d",&x,&y),link(x,y),link(y,x);
		fa[1][0]=1;  deep[1]=1;
		dfs(1);
		for (int j=1;j<=20;j++)
			for (i=1;i<=n;i++) fa[i][j]=fa[fa[i][j-1]][j-1];
		for (i=1;i<=m;i++)
		{
			scanf("%d%d",&a[i].x,&a[i].y);
			a[i].u=lca(a[i].x,a[i].y);
		}
		sort(a+1,a+m+1,cmp);
		int ans=0;
		for (i=1;i<=m;i++)
			if (!vis[a[i].x]&&!vis[a[i].y])
			{
				clean(a[i].u);
				ans++;
			}
		printf("%d\n",ans);
	}
}