11
28
2015
1

初探主席树

先膜拜一下初三大爷zyd,毕竟是他的点拨,再膜拜一下即将摘金牌的lyz大爷,毕竟也有他的帮助。

好了,现在开始讲主席树。听说这东西很多别名,比如,可持久化线段树函数式版本的线段树……他的主要思想就是存许多棵线段树,存的是一段数字区间出现次数。

对于每个前缀1...i,我们建一棵线段树Ti,存的是数字区间出现的次数。但这会MLE,然后我们又可以发现,Ti与Ti-1两棵线段树就相差了i这个点的值,所以只改变了logn个点,因此,Ti只要在Ti-1的基础上在最多建logn个,其他的直接指向Ti-1上的点就可以了,这样就解决了内存的问题,因此,主席树成功建完。

现在要用主席树来解决实际问题啦

一个经典问题是给你n个数,每次询问区间l-r第k大值。

假设我们现在已经离线处理好所有的线段树,我们只要在Tr与Tl-1两棵树上解决这个问题,我们令root[i]为Ti的根节点编号,令p1为当前在Tl-1上的节点编号,p2为当前在Tr上的节点编号,由于任意两棵线段树的大小形态都是完全一样的,因此p1,p2表示的数字区间是完全一样。我们令tr[p].sum为编号为p的数字区间出现次数,tr[p].l为p的左儿子的编号,tr[p].r为p的右儿子的节点编号。如果num[tr[p2].l]-num[tr[p1].l]>=k那么第k大数在左儿子的区间里,那么就往左儿子递归找第k大,否则就往右儿子递归找第(k-num[tr[p2].l]+num[tr[p1].l])大(因为要把左儿子的次数减掉)。因此这个经典问题就在nlogn的复杂度里完美解决了。

最后附上这个题的代码。

 

#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,cnt,a[100010],b[100010],root[100010],n1,x,y,k;
struct node{int l,r,sum;}tr[400010];
void change(int l,int r,int &p,int x)
{
	tr[++cnt]=tr[p];p=cnt;tr[p].sum++;
	if (l==r) return;
	int mid=(l+r)>>1;
	if (x<=mid) change(l,mid,tr[p].l,x);else change(mid+1,r,tr[p].r,x); 
}
int ask(int p1,int p2,int l,int r,int k)
{
	if (l==r) return l;
	int t=tr[tr[p2].l].sum-tr[tr[p1].l].sum;
	int mid=(l+r)>>1;
	if (t>=k) return ask(tr[p1].l,tr[p2].l,l,mid,k);else return ask(tr[p1].r,tr[p2].r,mid+1,r,k-t);
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		b[i]=a[i];
	}
	sort(b+1,b+n+1);
	n1=unique(b+1,b+n+1)-b-1;
	for (int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+n1+1,a[i])-b;//离散化
	for (int i=1;i<=n;i++){root[i]=root[i-1];change(1,n,root[i],a[i]);} //构建主席树
	while (m--)
	{
		scanf("%d%d%d",&x,&y,&k);
		printf("%d\n",b[ask(root[x-1],root[y],1,n,k)]);
	}
}
Category: 主席树 | Tags:
11
27
2015
0

【bzoj3626】【LNOI2014】LCA

3626: [LNOI2014]LCA

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 1045  Solved: 359
[Submit][Status][Discuss]

Description

给出一个n个节点的有根树(编号为0到n-1,根节点为0)。一个点的深度定义为这个节点到根的距离+1。
设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先。
有q次询问,每次询问给出l r z,求sigma_{l<=i<=r}dep[LCA(i,z)]。
(即,求在[l,r]区间内的每个节点i与z的最近公共祖先的深度之和)

 

Input

第一行2个整数n q。
接下来n-1行,分别表示点1到点n-1的父节点编号。
接下来q行,每行3个整数l r z。

 

Output

输出q行,每行表示一个询问的答案。每个答案对201314取模输出

 

Sample Input

5 2
0
0
1
1
1 4 3
1 4 2
 

Sample Output

8
5
 

HINT

共5组数据,n与q的规模分别为10000,20000,30000,40000,50000。

