• [冲刺国赛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));
    	}
    }
    
  • 相关阅读:
    oracle数据库迁移---windows环境下
    Tomcat内存溢出(java.lang.OutOfMemoryError: PermGen space)
    64位win系统上面tomcat6启动不了 window不能再本地计算机启动
    linux学习之一些琐碎知识点
    mysql备份与还原
    linux学习中遇到的各种故障与解决方法
    Jmeter调试工具---Debug Sampler
    python+requests接口自动化测试框架实例详解教程
    互联网架构的演变
    面试时如何考察应聘者的素质?
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16405286.html
Copyright © 2020-2023  润新知