做题记录 2

上一个写的太多了,卡爆了。所以再开一个。

P4321 随机漫游

一道综合多种算法的好题。

首先按照图上随机游走的套路,再依据 n 很小的限制,可以设出 dp 方程:设 fs,u 表示当前走过的点集为二进制数 s,当前在 u 点,再走完所有点的期望步数。那么显然有 f(1<<n)1,u=1

然后写一下转移柿子:

fs,u(u,v)E1degufsv,v+1

然后发现这是一个 2n×n 元的线性方程组。所以可以直接高斯消元。时间复杂度 O((2n×n)3)。但是显然爆了。

再考虑将转移方程改写一下,分 v 原本是否属于 s 进行讨论:

fs,u1degu((u,v)E,vsfs,v+(u,v)E,vsfsv,v)+1

整理得到:

1degu(u,v)E,vsfs,vfs,u=(1degu(u,v)E,vsfsv,v+1)

这样,只需要按照集合 s 大小倒序枚举,方程组大小自然降为 O(n)

时间复杂度 O(2nn3+Q)

AC code

P3723 [AH2017/HNOI2017] 礼物

首先发现加减相对于两个手环是对称的。因此可以把对一个手环的减法转化成对另一个手环的加法。这样可以假设全是在第一个手环上执行的加减操作。

第一个手环执行了加 c 的操作,且旋转过之后的序列为 [x1,x2xn],第二个手环为 [y1,y2yn]。计算差异值并化简,可以得到差异值是:

x2+y2+c2n+2c(xy)2xy

可以发现,这个序列只有最后一项是不定的。

因此将 y 序列翻转后再复制一倍,与 x 卷积,答案就是卷积后序列的 n+12n 项系数的 max

直接暴力枚举 c,加上前面依托就行了。

AC code

P3514 [POI2011] LIZ-Lollipop

回归简单题。

给定权值为 12 的序列,每次询问有没有权值等于 k 的子区间。

神仙思维题。

假设当前有一个序列权值和为 w。那么分下面情况讨论:

  • al=2[l+1,r] 的权值和为 w2

  • ar=2[l,r1] 的权值和为 w2

  • al=ar=1[l+1,r1] 的权值和为 w2

综上,总能根据一个权值为 w 的序列,构造出权值为 w2 的序列。

因此只需要知道序列中,权值为奇数的最大子区间和权值为偶数的最大子区间就可以了。

代码下周再补。

P8060 [POI2003] Sums

同余最短路典题。

CF1527E Partition Game

首先能够想到一个很典的 dp:记 fi,j 表示前 i 个元素划分成 j 段的最小代价。转移 O(n),时间 O(n2k)

考虑优化。首先看这个转移柿子:f(i,j)=min{f(k,j1)+cost(k+1,i)},这个东西看起来就很凸性。看了看题解确实也是这样的,但是我不会证明。

接下来考虑线段树优化。首先开一个线段树,把 costdp 值加起来放到线段树里。考虑每次转移都是加入一个新元素,并且对前面的 dp 值没有影响。接下来考虑这个新加入的元素对前面 cost 值的影响。可以发现,他只对 lastaji 的有影响。

所以只需要线段树支持区间加,区间取 min,单点插入就可以了。

CF1149C Tree Generator™

可以发现,两个点之间的距离,就是这两个点之间括号序列,消掉匹配的括号以后,剩下的没有匹配的括号。

因此问题转化为求最大权值子段,子段权值为消掉合法括号匹配之后的括号数。

这个东西可以线段树维护一下。由于最后括号序列一定形如 ))))...((((,可以记一下当前区间剩下的 ), ( 的数量分别是多少,以及区间最大权子段。转移的时候分类讨论一下即可。

CF242E XOR on Segment

这道题有许多做法,其中最简单的就是拆位建 20 棵线段树爆算

不赘述了。依据的是亦或的位独立性。O(nlognlogV)

P4046 快递服务

不妨设 fi,j,k 表示当前三个司机在 i,j,k 号公司,而且已经遍历到了 max(i,j,k) 号公司的 minimum cost。

转移是平凡的。可以做到 O(n3)n=1000。由于常数小 1s 完全不虚。

signed main() {
	read(n);
	rep(i, 1, n) rep(j, 1, n) read(d[i][j]);
	a[ ++ m] = 3, a[ ++ m] = 2, a[ ++ m] = 1;
	while (scanf("%lld", &a[ ++ m]) != EOF);
	memset(f, 0x3f, sizeof f); f[1][2][1] = 0;
	rep(i, 3, m) {
		rep(j, 2, i) rep(k, 1, j) f[(i & 1) ^ 1][j][k] = INF;
		rop(j, 2, i) rop(k, 1, j) {
			chkmin(f[(i & 1) ^ 1][j][k], f[i & 1][j][k] + d[a[i]][a[i + 1]]);
			chkmin(f[(i & 1) ^ 1][i][k], f[i & 1][j][k] + d[a[j]][a[i + 1]]);
			chkmin(f[(i & 1) ^ 1][i][j], f[i & 1][j][k] + d[a[k]][a[i + 1]]);
		}
	} rep(i, 1, m) rep(j, 1, m) ans = min(ans, f[m & 1][i][j]);
	printf("%lld\n", ans); return 0;
}

P4158 粉刷匠

发现木板的粉刷相对独立。若求出每个木板粉刷 j 次获得最大收益 wi,j,则容易进行分组背包。

现在考虑如何求 wi,j。不妨设 fi,j 表示粉刷了前 i 个格子,粉刷了 j 次获得的最大收益。枚举上一次粉刷的位置,可以得到转移

fi,jfk,j1+s0(ki)/s1(ki)

好像应该是 k+1。不管了,大概就这样。最后背包合并就行了。O(nm2t+nt2)

void calc(int n) {
	rep(i, 0, m) rep(j, 0, t) f[i][j] = 0;
	rep(i, 1, m) s1[i] = s1[i - 1] + (g[n][i] == '1');
	rep(i, 1, m) s0[i] = s0[i - 1] + (g[n][i] == '0');
	rep(i, 1, m) rep(j, 1, t) rop(k, 0, i) {
		f[i][j] = max(f[i][j], f[k][j - 1] + s1[i] - s1[k]);
		f[i][j] = max(f[i][j], f[k][j - 1] + s0[i] - s0[k]);
	} rep(i, 0, t) rep(j, 0, m) w[n][i] = max(w[n][i], f[j][i]);
}
signed main() {
	read(n, m, t);
	rep(i, 1, n) scanf("%s", g[i] + 1);
	rep(i, 1, n) calc(i); memset(f, 0, sizeof f);
	rep(i, 1, n) rep(j, 0, t) rep(k, 0, j)
		f[i][j] = max(f[i][j], f[i - 1][j - k] + w[i][k]);
	rep(i, 0, t) ans = max(ans, f[n][i]); printf("%lld\n", ans);
}

不妨让我们一起来考虑优化!可以发现,背包合并的物体体积是 O(m) 而不是 O(t) 级别的,因为最多染 m 次。

复杂度瓶颈变成了预处理。不妨改变一下状态,设 fi,j,k 表示染了前 i 个格子,用了 j 次,最后一个染的是红 / 蓝。这样就可以做到 O(nmt) 转移。故做到 O(nmt)

posted @   Link-Cut-Y  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示