[冲刺国赛2022] 模拟赛10

题目描述

有一棵 \(n\) 个点的树,边有边权。有 \(m\) 个关键点 \(a_1,a_2...a_m\)(这些关键点可重),你需要选择若干个中转站 \(p_1,p_2...p_k\),选择中转站的代价是 \(k\cdot C\),每个关键点的代价是到最近中转站的距离,最小化代价和。

\(n\leq 3000\)

解法

直接记录到中转站的距离不好做,我们不妨直接记录到管辖某个点的中转站的距离。

\(dp[u][i]\) 表示管辖点 \(u\) 的中转站是 \(i\),子树 \(u\) 内的最小代价和。转移考虑加入一个子树 \(v\),考虑管辖 \(u,v\) 的中转站要么相同,要么在 \(v\) 的子树内,我们在第二种情况加上 \(C\) 的权值即可:

\[dp[u][i]\leftarrow^{+} \min\{dp[v][i],\min dp[v][j]+C\} \]

为了方便转移我们其实放宽了限制,不合法的转移一定是不优的。

初始化 \(dp[u][i]=dis(u,i)\),最后的答案是 \(\min dp[1][i]+C\),时间复杂度 \(O(n^2)\)

其中这题就是 Red and Black Tree 的超级弱化版,不知道我为什么做不出来。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 3005;
#define int long long
const int inf = 1e18;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,C,ans,tot,f[M],a[M],dp[M][M];
struct edge{int v,c,next;}e[M<<1];
void get(int rt,int u,int fa)
{
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v,c=e[i].c;
		if(v==fa) continue;
		dp[rt][v]=dp[rt][u]+c;
		get(rt,v,u);
	}
}
void dfs(int u,int fa)
{
	for(int i=1;i<=n;i++) dp[u][i]*=a[u];
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v,c=e[i].c;
		if(v==fa) continue;
		dfs(v,u);
		int mi=inf;
		for(int j=1;j<=n;j++)
			mi=min(mi,dp[v][j]);
		for(int j=1;j<=n;j++)
			dp[u][j]+=min(dp[v][j],C+mi);
	}
}
signed main()
{
	freopen("post.in","r",stdin);
	freopen("post.out","w",stdout);
	n=read();m=read();C=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read(),c=read();
		e[++tot]=edge{v,c,f[u]},f[u]=tot;
		e[++tot]=edge{u,c,f[v]},f[v]=tot;
	}
	for(int i=1;i<=m;i++) a[read()]++;
	for(int i=1;i<=n;i++) get(i,i,0);
	dfs(1,0);ans=inf;
	for(int i=1;i<=n;i++)
		ans=min(ans,dp[1][i]+C);
	printf("%lld\n",ans);
}

题目描述

解法

有这样的性质:当我们把 \(solve(n)\) 推到 \(solve(n+1)\) 时,增量是 \(w(0)+w(\max(n)-\sec(n))\),其中 \(\max(n)\) 表示 \(n\) 二进制位的最高位,\(\sec(n)\) 表示 \(n\) 二进制的次高位,特别地,如果没有次高位那么 \(\sec(n)=-1\)

\(f=\max(n),g=\sec(n)\),考虑增量法,首先选取 \(2^f\) 为起点,直接计算它的值:

\[solve(2^f)=\sum_{i=0}^f 2^i(2^{f-i}\cdot(f-i+1))=2^f\cdot\frac{(f+1)(f+2)}{2} \]

\(2^f\) 增量到 \(2^f+1\) 需要特殊考虑,设 \(dt(x)=w(x)+w(0)\),那么增量是 \(dt(f+1)\);然后从 \(2^f+1\) 增量到 \(2^f+2^g\),我们枚举二进制的次高位:

\[w(0)(2^g-1)+\sum_{i=0}^{g-1} 2^i(2^{f-i}\cdot(f-i+1))=2^g-1+2^f\cdot\frac{g(2f-g+3)}{2} \]

最后考虑从 \(2^f+2^g\) 增量到 \(n\),由于最高位和次高位已经确定了,此时可以直接计算:

