帮助Bsny
题目描述
Bsny的书架乱成一团了,帮他一下吧!
他的书架上一共有n本书,我们定义混乱值是连续相同高度书本的段数。例如,如果书的高度是30,30,31,31,32,那么混乱值为3;30,32,32,31的混乱值也为3。但是31,32,31,32,31的混乱值为5,这实在是太乱了。
Bsny想尽可能减少混乱值,但他有点累了,所以他决定最多取出k本书,再随意将它们放回到书架上。你能帮助他吗?
输入
第一行两个整数n,k,分别表示书的数目和可以取出的书本数目。
接下来一行n个整数表示每本书的高度。
输出
仅一行一个整数,表示能够得到的最小混乱值。
样例输入
5 1
25 26 25 26 25
样例输出
3
提示
20%的数据:1≤n≤20,k=1。
40%的数据:书的高度不是25就是32,高度种类最多2种。
100%的数据:1≤k≤n≤100,注意所有书本高度在[25,32]。
来源
NOIP2014八校联考Test2 Day2
solution:
这道题是两年前出的,已经有很多题解了。正解是DP,比较复杂的状态压缩动态规划,这里就不多讲了。我将要讲另一种方法(纯属瞎搞),虽然很难AC,但平均可以得90分,在比赛里是很值的(这种方法不怎么用想,很容易实现,得分效率高,在不会做的时候是个不错的方法)。看到n比较小,但2^n枚举每本书是否取出肯定不行,不过还不算太离谱。想想曾经zhw学长教的一种方法——模拟退火法,在这题里似乎可行。在状态确定的情况下,可以轻松地在O(n)的时间里算出混乱度,然后直接套模拟退火法的模板就好了。可是,这种方法不常用,以至于我把退火的概率公式忘记了……然后随便编了一个,大概的趋势有那么一点像,但不靠谱。
这是我练习赛是的代码,公式乱造,只有55分(还可以了)
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<algorithm> 5 #include<cstdlib> 6 #include<cstring> 7 using namespace std; 8 typedef long long ll; 9 ll read(){ 10 ll ans=0; 11 char ch=getchar(),last=' '; 12 while(ch>'9'||ch<'0'){ 13 last=ch; 14 ch=getchar(); 15 } 16 while(ch<='9'&&ch>='0'){ 17 ans=ans*10+ch-'0'; 18 ch=getchar(); 19 } 20 if(last=='-') 21 ans=-ans; 22 return ans; 23 } 24 int n,m,a[200],b[100],d[100],x,y,ans,sum,last,pre,tot; 25 bool c[105]; 26 double T; 27 const double ze=1e-8; 28 int random(int x){ 29 unsigned int ans=x; 30 ans*=1234567891; 31 return ans; 32 } 33 int main(){ 34 //freopen("bb.in","r",stdin); 35 n=read(); 36 m=read(); 37 for(int i=1;i<=n;i++) 38 a[i]=read(),b[a[i]]++; 39 T=10000; 40 ans=n; 41 for(int i=1;i<=m;i++) 42 c[i]=true,b[a[i]]--,d[a[i]]++; 43 if(n==m){ 44 ans=0; 45 for(int i=25;i<=32;i++) 46 if(d[i]) 47 ans++; 48 printf("%d\n",ans); 49 return 0; 50 } 51 while(T>ze){ 52 //tot++; 53 x=rand()%n+1; 54 while(c[x]) 55 x=rand()%n+1; 56 y=rand()%n+1; 57 while(!c[y]) 58 y=rand()%n+1; 59 c[x]=true; 60 c[y]=false; 61 b[a[x]]--; 62 b[a[y]]++; 63 d[a[x]]++; 64 d[a[y]]--; 65 sum=0; 66 last=1; 67 while(c[last]) 68 last++; 69 if(last<=n){ 70 sum=1; 71 pre=last; 72 for(int i=last+1;i<=n;i++) 73 if(!c[i]&&a[pre]!=a[i]){ 74 sum++; 75 pre=i; 76 } 77 } 78 for(int i=25;i<=32;i++) 79 if(d[i]&&!b[i]) 80 sum++; 81 if(ans>sum) 82 ans=sum; 83 else{ 84 if(rand()%25+1>log(T+20)){ 85 c[x]=false; 86 c[y]=true; 87 b[a[x]]++; 88 b[a[y]]--; 89 d[a[x]]--; 90 d[a[y]]++; 91 } 92 } 93 T*=0.99998; 94 } 95 printf("%d\n",ans); 96 //printf("%d %d\n",ans,tot); 97 return 0; 98 }
赛后百度了一下正宗的模拟退火法,把代码修改一下,是这样的(90分,差不多了)
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<algorithm> 5 #include<cstdlib> 6 #include<cstring> 7 using namespace std; 8 typedef long long ll; 9 ll read(){ 10 ll ans=0; 11 char ch=getchar(),last=' '; 12 while(ch>'9'||ch<'0'){ 13 last=ch; 14 ch=getchar(); 15 } 16 while(ch<='9'&&ch>='0'){ 17 ans=ans*10+ch-'0'; 18 ch=getchar(); 19 } 20 if(last=='-') 21 ans=-ans; 22 return ans; 23 } 24 int n,m,a[200],b[100],d[100],x,y,ans,sum,last,pre,tot; 25 bool c[105]; 26 double T,de; 27 const double ze=1e-8; 28 int main(){ 29 //freopen("bb.in","r",stdin); 30 n=read(); 31 m=read(); 32 for(int i=1;i<=n;i++) 33 a[i]=read(),b[a[i]]++; 34 T=100000; 35 ans=n; 36 for(int i=1;i<=m;i++) 37 c[i]=true,b[a[i]]--,d[a[i]]++; 38 if(n==m){ 39 ans=0; 40 for(int i=25;i<=32;i++) 41 if(d[i]) 42 ans++; 43 printf("%d\n",ans); 44 return 0; 45 } 46 if(m==0){ 47 printf("%d\n",n); 48 return 0; 49 } 50 while(T>ze){ 51 //tot++; 52 x=rand()%n+1; 53 while(c[x]) 54 x=rand()%n+1; 55 y=rand()%n+1; 56 while(!c[y]) 57 y=rand()%n+1; 58 c[x]=true; 59 c[y]=false; 60 b[a[x]]--; 61 b[a[y]]++; 62 d[a[x]]++; 63 d[a[y]]--; 64 sum=0; 65 last=1; 66 while(c[last]) 67 last++; 68 if(last<=n){ 69 sum=1; 70 pre=last; 71 for(int i=last+1;i<=n;i++) 72 if(!c[i]&&a[pre]!=a[i]){ 73 sum++; 74 pre=i; 75 } 76 } 77 for(int i=25;i<=32;i++) 78 if(d[i]&&!b[i]) 79 sum++; 80 if(ans>sum) 81 ans=sum; 82 else{ 83 de=sum-ans; 84 if((1.0/exp(de/T))*100<=rand()%100+1){ 85 c[x]=false; 86 c[y]=true; 87 b[a[x]]++; 88 b[a[y]]--; 89 d[a[x]]--; 90 d[a[y]]++; 91 } 92 } 93 T*=0.99998; 94 } 95 printf("%d\n",ans); 96 //printf("%d %d\n",ans,tot); 97 return 0; 98 }
可惜这样得不了ac,然后ctime不能用,srand()也没办法,怎么办?手动改随机种子
这个95分
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<algorithm> 5 #include<cstdlib> 6 #include<cstring> 7 using namespace std; 8 typedef long long ll; 9 ll read(){ 10 ll ans=0; 11 char ch=getchar(),last=' '; 12 while(ch>'9'||ch<'0'){ 13 last=ch; 14 ch=getchar(); 15 } 16 while(ch<='9'&&ch>='0'){ 17 ans=ans*10+ch-'0'; 18 ch=getchar(); 19 } 20 if(last=='-') 21 ans=-ans; 22 return ans; 23 } 24 int n,m,a[200],b[100],d[100],x,y,ans,sum,last,pre,tot; 25 bool c[105]; 26 double T,de; 27 const double ze=1e-8; 28 int main(){ 29 //freopen("bb.in","r",stdin); 30 for(int i=1;i<=160;i++) 31 n=rand(); 32 n=read(); 33 m=read(); 34 for(int i=1;i<=n;i++) 35 a[i]=read(),b[a[i]]++; 36 T=1000000; 37 ans=n; 38 for(int i=1;i<=m;i++) 39 c[i]=true,b[a[i]]--,d[a[i]]++; 40 if(n==m){ 41 ans=0; 42 for(int i=25;i<=32;i++) 43 if(d[i]) 44 ans++; 45 printf("%d\n",ans); 46 return 0; 47 } 48 if(m==0){ 49 printf("%d\n",n); 50 return 0; 51 } 52 while(T>ze){ 53 //tot++; 54 x=rand()%n+1; 55 while(c[x]) 56 x=rand()%n+1; 57 y=rand()%n+1; 58 while(!c[y]) 59 y=rand()%n+1; 60 c[x]=true; 61 c[y]=false; 62 b[a[x]]--; 63 b[a[y]]++; 64 d[a[x]]++; 65 d[a[y]]--; 66 sum=0; 67 last=1; 68 while(c[last]) 69 last++; 70 if(last<=n){ 71 sum=1; 72 pre=last; 73 for(int i=last+1;i<=n;i++) 74 if(!c[i]&&a[pre]!=a[i]){ 75 sum++; 76 pre=i; 77 } 78 } 79 for(int i=25;i<=32;i++) 80 if(d[i]&&!b[i]) 81 sum++; 82 if(ans>sum) 83 ans=sum; 84 else{ 85 de=sum-ans; 86 if((1.0/exp(de/T))*100<=rand()%100+1){ 87 c[x]=false; 88 c[y]=true; 89 b[a[x]]++; 90 b[a[y]]--; 91 d[a[x]]--; 92 d[a[y]]++; 93 } 94 } 95 T*=0.99998; 96 } 97 printf("%d\n",ans); 98 //printf("%d %d\n",ans,tot); 99 //printf("%.6lf\n",exp(2)); 100 return 0; 101 }
难道到极限了吗?当然没有,一定可以ac的,我调了半天一直WA,同学看了一下,只改了一个字符,就ac了(强)。
下面是ac的代码
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<algorithm> 5 #include<cstdlib> 6 #include<cstring> 7 using namespace std; 8 typedef long long ll; 9 ll read(){ 10 ll ans=0; 11 char ch=getchar(),last=' '; 12 while(ch>'9'||ch<'0'){ 13 last=ch; 14 ch=getchar(); 15 } 16 while(ch<='9'&&ch>='0'){ 17 ans=ans*10+ch-'0'; 18 ch=getchar(); 19 } 20 if(last=='-') 21 ans=-ans; 22 return ans; 23 } 24 int n,m,a[200],b[100],d[100],x,y,ans,sum,last,pre,tot; 25 bool c[105]; 26 double T,de; 27 const double ze=1e-8; 28 int main(){ 29 //freopen("bb.in","r",stdin); 30 for(int i=1;i<=160;i++) 31 n=rand(); 32 n=read(); 33 m=read(); 34 for(int i=1;i<=n;i++) 35 a[i]=read(),b[a[i]]++; 36 T=2000000; 37 ans=n; 38 for(int i=1;i<=m;i++) 39 c[i]=true,b[a[i]]--,d[a[i]]++; 40 if(n==m){ 41 ans=0; 42 for(int i=25;i<=32;i++) 43 if(d[i]) 44 ans++; 45 printf("%d\n",ans); 46 return 0; 47 } 48 if(m==0){ 49 printf("%d\n",n); 50 return 0; 51 } 52 while(T>ze){ 53 tot++; 54 x=rand()%n+1; 55 while(c[x]) 56 x=rand()%n+1; 57 y=rand()%n+1; 58 while(!c[y]) 59 y=rand()%n+1; 60 c[x]=true; 61 c[y]=false; 62 b[a[x]]--; 63 b[a[y]]++; 64 d[a[x]]++; 65 d[a[y]]--; 66 sum=0; 67 last=1; 68 while(c[last]) 69 last++; 70 if(last<=n){ 71 sum=1; 72 pre=last; 73 for(int i=last+1;i<=n;i++) 74 if(!c[i]&&a[pre]!=a[i]){ 75 sum++; 76 pre=i; 77 } 78 } 79 for(int i=25;i<=32;i++) 80 if(d[i]&&!b[i]) 81 sum++; 82 if(ans>sum) 83 ans=sum; 84 else{ 85 de=sum-ans; 86 if((1.0/exp(de/T))*100<=(double)(rand()%100+1)){ 87 c[x]=false; 88 c[y]=true; 89 b[a[x]]++; 90 b[a[y]]--; 91 d[a[x]]--; 92 d[a[y]]++; 93 } 94 } 95 T*=0.999978; 96 } 97 printf("%d\n",ans); 98 //printf("%d %d\n",ans,tot); 99 return 0; 100 }
调随机种子是很傻的做法(比赛时不可能完成),调参数才更有效。
同样AC,不要调随机种子,在退火概率中,还有一个系数是可以改的。
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<algorithm> 5 #include<cstdlib> 6 #include<cstring> 7 using namespace std; 8 typedef long long ll; 9 ll read(){ 10 ll ans=0; 11 char ch=getchar(),last=' '; 12 while(ch>'9'||ch<'0'){ 13 last=ch; 14 ch=getchar(); 15 } 16 while(ch<='9'&&ch>='0'){ 17 ans=ans*10+ch-'0'; 18 ch=getchar(); 19 } 20 if(last=='-') 21 ans=-ans; 22 return ans; 23 } 24 int n,m,a[200],b[100],d[100],x,y,ans,sum,last,pre,tot; 25 bool c[105]; 26 double T,de; 27 const double ze=1e-8; 28 int main(){ 29 n=read(); 30 m=read(); 31 for(int i=1;i<=n;i++) 32 a[i]=read(),b[a[i]]++; 33 T=2000000; 34 ans=n; 35 for(int i=1;i<=m;i++) 36 c[i]=true,b[a[i]]--,d[a[i]]++; 37 if(n==m){ 38 ans=0; 39 for(int i=25;i<=32;i++) 40 if(d[i]) 41 ans++; 42 printf("%d\n",ans); 43 return 0; 44 } 45 if(m==0){ 46 printf("%d\n",n); 47 return 0; 48 } 49 while(T>ze){ 50 tot++; 51 x=rand()%n+1; 52 while(c[x]) 53 x=rand()%n+1; 54 y=rand()%n+1; 55 while(!c[y]) 56 y=rand()%n+1; 57 c[x]=true; 58 c[y]=false; 59 b[a[x]]--; 60 b[a[y]]++; 61 d[a[x]]++; 62 d[a[y]]--; 63 sum=0; 64 last=1; 65 while(c[last]) 66 last++; 67 if(last<=n){ 68 sum=1; 69 pre=last; 70 for(int i=last+1;i<=n;i++) 71 if(!c[i]&&a[pre]!=a[i]){ 72 sum++; 73 pre=i; 74 } 75 } 76 for(int i=25;i<=32;i++) 77 if(d[i]&&!b[i]) 78 sum++; 79 if(ans>sum) 80 ans=sum; 81 else{ 82 de=sum-ans; 83 if((1.0/(exp(de/(T*1.2))))*100<=(double)(rand()%100+1)){ 84 c[x]=false; 85 c[y]=true; 86 b[a[x]]++; 87 b[a[y]]--; 88 d[a[x]]--; 89 d[a[y]]++; 90 } 91 } 92 T*=0.999978; 93 } 94 printf("%d\n",ans); 95 //printf("%d %d\n",ans,tot); 96 return 0; 97 }
于是,这种费正解也把这道题ac了。如果书的高度种类有很多的话,这种方法可以比正解更好用,难道不是吗?
本片文章纯属乱搞,神犇不要喷……