ZJOI2016部分题解

**bzoj4455: [Zjoi2016]小星星

在这里

**bzoj4456: [Zjoi2016]旅行者

在这里

**bzoj4573: [Zjoi2016]大森林

这个题吼啊
以前做这类题都是要动态做一个倍增数组的…比如HNOI那道题
但是这个直接就给我打自闭了啊…然后又开始想类似二维偏序…然后又发现至少是三维的…
我们来讲题解做法
首先要先发现题目的一些小trick,保证了询问的每个点都是在每棵树中出现的
所以…我们其实可以先离线把树建出来,然后再回答询问是一个效果的
因为那些没有用的点是不会对答案产生影响的
然后再考虑多组询问怎么做
不妨考虑做完第 i i i棵树的询问之后来做第 i + 1 i+1 i+1棵的
假设对于移动了 [ L , R ] [L,R] [L,R]的生长节点并且后面均没有移动了的话
对于 L L L L − 1 L-1 L1这两棵树,他们的差别仅仅在于移动操作之后的加点操作
即我们可以把第 L − 1 L-1 L1棵树移动了生长节点之后添加的节点,全部移植到 L L L这棵树更改了的生长节点下面
同理,对于 R R R R + 1 R+1 R+1棵树,我们只需要把这些节点全部移植回原来的生长节点下面
所以现在的问题就在于怎么整体移动一些子树了
我们可以把每一次移动生长节点操作都加入一个虚点,然后后面添加节点操作,就让他挂在这个虚点下面
所以每次移动就转化为 c u t cut cut一个虚点然后把这个虚点 l i n k link link到另外一个实点下面
那么对于每个 1 1 1操作,就可以拆分成两个操作,即在 L L L是把这个虚点移动到一个实点下面,然后在 R + 1 R+1 R+1将其移回上一个虚点下方
增加节点的话就把他挂到时间轴距离他最近的虚点上
那么还有一个小问题,就是如果在区间移动生长节点的时候没有这个点给我移的话怎么办,估计只有我这个sb在想这个…其实可以知道,每次加入某个编号的点,他是在连续一段中加入的,所以如果在加入的区间中存在不合法,那么合法的区间也是一整段的。 L , R L,R L,R分别取一下限制就可以了…
然后就到怎么算答案的问题了…
把实点的权看作1,虚点的权看作0,就是他们的路径点权和了。然而这里并不能直接 m a r k r o o t markroot markroot来提取路径,因为这样一做的话父子关系就乱掉了…注意他是一个以 1 1 1为根的有根树啊。我们其实可以利用 L C T LCT LCT来求 L C A LCA LCA,即先 a c c e s s ( x ) access(x) access(x),然后 s p l a y ( x ) splay(x) splay(x),再 a c c e s s ( y ) access(y) access(y) a c c e s s ( y ) access(y) access(y)时路径上遇到的最后一条虚边的父亲即为 L C A LCA LCA
这篇blog中有图可以瞅瞅

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<ctime>
#include<map>
#include<bitset>
#include<set>
#define LL long long
#define mp(x,y) make_pair(x,y)
#define pll pair<long long,long long>
#define pii pair<int,int>
using namespace std;
inline int read()
{
	int f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int stack[20];
inline void write(int x)
{
	if(x<0){putchar('-');x=-x;}
    if(!x){putchar('0');return;}
    int top=0;
    while(x)stack[++top]=x%10,x/=10;
    while(top)putchar(stack[top--]+'0');
}
inline void pr1(int x){write(x);putchar(' ');}
inline void pr2(int x){write(x);putchar('\n');}
const int MAXN=100005;
const int MAXM=200005;
struct link_cut_tree{int son[2],f,sum,val;}tr[MAXN*3];int cnt,now;
void updata(int x){tr[x].sum=tr[tr[x].son[0]].sum+tr[tr[x].son[1]].sum+tr[x].val;}
void rotate(int x,int w)
{
	int R,r;
	int f=tr[x].f,ff=tr[f].f;
	R=f;r=tr[x].son[w];
	tr[R].son[1-w]=r;
	if(r)tr[r].f=R;
	R=ff;r=x;
	if(tr[R].son[0]==f)tr[R].son[0]=r;
	else if(tr[R].son[1]==f)tr[R].son[1]=r;
	tr[r].f=R;
	R=x;r=f;
	tr[R].son[w]=r;
	if(r)tr[r].f=R;
	updata(f);updata(x);
}
bool isroot(int x){return tr[tr[x].f].son[0]!=x&&tr[tr[x].f].son[1]!=x;}
void splay(int x,int rt)
{
	while(tr[x].f!=rt&&!isroot(x))
	{
		int f=tr[x].f,ff=tr[f].f;
		if(ff==rt||isroot(f))
		{
			if(tr[f].son[0]==x)rotate(x,1);
			else rotate(x,0);
		}
		else
		{
			if(tr[ff].son[0]==f&&tr[f].son[0]==x)rotate(f,1),rotate(x,1);
			else if(tr[ff].son[0]==f&&tr[f].son[1]==x)rotate(x,0),rotate(x,1);
			else if(tr[ff].son[1]==f&&tr[f].son[1]==x)rotate(f,0),rotate(x,0);
			else rotate(x,1),rotate(x,0);
		}
	}
}
int access(int x)
{
	int y=0;
	while(x!=0)
	{
		splay(x,0);
		tr[x].son[1]=y;
		if(y)tr[y].f=x;
		updata(x);
		y=x;x=tr[x].f;
	}
	return y;
}
void cut(int x)
{
	access(x);splay(x,0);
	tr[tr[x].son[0]].f=0;tr[x].son[0]=0;
	updata(x);
}
int n,m;
struct oper
{
	int L,o,fa,u,v;
	oper(){}
	oper(int _L,int _o,int _fa,int _u,int _v){L=_L;o=_o;fa=_fa;u=_u;v=_v;}
}w[MAXM*2];int ln;
int L[MAXM],R[MAXM],answer[MAXM];
bool cmp(oper n1,oper n2){return n1.L!=n2.L?n1.L<n2.L:n1.o<n2.o;}
int main()
{
	n=read();m=read();int lst=0;
	tr[cnt=n+1].f=1;now=1;L[1]=1;R[1]=n;
	for(int i=1;i<=m;i++)
	{
		int o=read(),l=read(),r=read();
		if(o==0)
		{
			now++;tr[now].val=1;tr[now].f=cnt;
			L[now]=l;R[now]=r;
		}
		else if(o==1)
		{
			int x=read();
			l=max(L[x],l);r=min(R[x],r);if(l>r)continue;
			cnt++;tr[cnt].f=cnt-1;
			w[++ln]=oper(l,0,x,cnt,0);w[++ln]=oper(r+1,0,cnt-1,cnt,0);
		}
		else
		{
			int x=read();
			w[++ln]=oper(l,++lst,0,r,x);
		}
	}
	sort(w+1,w+1+ln,cmp);
	for(int i=1;i<=ln;i++)
	{
		if(w[i].o)
		{
			int as=0;
			access(w[i].u);splay(w[i].u,0);as+=tr[w[i].u].sum;
			int LA=access(w[i].v);splay(w[i].v,0);as+=tr[w[i].v].sum;
			access(LA);splay(LA,0);as-=tr[LA].sum<<1;
			answer[w[i].o]=as;
		}
		else cut(w[i].u),tr[w[i].u].f=w[i].fa;
	}
	for(int i=1;i<=lst;i++)pr2(answer[i]);
	return 0;
}

**bzoj4574: [Zjoi2016]线段树

这个题也吼啊…一开始在状态方面就直接想歪了.
首先先要知道随机数据怎么用…发现 n n n很小所以大胆猜想数没有相同的存在…
事实上应该是正确的??
虽然相同好像也可以做但是就更恶心
考虑每个数最终的贡献的话会变得好做,然后就很显然了
枚举每个数,那么他能作为最大值的范围是唯一确定的 [ L , R ] [L,R] [L,R]
定义对于一个数 a [ x ] a[x] a[x]的极大区间 [ i , j ] [i,j] [i,j]为对于 i ≤ u ≤ j i\leq u\leq j iuj,满足 m a x ( a [ u ] ) ≤ a [ x ] max(a[u])\leq a[x] max(a[u])a[x]
m i n ( a [ i − 1 ] , a [ j + 1 ] ) &gt; a [ x ] min(a[i-1],a[j+1])&gt;a[x] min(a[i1],a[j+1])>a[x]
那么设状态 d p [ k , i , j , x ] dp[k,i,j,x] dp[k,i,j,x]表示第 k k k次操作过后,当前对于 x x x的极大区间为 [ i , j ] [i,j] [i,j]的方案数
考虑转移
我们思考这个区间在什么位置的时候会对状态转移产生什么影响
假如选中的区间在 [ 1 , i − 1 ] [1,i-1] [1,i1] [ i , j ] [i,j] [i,j] [ j + 1 , n ] [j+1,n] [j+1,n],那么不会对下一个极大区间产生任何影响
假如选中的区间完全覆盖了 [ i , j ] [i,j] [i,j],即左端点小于 i i i右端点大于 j j j,显然就不存在极大区间了,所以不会对下一个状态产生任何贡献
那么仅剩下选中的区间部分在 [ i , j ] [i,j] [i,j]内了,先讨论左端点 v v v在外面的情况,右端点 u u u同理
那么下一个极大区间显然就是 [ u + 1 , j ] [u+1,j] [u+1,j]
c n t [ i ] cnt[i] cnt[i]表示长度为 i i i的序列任选的方案数,显然是 i ∗ ( i + 1 ) 2 \frac{i*(i+1)}{2} 2i(i+1)
所以状态转移就是
d p [ k , i , j , x ] = d p [ k − 1 , i , j , x ] ∗ ( c n t [ i − 1 ] + c n t [ j − i + 1 ] + c n t [ n − j ] ) dp[k,i,j,x]=dp[k-1,i,j,x]*(cnt[i-1]+cnt[j-i+1]+cnt[n-j]) dp[k,i,j,x]=dp[k1,i,j,x](cnt[i1]+cnt[ji+1]+cnt[nj])
+ ∑ v &lt; i d p [ k − 1 , v , j , x ] ∗ ( v − 1 ) + ∑ u &gt; j d p [ k − 1 , i , u , x ] ∗ ( n − u ) +\sum_{v&lt;i} dp[k-1,v,j,x]*(v-1)+\sum_{u&gt;j} dp[k-1,i,u,x]*(n-u) +v<idp[k1,v,j,x](v1)+u>jdp[k1,i,u,x](nu)
这里乘的 v − 1 v-1 v1 n − u n-u nu表示了选择的区间左端点/右端点的方案数
前缀和一下转移就可以 O ( 1 ) O(1) O(1)
由于数据随机,所以实际复杂度是 q n 2 qn^2 qn2
卡卡常数就能过了
留了个卡常的好技巧…其实有时候 l o n g l o n g long long longlong也是很有优势的
比如 ∑ \sum 起来不超过 l o n g l o n g long long longlong范围但是对 i n t int int取模的,我们可以先把他扔到 l o n g l o n g longlong longlong里,然后最后一次取模,因为 c + + c++ c++取模很慢所以这样可以大幅优化常数,适用于取模多且不超过 l o n g l o n g longlong longlong的题

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<ctime>
#include<map>
#include<bitset>
#include<set>
#define LL long long
#define mp(x,y) make_pair(x,y)
#define pll pair<long long,long long>
#define pii pair<int,int>
using namespace std;
inline int read()
{
	int f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int stack[20];
inline void write(int x)
{
	if(x<0){putchar('-');x=-x;}
    if(!x){putchar('0');return;}
    int top=0;
    while(x)stack[++top]=x%10,x/=10;
    while(top)putchar(stack[top--]+'0');
}
inline void pr1(int x){write(x);putchar(' ');}
inline void pr2(int x){write(x);putchar('\n');}
const int MAXN=405;
const int mod=1e9+7;
int n,Q,a[MAXN],rk[MAXN],b[MAXN];
LL f[2][MAXN][MAXN];
int cal[MAXN][MAXN],sum[MAXN][MAXN];
void clear(int nw,int l,int r){for(int i=l;i<=r;i++)for(int j=i;j<=r;j++)f[nw][i][j]=0;}
void ad(int &x,int y){x+=y;if(x>=mod)x-=mod;}
void dl(int &x,int y){x-=y;if(x<0)x+=mod;}

void solve(int l,int r,int x)
{
	int nw=0;clear(nw,l,r);f[nw][l][r]=1;
	LL su=0;
	for(int T=1;T<=Q;++T)
	{
		nw^=1;
		for(int i=l;i<=r;++i)
		{
			su=0;
			for(int j=r;j>=i;--j)
			{
				f[nw][i][j]=f[nw^1][i][j]*cal[i][j];
				f[nw][i][j]+=su;
				su+=f[nw^1][i][j]*(n-j);
			}
		}
		for(int j=l;j<=r;++j)
		{
			su=0;
			for(int i=l;i<=j;++i)
			{
				f[nw][i][j]=(f[nw][i][j]+su)%mod;
				su+=f[nw^1][i][j]*(i-1);
			}
		}
	}
	for(int i=l;i<=r;i++)for(int j=i;j<=r;j++)
		ad(sum[x][i],f[nw][i][j]),dl(sum[x][j+1],f[nw][i][j]);
}
int cnt[MAXN];
int main()
{
	n=read();Q=read();
	for(int i=1;i<=n;i++)cnt[i]=i*(i+1)/2;
	for(int i=1;i<=n;i++)for(int j=i;j<=n;j++)cal[i][j]=cnt[i-1]+cnt[j-i+1]+cnt[n-j];
	a[0]=a[n+1]=(1<<31-1);
	for(int i=1;i<=n;i++)a[i]=b[i]=read();
	sort(b+1,b+1+n);
	for(int i=1;i<=n;i++)
	{
		rk[i]=lower_bound(b+1,b+1+n,a[i])-b;
		int ls=i,rs=i;
		while(a[ls-1]<a[i])ls--;
		while(a[rs+1]<a[i])rs++;
		solve(ls,rs,rk[i]);
	}
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)ad(sum[i][j],sum[i][j-1]);
	for(int i=1;i<=n;i++)
	{
		int ans=0,ls=0;
		for(int j=1;j<=n;j++)
		{
			if(!sum[j][i])continue;
			int vi=(sum[j][i]-sum[ls][i]+mod)%mod;
			ad(ans,1LL*b[j]*vi%mod);ls=j;
		}
		pr1(ans);
	}
	puts("");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值