\[(n-2^f-2^g)\cdot dt(f-g) \]

那么线段树就只需要支持区间查询二进制数,寻找区间的最后一个 \(1\)(直接线段树上二分),这是极其易于维护的,时间复杂度 \(O(n\log n)\)

#include <cstdio>
const int M = 100005;
const int N = M<<2;
const int MOD = 998244353;
const int inv2 = (MOD+1)/2;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,a[M],pw[M],inv[M],s[N],fl[N],cov[N],all[N];
void build(int i,int l,int r)
{
	cov[i]=-1;
	if(l==r)
	{
		all[i]=pw[l];
		s[i]=(a[l]?pw[l]:0);
		return ;
	}
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	s[i]=(s[i<<1]+s[i<<1|1])%MOD;
	all[i]=(all[i<<1]+all[i<<1|1])%MOD;
}
void cover(int i,int c)
{
	cov[i]=c;fl[i]=0;
	if(c==0) s[i]=0;
	else s[i]=all[i];
}
void flip(int i)
{
	s[i]=(all[i]-s[i]+MOD)%MOD;fl[i]^=1;
}
void down(int i)
{
	if(cov[i]!=-1)
		cover(i<<1,cov[i]),
		cover(i<<1|1,cov[i]),cov[i]=-1;
	if(fl[i])
		flip(i<<1),flip(i<<1|1),fl[i]=0;
}
void work(int i,int l,int r,int L,int R,int f)
{
	if(L>r || l>R) return ;
	if(L<=l && r<=R)
	{
		if(f==1) flip(i);
		if(f==2) cover(i,0);
		if(f==3) cover(i,1);
		return ;
	}
	int mid=(l+r)>>1;down(i);
	work(i<<1,l,mid,L,R,f);
	work(i<<1|1,mid+1,r,L,R,f);
	s[i]=(s[i<<1]+s[i<<1|1])%MOD;
}
int find(int i,int l,int r,int L,int R)
{
	if(L>r || l>R || !s[i]) return 0;
	if(l==r) return l;
	int mid=(l+r)>>1,t=0;down(i);
	if(t=find(i<<1|1,mid+1,r,L,R)) return t;
	return find(i<<1,l,mid,L,R);
}
int ask(int i,int l,int r,int L,int R)
{
	if(L>r || l>R) return 0;
	if(L<=l && r<=R) return s[i];
	int mid=(l+r)>>1;down(i);
	return ask(i<<1,l,mid,L,R)+
	ask(i<<1|1,mid+1,r,L,R);
}
void add(int &x,int y) {x=(x+y)%MOD;}
int dt(int x) {return ((x+1)*pw[x]%MOD+1)%MOD;}
int ask(int l,int r)
{
	int f=find(1,1,n,l,r),ans=0;
	if(!f) return 0;
	int g=find(1,1,n,l,f-1);f-=l;
	if(!g) return (f+1)*(f+2)%MOD*inv2%MOD*pw[f]%MOD;
	g-=l;
	ans=(f+1)*(f+2)%MOD*inv2%MOD*pw[f]%MOD;
	add(ans,dt(f+1));
	add(ans,(2*f-g+3)*g%MOD*inv2%MOD*pw[f]);
	add(ans,pw[g]-1);
	add(ans,dt(f-g)*ask(1,1,n,l,g+l-1)%MOD*inv[l]);
	return ans;
}
signed main()
{
	freopen("run.in","r",stdin);
	freopen("run.out","w",stdout);
	n=read();m=read();pw[0]=inv[0]=1;
	for(int i=1;i<=n;i++)
	{
		scanf("%1d",&a[i]);
		pw[i]=pw[i-1]*2%MOD;
		inv[i]=inv[i-1]*inv2%MOD;
	}
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		int op=read(),l=read(),r=read();
		if(op<=3) work(1,1,n,l,r,op);
		else printf("%lld\n",ask(l,r));
	}
}
posted @ 2022-06-23 15:28  C202044zxy  阅读(144)  评论(0编辑  收藏  举报