8.23考試總結(NOIP模擬46)[數數·數樹·鼠樹·ckw的樹]

Varuxn發表於2021-08-25

T1 數數

解題思路

大概是一個簽到題的感覺。。。(但是 pyt 並沒有簽上)

第一題當然可以找規律,但是我們們還是老老實實搞正解吧。。。

先從小到大拍個序,這樣可以保證 \(a_l<a_r\) 直接去掉絕對值。

然後就可以推出如下柿子:

\[\displaystyle\sum_{l=1}^{k}-a_l\times(k-l)+\sum_{r=2}^{k}a_r\times a_r(r-1) \]

\[\displaystyle\sum_{i=1}^{k}a_i\times (2\times i-k-1) \]

然後就會發現 \(a_i\) 的貢獻的正負與下標有關係那麼對於 \(i<\dfrac{k+1}{2}\) 儘量選擇小的數字。

同樣的,對於 \(i>\dfrac{k+1}{2}\) 儘量選擇比較大的數字。

對於不同的 \(k\) 每一個 \(a_i\) 的係數都是會發生變化的,簡單處理一下就好了。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=3e5+10;
int n,l,r,sum1,sum2,pre[N],s[N];
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
		s[i]=read();
	l=0;	r=n+1;
	sort(s+1,s+n+1);
	for(int i=1;i<=n;i++)
		pre[i]=pre[i-1]+s[i];
	for(int k=1;k<=n;k++)
	{
		int pos=(k+1)/2;
		sum1-=pre[l];	sum2+=pre[n]-pre[r-1];
		while(l<pos)	l++,sum1+=s[l]*(2*l-k-1);
		while(n-r+1<pos)	r--,sum2+=s[r]*(2*(-n+r)+k-1);
		printf("%lld\n",sum2+sum1);
	}
	return 0;
}

T2 數樹

解題思路

樹上揹包 DP 。

\(f_{i,j,0/1/2/3}\) 表示 節點 \(i\) 目前至少\(j\)不合法的邊,並且當前節點狀態是:沒有出入邊,有一條出邊,有一條入邊,有一條出邊一條入邊。

然後進行樹形 DP 子樹合併計算出葉子節點對於自身的貢獻,當然也要把沒有貢獻的繼承過來。

最後算出來的結果運用一個類似於二項式反演的東西就可以得到恰好沒有不合法邊的狀態。

為什麼說是類似呢,它的係數是有一些差別的 \((n-i)!\) ,畢竟對於有 \(i\) 條不合法的邊而言,它所連結的兩個點其實可以看作是一個點,然後就是一個數值的排問題了。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=5e3+10,mod=998244353;
int n,ans,fac[N],siz[N],f[N][N][4],g[N][4];
int tot,head[N],nxt[N<<1],edge[N<<1],ver[N<<1];
void add_edge(int x,int y,int val)
{
	ver[++tot]=y;
	nxt[tot]=head[x];
	edge[tot]=val;
	head[x]=tot;
}
void dfs(int x)
{
	siz[x]=1;	f[x][0][0]=1;
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(siz[to])	continue;
		dfs(to);
		memset(g,0,sizeof(g));
		for(int j=0;j<siz[x];j++)
			for(int k=0;k<siz[to];k++)
			{
				int cnt=(f[to][k][0]+f[to][k][1]+f[to][k][2]+f[to][k][3])%mod;
				switch(edge[i])
				{
					case 1:
						(g[j+k+1][1]+=f[x][j][0]*(f[to][k][0]+f[to][k][1]))%=mod;
						(g[j+k+1][3]+=f[x][j][2]*(f[to][k][0]+f[to][k][1]))%=mod;
						break;
					case 0:
						(g[j+k+1][2]+=f[x][j][0]*(f[to][k][0]+f[to][k][2]))%=mod;
						(g[j+k+1][3]+=f[x][j][1]*(f[to][k][0]+f[to][k][2]))%=mod;
						break;
				}
				(g[j+k][0]+=cnt*f[x][j][0])%=mod;
				(g[j+k][1]+=cnt*f[x][j][1])%=mod;
				(g[j+k][2]+=cnt*f[x][j][2])%=mod;
				(g[j+k][3]+=cnt*f[x][j][3])%=mod;
			}
		siz[x]+=siz[to];
		for(int j=0;j<siz[x];j++)
		{
			f[x][j][0]=g[j][0];
			f[x][j][1]=g[j][1];
			f[x][j][2]=g[j][2];
			f[x][j][3]=g[j][3];
		}
	}
}
signed main()
{
	n=read();
	for(int i=1,x,y;i<n;i++)
	{
		x=read();	y=read();
		add_edge(x,y,1);	add_edge(y,x,0);
	}
	dfs(1);	fac[0]=1;
	for(int i=1;i<=n;i++)
		fac[i]=fac[i-1]*i%mod;
	for(int i=0,temp;i<n;i++)
	{
		temp=0;
		(temp+=f[1][i][0])%=mod;
		(temp+=f[1][i][1])%=mod;
		(temp+=f[1][i][2])%=mod;
		(temp+=f[1][i][3])%=mod;
		if(i&1)	(ans+=mod-fac[n-i]*temp%mod)%=mod;
		else	(ans+=fac[n-i]*temp%mod)%=mod;
	}
	printf("%lld",ans);
	return 0;
}