题解:我们可以先将问题转化一下,求lca的深度之和就是lca及其上面的点数之和,因此暴力可以这样打,把l~r所有点到根节点的路径上每个点的权值加1,答案就是z到根节点的路径上所有点的权值之和。然而,这时间复杂度是n3的。但这优化非常容易,我们只要把询问离线处理,l~r的答案就是(0~r)-(0~l-1),所以我们只要每次0~n-1中加n这个点,将n到根节点所有点权值加1,然后处理答案。这个用树剖轻松解决。总时间复杂度O(n*logn*logn),完美解决此题。

代码:

 

/**************************************************************
    Problem: 3626
    User: yeweining
    Language: C++
    Result: Accepted
    Time:2876 ms
    Memory:9816 kb
****************************************************************/
 
#include<cstdio>
#include<algorithm>
#include<iostream>
#define mod 201314
#define N 50010
using namespace std;
int n,q,v,edgenum,Edgenum,tot;
int vet[2*N],next[2*N],head[2*N],Vet[2*N],Next[2*N],Head[2*N],Id[2*N],Calc[2*N],x[N],y[N],z[N],ans[N];
int f[N],d[N],num[N],son[N],top[N],tid[N],pre[N];
struct node{int l,r,sum,tag;}tr[4*N];
void add(int u,int v)
{
    edgenum++;
    vet[edgenum]=v;
    next[edgenum]=head[u];
    head[u]=edgenum;
}
void Add(int u,int v,int w,int opt)
{
    Edgenum++;
    Vet[Edgenum]=v;
    Next[Edgenum]=Head[u];
    Head[u]=Edgenum;
    Id[Edgenum]=w;
    Calc[Edgenum]=opt;
}
void dfs(int u,int fa,int dep)
{
    f[u]=fa;
    d[u]=dep;
    num[u]=1;
    int maxnum=0;
    for (int e=head[u];e;e=next[e])
    {
        int v=vet[e];
        if (v!=fa)
        {
            dfs(v,u,dep+1);
            num[u]+=num[v];
            if (num[v]>maxnum){maxnum=num[v];son[u]=v;}
        }
    }
}
void dfs(int u,int number)
{
    top[u]=number;
    tid[u]=++tot;
    pre[tot]=u;
    if (!son[u]) return;
    dfs(son[u],number);
    for (int e=head[u];e;e=next[e])
    {
        int v=vet[e];
        if (v!=f[u]&&v!=son[u]) dfs(v,v);
    }
}
void build(int l,int r,int p)
{
    tr[p].l=l;tr[p].r=r;tr[p].sum=tr[p].tag=0;
    if (l==r) return;
    int mid=(l+r)>>1;
    build(l,mid,p*2);
    build(mid+1,r,p*2+1);
}
void pushdown(int p)
{
    int l=tr[p].l,r=tr[p].r;
    int mid=(l+r)>>1;
    if (l!=r)
    {
        tr[p*2].tag+=tr[p].tag;
        tr[p*2+1].tag+=tr[p].tag;
    }
    tr[p].sum+=tr[p].tag*(r-l+1);
    tr[p].sum%=mod;
    tr[p].tag=0;
}
void update(int p)
{
    pushdown(p*2);
    pushdown(p*2+1);
    tr[p].sum=tr[p*2].sum+tr[p*2+1].sum;
    tr[p].sum%=mod;
}
void change(int p,int x,int y)
{
    int l=tr[p].l,r=tr[p].r;
    pushdown(p);
    if (l==x&&y==r){tr[p].tag+=1;return;}
    int mid=(l+r)>>1;
    if (y<=mid) change(p*2,x,y);else
    if (x>mid) change(p*2+1,x,y);else
    {change(p*2,x,mid);change(p*2+1,mid+1,y);}
    update(p);
}
void update(int u,int v)
{
    while (top[u]!=top[v]) {change(1,tid[top[u]],tid[u]);u=f[top[u]];}
    change(1,tid[v],tid[u]);
}
int ask(int p,int x,int y)
{
    int l=tr[p].l,r=tr[p].r;
    pushdown(p);
    if (l==x&&y==r) return tr[p].sum;
    int mid=(l+r)>>1;
    if (y<=mid) return ask(p*2,x,y);else
    if (x>mid) return ask(p*2+1,x,y);else
    return (ask(p*2,x,mid)+ask(p*2+1,mid+1,y))%mod;
}
int get(int u,int v)
{
    int ans=0;
    while (top[u]!=top[v]) {ans+=ask(1,tid[top[u]],tid[u]);ans%=mod;u=f[top[u]];}
    ans+=ask(1,tid[v],tid[u]);
    ans%=mod;
    return ans;
}
int main()
{
    scanf("%d%d",&n,&q);
    for (int i=2;i<=n;i++)
    {
        scanf("%d",&v);
        v++;
        add(v,i);
    }
    for (int i=1;i<=q;i++)
    {
        scanf("%d%d%d",&x[i],&y[i],&z[i]);
        x[i]++;y[i]++;z[i]++;
        if (x[i]>y[i]) swap(x[i],y[i]);
        Add(x[i]-1,z[i],i,-1);Add(y[i],z[i],i,1);
    }
    dfs(1,0,1);
    dfs(1,1);
    build(1,n,1);
    for (int i=1;i<=n;i++)
    {
        update(i,1);
        for (int e=Head[i];e;e=Next[e])
        {
            int v=Vet[e];
            ans[Id[e]]+=(mod+Calc[e]*get(v,1))%mod;
            ans[Id[e]]%=mod;
        }
    }
    for (int i=1;i<=q;i++) printf("%d\n",ans[i]%mod);
}
Category: 树链剖分 | Tags:
11
26
2015
0

