【洛谷4749】[CERC2017] Kitchen Knobs(差分+背包)
作者:互联网
- 有\(n\)个旋钮,每个旋钮上有\(7\)个数码,一个旋钮的读数方式是从顶端开始顺时针读一圈。
- 一次操作可以将一段区间内所有旋钮一起向同一方向转动任意位数。
- 求最少的操作次数,使得所有旋钮的读数都成为可能的最大值。
- \(n\le501\)
巧妙的差分
如果一个旋钮上的数全部相同,那么无论怎么转都取到最大值,可以直接删去。
否则,由于\(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\n",t+DFS(w[v[0]],w[v[1]],w[v[2]])),0;//一个恰好为7,另两个为0;两个数对掉的答案+记搜答案
}
标签:4749,洛谷,int,CI,旋钮,&&,CERC2017,集合,define 来源: https://www.cnblogs.com/chenxiaoran666/p/Luogu4749.html