20220623

  • rk 24/48, 15+20+50=85
  • max: 100, 80, 100, 100+20+100=220

代价是相加形式,可以拆到每条边上计算。对于一棵子树,可能是外部的关键点到子树中中转站,也可能是子树中关键点到外部中转站,但不会同时出现这两种情况。设 \(f[u,i]\) 为子树 \(u\) 中有 \(i\) 个关键点与子树外中转站匹配的最小代价(已经统计上了这些点到 \(u\) 的代价和),\(g[u,i]\) 为子树中距 \(u\) 最近的中转站为 \(i\) 的最小代价

\(f\) 的转移就是树上背包,\(g\) 可以对每个当前子树中的 \(j\) 枚举前面的未匹配点数,对前面子树的 \(j\) 枚举当前的未匹配点数

每一对关键点与关键点、关键点与树点会在 lca 处枚举一次,时间复杂度 \(O(n^{2})\)

code
const int N = 3e3+5;
const LL infl = 0x3f3f3f3f3f3f3f3f;
int n,m,c,siz[N];
LL dis[N],f[N][N],g[N][N],h[N];
Vi st[N];
vector<Pii> e[N];

#define v ei.fi
#define w ei.se
void dfs(int u,int fa) {
	f[u][0] = g[u][u] = c, f[u][siz[u]] = 0, st[u].pb(u);
	for(auto &ei : e[u]) if( v != fa ) {
		dis[v] = dis[u]+w, dfs(v,u);
		copy(f[u],f[u]+siz[u]+1,h), mem(f[u],0x3f,siz[u]);
		for(int i : st[u]) {
			LL res = infl;
			For(j,0,siz[v]) ckmin(res,g[u][i]+f[v][j]+(dis[i]-dis[u]+w)*j);
			g[u][i] = res, ckmin(f[u][0],res);
		}
		for(int i : st[v]) {
			LL res = infl;
			For(j,0,siz[u]) ckmin(res,g[v][i]+h[j]+(dis[i]-dis[u])*j);
			g[u][i] = res, ckmin(f[u][0],res);
		}
		For(i,0,siz[u]) For(j,0,siz[v]) ckmin(f[u][i+j],h[i]+f[v][j]+(LL)w*j);
		siz[u] += siz[v], st[u].insert(st[u].end(),all(st[v]));
	}
//	cerr<<u<<":\n";
//	For(i,0,siz[u]) cerr<<f[u][i]<<' '; cerr<<endl;
//	for(int i : st[u]) cerr<<i<<' '<<g[u][i]<<endl;
}

signed main() { freopen("post.in","r",stdin); freopen("post.out","w",stdout);
	memset(f,0x3f,sizeof f), memset(g,0x3f,sizeof g);
	io>>n>>m>>c; Rep(i,1,n, x,y,z) io>>x>>y>>z, e[x].pb(y,z), e[y].pb(x,z);
	For(i,1,m) ++siz[read()];
	dfs(1,0);
	io<<f[1][0];
	return 0;
}

赛时做法:先写出 \(O(n^{2})\) 暴力:

// x 表示进位。省略了记忆化
mint slv(int l,int r,int x) {
	if( l == r ) return a[l]+x==1 ? 1 : 6;
	int i = r-l + max(x,*max_element(a+l,a+r));
	return (i+1)*Pow(2,i) + slv(l+1,r,a[l]) + slv(l+1,r,x);
}

\(r\) 调整到左侧第一个为 \(1\) 的位置。考虑拆开与 \(x\)\(\max\)。找到 \(r\) 左侧第一个为 \(1\) 的位置 \(p\),那么 \(l\le p\)*max_element(a+l,a+r) = 1\(l>p\) 时剩余的数位一定形如 \(000\cdots001\),可以 \(O(n)\) 计算。可以把代码改成 \(l>p\) 时返回 \(f[x,r-p+1]\)\(f\) 表示预处理出的上述子问题的答案)

把贡献拆成 (i+1)*Pow(2,i)slv(l+1,r,a[l]) + slv(l+1,r,x),前者是等差乘等比数列求和,后者本质是要计算 \(f[0/1]\) 的贡献系数之和,\(i\) 位置对系数的贡献是 \(2^{i-l}\),对 \(0,1\) 分别求和即可

上述信息在带修的情况下可以用线段树轻松维护,时间复杂度 \(O(n\log n)\)

code
const int N = 1e5+5;
const mint iv2 = mint(2).pow();
int n,m;
mint pw[N],ipw[N],spw[N];