【JSOI2015】对称图形

这是这套题最后完成的一题。然而做的我欲罢不能,骑虎难下啊。。。

题意:给你一个01矩阵,让你找出最大子正方形,使得他们是轴对称,4对称,8对称,90对称,180对称。轴对称就是关于四条对称轴任意一条对称,90对称就是顺时针旋转90与原矩阵相同,180对称就是旋转180与原矩阵相同,4对称就是既是轴对称又是180对称,8对称就是既是轴对称又是90对称。

题解:运用hash思想来解决这道题目。将原矩阵的每个点赋一个权值,(i,j)的权值为Ai*Bj,把每个点乘以权值形成一个新矩阵,令这个矩阵为P,那么如何解决对称问题呢,举一个左右对称的例子,假如要判断最大的左右对称的子矩阵,我们可以把权值左右对称一下,然后得到另一个矩阵,令这个矩阵为Q,然后就是计算任意一个矩阵的hash值,我们只要用一下片段和,然后把整个矩形移到右下角(乘一个数即可),这就是一个子矩阵的hash值。假如要判断(x1,y1)->(x2,y2)的矩阵是否是左右对称,我们只要计算这个矩阵在P上的hash值与在Q上的hash值是否相同。以此类推其他对称情况。这样就O(1)实现矩阵对称性的判断,然而总复杂度还是n3的。但是我们可以发现只要确定中心,是有单调性的,只要二分即可。所以最我们在O(n2logn)的复杂度下日(哔~~~)掉了本题。

代码:

 

#include<cstdio>
using namespace std;
typedef unsigned int ull;
int n;
const int maxn=511,d1=666233,d2=2333;
ull h[7][maxn][maxn];
ull mi1[maxn],mi2[maxn];
char s[510];
/*
0代表原始1代表竖对称,2代表横对称,3代表主对角线对称,4代表副对角线对称 
5代表顺时针90度转,6代表180度转 
*/
void gethash()
{
    for (int k=0;k<=6;k++)
        for (int i=1;i<=n;i++)
        {
            ull P=mi1[n-i];
            for (int j=1;j<=n;j++)
            {
                h[k][i][j]=h[k][i][j]*P*mi2[n-j];
                h[k][i][j]+=h[k][i-1][j]+h[k][i][j-1]-h[k][i-1][j-1];
            }
        }
}
ull calc(int k,int x,int y,int len){return (h[k][x][y]-h[k][x-len][y]-h[k][x][y-len]+h[k][x-len][y-len])*mi1[x]*mi2[y];}
int judge(int i,int j,int len,int x)
{
    if (x==8)return judge(i,j,len,90)&judge(i,j,len,0);
    if (x==4)return judge(i,j,len,180)&judge(i,j,len,0);
    if (x==180&&calc(0,i,j,len)==calc(6,n-i+len,n-j+len,len))return 1;
    if (x==90&&calc(0,i,j,len)==calc(5,j,n-i+len,len))return 1;
    if (x==0&&
    (calc(0,i,j,len)==calc(1,i,n-j+len,len)||
    calc(0,i,j,len)==calc(2,n-i+len,j,len)||
    calc(0,i,j,len)==calc(3,j,i,len)||
    calc(0,i,j,len)==calc(4,n-i+len,n-j+len,len))) return 1;
    return 0;
}
int check(int len,int x)
{
    for (int i=len;i<=n;i++)
        for (int j=len;j<=n;j++) if (judge(i,j,len,x)) return 1;
    return 0;
}
int solve(int x)
{
    int l,r,mid;
    for (l=1,r=n/2;mid=l+r>>1,l<=r;){
        if (check(2*mid,x))l=mid+1;else r=mid-1;}
    l--;if (!check(2*l+1,x)) return 2*l;
    for (r=n/2;mid=l+r>>1,l<=r;)
        if (check(2*mid+1,x))l=mid+1;else r=mid-1;
    l--;return 2*l+1;
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        scanf("%s",s);
        for (int j=1;j<=n;j++) h[0][i][j]=h[1][i][n-j+1]=h[2][n-i+1][j]=h[3][j][i]=h[4][n-j+1][n-i+1]=h[5][j][n-i+1]=h[6][n-i+1][n-j+1]=s[j-1]-'0';
    }
    mi1[0]=1;for (int i=1;i<=n;i++) mi1[i]=mi1[i-1]*d1;
    mi2[0]=1;for (int i=1;i<=n;i++) mi2[i]=mi2[i-1]*d2;
    gethash();
    printf("%d %d %d %d %d\n",solve(8),solve(90),solve(4),solve(180),solve(0));
    return 0;
}
Category: 哈希 | Tags:
11
25
2015
1

