【洛谷4749】[CERC2017] Kitchen Knobs(差分+背包)

点此看题面

  • (n)个旋钮,每个旋钮上有(7)个数码,一个旋钮的读数方式是从顶端开始顺时针读一圈。
  • 一次操作可以将一段区间内所有旋钮一起向同一方向转动任意位数。
  • 求最少的操作次数,使得所有旋钮的读数都成为可能的最大值。
  • (nle501)

巧妙的差分

如果一个旋钮上的数全部相同,那么无论怎么转都取到最大值,可以直接删去。

否则,由于(7)是一个质数,不存在(border),因此可能的最大值只会在唯一的位置取到,不妨设为(a_i)

那么现在我们就是要对(a)序列进行若干次区间加法(在模(7)意义下)使得所有位置变成(0)

考虑差分,那么区间加法就可以看作给一个位置加上(x),另一个位置减去(x)

因此我们先对(a)序列进行一次反差分(从后往前令每个(a_{i+1})减去(a_i)),然后问题就变成每次操作可以给任意两个位置分别加/减同一个数,求最少的操作次数。

背包

如果能选出(x)个位置在模(7)意义下和为(0),我们就可以通过(x-1)次操作让这(x)个位置全部变成(0)

又由于原序列的总和肯定为(0)(注意到差分序列包括第(n+1)位),因此我们的问题就在于把这(n+1)个位置划分成尽量多的集合,使得每个集合的总和为(0)

首先,贪心地把每两个和为(0)的数合并掉,这显然不会使答案变劣。而这样一来一大好处就是剩余的数最多只有(3)种。

所以就可以设(f_{i,j,k})表示这三种数的个数分别剩余(i,j,k)时仍需的最小代价,每次取一个总和为(0)的集合转移(相当于是一个三维背包)。

我们肯定有很多取法使得一个集合({a,b,c})总和为(0),但也肯定有很多集合是不优的,因此需要剪枝:

  • (a,b,c<7)(除非一个恰好等于(7),另外两个都为(0)),因为(7)个相同的数完全可以自成一个集合,分开来肯定更优。
  • (gcd(a,b,c)=1),因为如果(gcd(a,b,c)=d>1),这个集合可以拆分成(d)个,也肯定更优。

这样一搞之后合法的集合个数实际上已经很少了,然后我们求解(f)的时候只要使用记忆化搜索,绝对跑不满了。

代码:(O(n^3))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 501
using namespace std;
int n,a[N+5],w[7];char s[10];I int gcd(CI x,CI y) {return y?gcd(y,x%y):x;}
int ct,p[50][3],g[50],v[3];short f[N+5][N+5][N+5];I int DFS(CI x,CI y,CI z)//记忆化搜索
{
	if(!x&&!y&&!z) return 0;if(f[x][y][z]) return f[x][y][z];RI t=n;//x=y=z=0;记忆化
	for(RI i=1;i<=ct;++i) x>=p[i][0]&&y>=p[i][1]&&z>=p[i][2]&&(t=min(t,DFS(x-p[i][0],y-p[i][1],z-p[i][2])+g[i]));//枚举取法
	return f[x][y][z]=t;
}
int main()
{
	RI i,j,k;for(scanf("%d",&n),i=1;i<=n;++i)
	{
		for(scanf("%s",s),j=1;j^7&&s[j]==s[j-1];++j);if(j==7) {--i,--n;continue;}//全部相同直接扔掉
		for(j=1;j^7;s[(a[i]+k)%7]<s[(j+k)%7]&&(a[i]=j),++j) for(k=0;s[(a[i]+k)%7]==s[(j+k)%7];++k);//求出最大表示开始位置
	}
	for(i=n;i;--i) ++w[a[i+1]=(a[i+1]-a[i]+7)%7];++w[a[1]];//反差分
	RI t=0,o;for(w[0]=0,i=1;i<=3;++i) o=min(w[i],w[7-i]),t+=o,w[i]-=o,w[7-i]-=o;//两个和为0的数直接对掉
	RI c=0;for(i=1;i^7;++i) w[i]&&(v[c++]=i);//记下剩余的所有数
	for(i=0;i^7&&(!i||v[0]);++i) for(j=0;j^7&&(!j||v[1]);++j) for(k=0;k^7&&(!k||v[2]);++k) (i||j||k)&&//枚举集合取法,i,j,k<7
		!((i*v[0]+j*v[1]+k*v[2])%7)&&gcd(i,gcd(j,k))==1&&(p[++ct][0]=i,p[ct][1]=j,p[ct][2]=k,g[ct]=i+j+k-1);//和为0且gcd=1
	for(i=0;i^3;++i) p[++ct][i]=7,g[ct]=6;return printf("%d
",t+DFS(w[v[0]],w[v[1]],w[v[2]])),0;//一个恰好为7,另两个为0;两个数对掉的答案+记搜答案
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4749.html