#define ls (u<<1)
#define rs (u<<1|1)
#define mid (l+r>>1)
#define root int u=1,int l=1,int r=n
#define lson ls,l,mid
#define rson rs,mid+1,r
struct {
	mint res0,res1;
	struct { int len,cov,cnt; mint sum,s[2]; bool rev; } t[N*4];
	void up(int u) {
		t[u].cnt = t[ls].cnt + t[rs].cnt,
		t[u].s[0] = t[ls].s[0] + t[rs].s[0], t[u].s[1] = t[ls].s[1] + t[rs].s[1];
	}
	void dcov(int u,bool x) {
		t[u].cnt = x*t[u].len, t[u].s[x] = t[u].sum, t[u].s[!x] = 0,
		t[u].cov = x, t[u].rev = 0;
	}
	void drev(int u) {
		t[u].cnt = t[u].len-t[u].cnt, swap(t[u].s[0],t[u].s[1]);
		if( ~t[u].cov ) t[u].cov ^= 1;
		else t[u].rev ^= 1;
	}
	void down(int u) {
		if( ~t[u].cov ) dcov(ls,t[u].cov), dcov(rs,t[u].cov), t[u].cov = -1;
		else if( t[u].rev ) drev(ls), drev(rs), t[u].rev = 0;
	}
	void bld(root) {
		t[u].len = r-l+1, t[u].sum = spw[r]-spw[l-1], t[u].cov = -1;
		if( l == r ) return dcov(u,read<char>()-'0');
		bld(lson), bld(rson), up(u);
	}
	void cov(int ql,int qr,bool x,root) {
		if( qr < l || r < ql ) return;
		if( ql <= l && r <= qr ) return dcov(u,x);
		down(u), cov(ql,qr,x,lson), cov(ql,qr,x,rson), up(u);
	}
	void rev(int ql,int qr,root) {
		if( qr < l || r < ql ) return;
		if( ql <= l && r <= qr ) return drev(u);
		down(u), rev(ql,qr,lson), rev(ql,qr,rson), up(u);
	}
	int bs(int qr,root) {
		if( qr < l || !t[u].cnt ) return 0;
		if( l == r ) return t[u].cnt ? l : 0;
		down(u);
		if( int res = bs(qr,rson) ) return res;
		return bs(qr,lson);
	}
	void qry(int ql,int qr,root) {
		if( qr < l || r < ql ) return;
		if( ql <= l && r <= qr ) return res0 += t[u].s[0], res1 += t[u].s[1], void();
		down(u), qry(ql,qr,lson), qry(ql,qr,rson);
	}
} seg;

auto w=[](int x) { return (x+1)*pw[x]; };
mint slv(bool x,int r) {
	static bool vis[2][N]; static mint f[2][N];
	if( r == 1 ) return x ? 6 : 1;
	if( vis[x][r] ) return f[x][r];
	return vis[x][r] = 1, f[x][r] = w(r-1+x) + slv(x,r-1) + slv(0,r-1);
}

auto sid=[](int l,int r) { return iv2*(l+r)*(r-l+1); };
mint qry(int l,int r) {
	if( l > (r=seg.bs(r)) ) return 0;
	int ll = seg.bs(r-1);
	if( l > ll ) return slv(0,r-l+1);
	seg.res0 = seg.res1 = 0, seg.qry(l,ll);
	return pw[r-l+1]*sid(r-ll+2,r-l+2) + slv(0,r-ll) +
		   ipw[l]*(seg.res0*slv(0,r-ll)+seg.res1*slv(1,r-ll));
}

signed main() {
#ifdef FS
	freopen("run7.in","r",stdin); freopen("a.out","w",stdout);
#else
	freopen("run.in","r",stdin); freopen("run.out","w",stdout);
#endif
	ios::sync_with_stdio(0);cout.tie(0);
	pw[0] = ipw[0] = spw[0] = 1;
	Rep(i,1,N) pw[i] = pw[i-1] * 2, ipw[i] = ipw[i-1] * iv2, spw[i] = spw[i-1]+pw[i];
	io>>n>>m, seg.bld();
	while( m-- ) {
		int op,l,r; io>>op>>l>>r;
		if( op == 1 ) seg.rev(l,r);
		else if( op == 2 ) seg.cov(l,r,0);
		else if( op == 3 ) seg.cov(l,r,1);
		else cout<<qry(l,r).x<<endl;
	}
	return 0;
}

std 做法更泛用。记 \(ans[i] = solve(1,n)\)。考虑从右往左推,仍然以 \(p\) 位置分开计算,需要维护 \(ans[i],ans[i+1]\)(其中 \(i\) 是当前位置到 \(r\) 形成的二进制数,\(i+1\) 是上一位进位后的数)。

根据上面的暴力不难递推到 \(ans[2i],ans[2i+1]\),这个过程可以用矩乘表示。以 \(>p\) 的位置为例:

\[\begin{bmatrix} ans[i] & ans[i+1] & 2^{j} & (j+1)2^{j} \end{bmatrix} \begin{bmatrix} 2 & 1 & & \\ & 1 & & \\ & 2 & 2 & 2\\ 1 & 2 & & 2 \end{bmatrix} =\begin{bmatrix} ans[2i] & ans[2i+1] & 2^{j+1} & (j+2)2^{j+1} \end{bmatrix} \]

\(<p\) 的位置类似,对 \(0/1\) 分别转移即可。使用线段树维护区间矩阵乘积,时间复杂度 \(O(4^{3}n\log n)\),可能需要卡常

posted @ 2022-06-25 07:53  401rk8  阅读(41)  评论(0编辑  收藏  举报