【JSOI2015】旅行售货员

本题本人现场就A了哟!!

比较简单,就不多讲了,注意一下第二问的一些细节就好了。

代码:

#include<cstdio>
#include<algorithm>
using namespace std;
int n,u,v,tot,edgenum,ans,a[100010],f[100010],num[100010],q[100010],p[100010];
int vet[200010],next[200010],head[200010],ok[100010];
void add(int u,int v)
{
	edgenum++;
	vet[edgenum]=v;
	next[edgenum]=head[u];
	head[u]=edgenum;
}
bool cmp(const int x,const int y){return f[x]>f[y];}
void dfs(int u,int fa)
{
	int tot1=tot;
	f[u]=a[u];
	for (int e=head[u];e;e=next[e])
	{
		int v=vet[e];
		if (v!=fa) 
		{
			dfs(v,u);
			if (u==1){if (f[v]>0) {ans+=f[v];ok[1]=ok[1]|ok[v];}if (f[v]==0) ok[1]=1;}else q[++tot]=v;
		}
	}
	if (tot==tot1) return;
	if (u==1) return;
	for (int i=tot1+1;i<=tot;i++)
		p[i]=q[i];
	sort(p+tot1+1,p+tot+1,cmp);
	for (int i=tot1+1;i<=tot;i++)
	if (i-tot1>num[u]){if (i>tot1+1&&f[p[i]]==f[p[i-1]])ok[u]=1;break;}else if (f[p[i]]==0) ok[u]=1;else if (f[p[i]]<0) break;else {f[u]+=f[p[i]];ok[u]=ok[u]|ok[p[i]];}
	tot=tot1;
}
int main()
{
	freopen("salesman.in","r",stdin);
	freopen("salesman.out","w",stdout);
	scanf("%d",&n);
	for (int i=2;i<=n;i++)
		scanf("%d",&a[i]);
	for (int i=2;i<=n;i++)
	{
		scanf("%d",&num[i]);
		num[i]--;
	}
	for (int i=1;i<n;i++)
	{
		scanf("%d%d",&u,&v);
		add(u,v);add(v,u);
	}
	ans=0;
	dfs(1,0);
	printf("%d\n",ans);
	if (ok[1]) printf("solution is not unique\n");else printf("solution is unique\n");
}
Category: | Tags:
11
25
2015
1

【JSOI2015】简化树的同构

第一次写省队难度题目的题解,心里也是有点激动的辣!!!

题意:给你m棵树,让你先简化一下每棵树(这乱搞一下),然后把不同构(同构的定义不知道的话就去百度一下吧。。)的所有简化树的节点数从小到大输出来。

