zoukankan      html  css  js  c++  java
  • 【洛谷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;两个数对掉的答案+记搜答案
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    实验一 开发环境的熟悉(小组)
    第六章家庭作业
    Linux常用命令-1
    Linux简介
    Python for写死循环?
    python将某个列表按元素值分成多个子列表
    xshell 5连接NAT模式的虚拟机
    python中remove的一些坑
    Sender IP字段为"0.0.0.0"的ARP请求报文
    免费ARP
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4749.html
Copyright © 2011-2022 走看看