T3 鼠樹

解題思路

其實題寫的挺艱辛的。。

考場上是想到了一個兩顆線段樹維護(一棵維護歸屬點,一棵維護權值)的辦法,但是貌似單次修改的複雜度是 \(nlog^2n\) 的,還不如暴力。。

最主要的是它的 3 操作還是有問題的。。(\(code\)

正解的做法就比較神仙了。

開一個 set 維護單個點的歸屬點,具體實現主要結合樹鏈剖分,維護每一條鏈最頂端的點 set 並且記錄該 set 裡的深度最小值,與目前的點的深度相比較。

時間複雜度大概是 \(log\;n\) ,這裡的 set 是按照深度由小到大排的序。

還有兩棵線段樹,一個維護黑色節點在它的所有管轄點應該下放的權值還有範圍以及權值和,另一個維護因為節點操作的緣故,而出現的比較雜碎的權值。

第二個的話,其實就是區間修改,區間查詢的線段樹,也可以用樹狀陣列來實現。

為了方便下文將會把第一顆線段樹稱為 T1 ,第二顆稱為 T2 。

實現的話分操作講一下吧。。

  • 1 操作除了需要查詢歸屬點應該下放的權值外也要計算一下 T2 裡面瑣碎的權值。

  • 2 操作直接在 T1 裡單點修改權值就好了。

  • 3 操作查詢 T1 子樹範圍內的管轄點應下放的權值與範圍的乘積,也要算上計算 T2 子樹範圍內的權值和,還有就是對於子樹根節點向下一部分的點,他們的管轄點可能是在子樹之外的,這裡也需要算進去。

  • 4 操作直接給 T1 區間修改就好了。

  • 5 操作就開始有一點噁心人了,先是查詢更改之前當前節點(計為 \(x\) )的歸屬點(計為 \(att\)),將現在應該加到 \(x\) 上的範圍減去,然後將範圍加入到 \(x\) 上,對於之前的下放權值繼承到 \(x\) 上。

  • 6操作與 5 操作有一些類似,同樣是範圍的加減繼承,同樣的把相對於 \(att\) 多的權值區間修改到 T2 上,這也就是維護兩顆線段樹的意義所在。

code

#include<bits/stdc++.h>
#define ui unsigned int
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
#define ls x<<1
#define rs x<<1|1
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=1e6+10,INF=1e9;
int n,m,minn[N];
int tot,head[N],nxt[N<<1],ver[N<<1];
int tim,dfn[N],siz[N],son[N],dep[N],fa[N],topp[N];
struct Sort{bool operator ()(int x,int y){return dep[x]<dep[y];}};
set<int,Sort> s[N];
struct Segment_Tree1
{
	struct Node
	{
		ui val,ran,dat;
	}tre[N<<2];
	void push_down(int x)
	{
		if(!tre[x].val)	return ;
		if(tre[ls].ran)
		{
			tre[ls].val+=tre[x].val;
			tre[ls].dat+=tre[x].val*tre[ls].ran;
		}
		if(tre[rs].ran)
		{
			tre[rs].val+=tre[x].val;
			tre[rs].dat+=tre[x].val*1tre[rs].ran;
		}
		tre[x].val=0;
	}
	void push_up(int x)
	{
		tre[x].ran=tre[ls].ran+tre[rs].ran;
		tre[x].dat=tre[ls].dat+tre[rs].dat;
	}
	ui query_val(int x,int l,int r,int pos)
	{
		if(l==r)	return tre[x].val;
		push_down(x);
		int mid=(l+r)>>1;	ui sum=0;
		if(pos<=mid)	sum=query_val(ls,l,mid,pos);
		else	sum=query_val(rs,mid+1,r,pos);
		push_up(x);
		return sum;
	}
	ui query_dat(int x,int l,int r,int L,int R)
	{
		if(L<=l&&r<=R)	return tre[x].dat;
		push_down(x);
		int mid=(l+r)>>1;	ui sum=0;
		if(L<=mid)	sum+=query_dat(ls,l,mid,L,R);
		if(R>mid)	sum+=query_dat(rs,mid+1,r,L,R);
		push_up(x);
		return sum;
	}
	int query_ran(int x,int l,int r,int L,int R)
	{
		if(L<=l&&r<=R)	return tre[x].ran;
		push_down(x);
		int mid=(l+r)>>1,sum=0;
		if(L<=mid)	sum+=query_ran(ls,l,mid,L,R);
		if(R>mid)	sum+=query_ran(rs,mid+1,r,L,R);
		push_up(x);
		return sum;
	}
	void insert_val(int x,int l,int r,int L,int R,ui num)
	{
		if(L<=l&&r<=R)
		{
			if(!tre[x].ran)	return;
			tre[x].val+=num;
			tre[x].dat+=num*tre[x].ran;
			return ;
		}
		push_down(x);
		int mid=(l+r)>>1;
		if(L<=mid)	insert_val(ls,l,mid,L,R,num);
		if(R>mid)	insert_val(rs,mid+1,r,L,R,num);
		push_up(x);
	}
	void insert_ran(int x,int l,int r,int pos,ui num)
	{
		if(l==r)
		{
			tre[x].ran+=num;
			if(!tre[x].ran)	tre[x].val=0;
			tre[x].dat=tre[x].val*tre[x].ran;
			return ;
		}
		push_down(x);
		int mid=(l+r)>>1;
		if(pos<=mid)	insert_ran(ls,l,mid,pos,num);
		else	insert_ran(rs,mid+1,r,pos,num);
		push_up(x);
	}
}t1;
struct Segment_Tree2
{
	struct Node
	{
		ui dat,laz;
	}tre[N<<1];
	void push_down(int x,int l,int r)
	{
		if(!tre[x].laz)	return ;
		int mid=(l+r)>>1;
		tre[ls].laz+=tre[x].laz;	tre[rs].laz+=tre[x].laz;
		tre[ls].dat+=(mid-l+1)*tre[x].laz;
		tre[rs].dat+=(r-mid)*tre[x].laz;
		tre[x].laz=0;
	}
	void push_up(int x)
	{
		tre[x].dat=tre[ls].dat+tre[rs].dat;
	}
	void insert(int x,int l,int r,int L,int R,ui num)
	{
		if(L<=l&&r<=R)
		{
			tre[x].dat+=num*(r-l+1);
			tre[x].laz+=num;
			return ;
		}
		push_down(x,l,r);
		int mid=(l+r)>>1;
		if(L<=mid)	insert(ls,l,mid,L,R,num);
		if(R>mid)	insert(rs,mid+1,r,L,R,num);
		push_up(x);
	}
	ui query(int x,int l,int r,int L,int R)
	{
		if(L<=l&&r<=R)	return tre[x].dat;
		push_down(x,l,r);
		int mid=(l+r)>>1;	ui sum=0;
		if(L<=mid)	sum+=query(ls,l,mid,L,R);
		if(R>mid)	sum+=query(rs,mid+1,r,L,R);
		push_up(x);
		return sum;
	}
}t2;
void add_edge(int x,int y)
{
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
void dfs1(int x)
{
	siz[x]=1;
	dfn[x]=++tim;
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		fa[to]=x;	dep[to]=dep[x]+1;
		dfs1(to);
		siz[x]+=siz[to];
		if(siz[to]>siz[son[x]])
			son[x]=to;
	}
}
void dfs2(int x,int tp)
{
	topp[x]=tp;
	if(son[x])	dfs2(son[x],tp);
	for(int i=head[x];i;i=nxt[i])
		if(ver[i]!=son[x])
			dfs2(ver[i],ver[i]);
}
int find_att(int x)
{
	while(x)
	{
		if(minn[topp[x]]<=dep[x])
		{
			auto pos=s[topp[x]].upper_bound(x);	pos--;
			return (*pos);
		}
		x=fa[topp[x]];
	}
}
void update_min(int x)
{
	minn[x]=(s[x].size()?dep[*(s[x].begin())]:INF);
}
signed main()
{
	n=read();	m=read();
	for(int i=2,x;i<=n;i++)
		x=read(),add_edge(x,i);
	dfs1(1);	dfs2(1,1);
	memset(minn,0x7f,sizeof(minn));
	s[1].insert(1);	update_min(1);
	t1.insert_ran(1,1,n,dfn[1],siz[1]);
	while(m--)
	{
		int opt,x,val,att;
		opt=read();	x=read();
		if(opt==2||opt==4)	val=read();
		if(opt==1||opt==3||opt==5)	att=find_att(x);
		if(opt==6)	att=find_att(fa[x]);
		if(opt==1)	printf("%u\n",t1.query_val(1,1,n,dfn[att])+t2.query(1,1,n,dfn[x],dfn[x]));
		if(opt==2)	t1.insert_val(1,1,n,dfn[x],dfn[x],val);
		if(opt==3)
		{
			int att=find_att(x);
			ui ran=siz[x]-t1.query_ran(1,1,n,dfn[x],dfn[x]+siz[x]-1);
			ui sum=t2.query(1,1,n,dfn[x],dfn[x]+siz[x]-1);
			sum+=t1.query_dat(1,1,n,dfn[x],dfn[x]+siz[x]-1);
			sum+=t1.query_val(1,1,n,dfn[att])*ran;
			printf("%u\n",sum);
		}
		if(opt==4)	t1.insert_val(1,1,n,dfn[x],dfn[x]+siz[x]-1,val);
		if(opt==5)
		{
			int att=find_att(x);
			ui ran=siz[x]-t1.query_ran(1,1,n,dfn[x],dfn[x]+siz[x]-1);
			ui val=t1.query_val(1,1,n,dfn[att]);
			t1.insert_ran(1,1,n,dfn[att],-ran);
			t1.insert_ran(1,1,n,dfn[x],ran);
			t1.insert_val(1,1,n,dfn[x],dfn[x],val);
			s[topp[x]].insert(x);	update_min(topp[x]);
		}
		if(opt==6)
		{
			int att=find_att(fa[x]);
			ui ran=t1.query_ran(1,1,n,dfn[x],dfn[x]);
			ui val=t1.query_val(1,1,n,dfn[x]);
			t1.insert_ran(1,1,n,dfn[att],ran);
			t1.insert_ran(1,1,n,dfn[x],-ran);
			val-=t1.query_val(1,1,n,dfn[att]);
			t2.insert(1,1,n,dfn[x],dfn[x]+siz[x]-1,val);
			t1.insert_val(1,1,n,dfn[x],dfn[x]+siz[x]-1,-val);
			s[topp[x]].erase(x);	update_min(topp[x]);
		}
	}
	return 0;
}

相關文章