题解:我们只要把每种不同构的树用不同的权值表示,这样这道题就迎刃而解了。那么如何表示这个权值呢?请教了一下金牌爷gonens,。我们可以用类似于拓扑排序的方法来计算这个权值。先把所有度为1的点放入队列中,令他们的权值都为1。然后一层一层往上搜,每次发现当前的这个点度变为1时,就计算一下他的权值,把他儿子(注意:这里的儿子是有限制的:已经算好权值且和他不在同一层)的权值从小到大排个序,然后第一个数乘以A1,第二个数乘以A2,…,第n个数乘以An,A为自己定一个质数,然后加起来取模一个P就好了。至此,此题就顺利解决了。

不要问我一开始为什么T了,边表数组没开两倍啊。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include <iostream>
#define mod 1000000007
using namespace std;
typedef long long ll;
struct node{ll hash;int num;}p[25];
int n,m,u,v,edgenum,num;
ll mi[10010];
int vet[20010],next[20010],head[20010],flag[10010],fa[10010],d[10010],du[10010],id[10010],q[10010],dep[10010],P[25];
ll f[10010],a[10010];
void add(int u,int v)
{
	edgenum++;
	vet[edgenum]=v;
	next[edgenum]=head[u];
	head[u]=edgenum;
}
void dfs(int u,int pre)
{
	flag[u]=1;
	fa[u]=pre;
	if (d[u]!=2) id[u]=++num;else id[u]=pre;
	for (int e=head[u];e>0;e=next[e])
	{
		int v=vet[e];
		if (!flag[v])
			dfs(v,id[u]);	
	}
}
ll calc(int u)
{
	int cnt=0;
	for (int e=head[u];e;e=next[e])
	{
		int v=vet[e];
		if (flag[v]&&dep[v]!=dep[u])
			a[cnt++]=f[v];
	}
	sort(a,a+cnt);
	ll ans=0;
	for (int i=0;i<cnt;i++)
		ans=(ans+mi[i]*a[i]%mod)%mod;
	return ans;
}
bool cmp1(const node x,const node y){return x.hash<y.hash;}
int main()
{
	scanf("%d",&m);
	mi[0]=1;
	for (int i=1;i<=10000;i++)
		mi[i]=mi[i-1]*10003%mod;
	for (int k=1;k<=m;k++)
	{
		scanf("%d",&n);
		memset(d,0,sizeof(d));
		memset(head,0,sizeof(head));
		edgenum=0;
		for (int i=1;i<n;i++)
		{
			scanf("%d%d",&u,&v);
			d[u]++;d[v]++;
			add(u,v);add(v,u);
		}
		num=0;
		memset(flag,0,sizeof(flag));
		memset(fa,0,sizeof(fa));
		for (int i=1;i<=n;i++)
		if (d[i]!=2){dfs(i,0);break;}
		memset(head,0,sizeof(head));
		memset(du,0,sizeof(du));
		edgenum=0;
		for (int i=1;i<=n;i++)
		if (id[i]!=fa[i]&&fa[i]!=0){add(id[i],fa[i]);add(fa[i],id[i]);du[id[i]]++;du[fa[i]]++;}
		int p1=0,p2=0;
		memset(dep,0,sizeof(dep));
		memset(flag,0,sizeof(flag));
		for (int i=1;i<=num;i++)
		if (du[i]==1){q[++p2]=i;dep[i]=0;flag[i]=1;f[i]=1;}
		while (p1<p2)
		{
			u=q[++p1];
			for (int e=head[u];e;e=next[e])
			{
				int v=vet[e];
				if (!flag[v])
				{
					du[v]--;
					if (du[v]==1)
					{
						flag[v]=1;
						dep[v]=dep[u]+1;
						q[++p2]=v;
						f[v]=calc(v);
					}
				}
			}
		}
		sort(f+1,f+num+1);
		for (int i=1;i<=num;i++)
			p[k].hash=(p[k].hash+f[i]*mi[i-1]%mod)%mod;
		p[k].num=num;
	}
	sort(p+1,p+m+1,cmp1);
	n=1;
	P[1]=p[1].num;
	for (int i=2;i<=m;i++)
	if (p[i].hash!=p[i-1].hash) P[++n]=p[i].num;
	printf("%d\n",n);
	sort(P+1,P+n+1);
	for (int i=1;i<n;i++) printf("%d ",P[i]);
	printf("%d\n",P[n]);
}
Category: | Tags:

Host by is-Programmer.com | Power by Chito 1.3.3 beta | Theme: Aeros 2.0 by TheBuckmaker.com