帮助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分(还可以了)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<cstring>
using namespace std;
typedef long long ll;
ll read(){
ll ans=0;
char ch=getchar(),last=' ';
while(ch>'9'||ch<'0'){
last=ch;
ch=getchar();
}
while(ch<='9'&&ch>='0'){
ans=ans*10+ch-'0';
ch=getchar();
}
if(last=='-')
ans=-ans;
return ans;
}
int n,m,a[200],b[100],d[100],x,y,ans,sum,last,pre,tot;
bool c[105];
double T;
const double ze=1e-8;
int random(int x){
unsigned int ans=x;
ans*=1234567891;
return ans;
}
int main(){
//freopen("bb.in","r",stdin);
n=read();
m=read();
for(int i=1;i<=n;i++)
a[i]=read(),b[a[i]]++;
T=10000;
ans=n;
for(int i=1;i<=m;i++)
c[i]=true,b[a[i]]--,d[a[i]]++;
if(n==m){
ans=0;
for(int i=25;i<=32;i++)
if(d[i])
ans++;
printf("%d\n",ans);
return 0;
}
while(T>ze){
//tot++;
x=rand()%n+1;
while(c[x])
x=rand()%n+1;
y=rand()%n+1;
while(!c[y])
y=rand()%n+1;
c[x]=true;
c[y]=false;
b[a[x]]--;
b[a[y]]++;
d[a[x]]++;
d[a[y]]--;
sum=0;
last=1;
while(c[last])
last++;
if(last<=n){
sum=1;
pre=last;
for(int i=last+1;i<=n;i++)
if(!c[i]&&a[pre]!=a[i]){
sum++;
pre=i;
}
}
for(int i=25;i<=32;i++)
if(d[i]&&!b[i])
sum++;
if(ans>sum)
ans=sum;
else{
if(rand()%25+1>log(T+20)){
c[x]=false;
c[y]=true;
b[a[x]]++;
b[a[y]]--;
d[a[x]]--;
d[a[y]]++;
}
}
T*=0.99998;
}
printf("%d\n",ans);
//printf("%d %d\n",ans,tot);
return 0;
}
赛后百度了一下正宗的模拟退火法,把代码修改一下,是这样的(90分,差不多了)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<cstring>
using namespace std;
typedef long long ll;
ll read(){
ll ans=0;
char ch=getchar(),last=' ';
while(ch>'9'||ch<'0'){
last=ch;
ch=getchar();
}
while(ch<='9'&&ch>='0'){
ans=ans*10+ch-'0';
ch=getchar();
}
if(last=='-')
ans=-ans;
return ans;
}
int n,m,a[200],b[100],d[100],x,y,ans,sum,last,pre,tot;
bool c[105];
double T,de;
const double ze=1e-8;
int main(){
//freopen("bb.in","r",stdin);
n=read();
m=read();
for(int i=1;i<=n;i++)
a[i]=read(),b[a[i]]++;
T=100000;
ans=n;
for(int i=1;i<=m;i++)
c[i]=true,b[a[i]]--,d[a[i]]++;
if(n==m){
ans=0;
for(int i=25;i<=32;i++)
if(d[i])
ans++;
printf("%d\n",ans);
return 0;
}
if(m==0){
printf("%d\n",n);
return 0;
}
while(T>ze){
//tot++;
x=rand()%n+1;
while(c[x])
x=rand()%n+1;
y=rand()%n+1;
while(!c[y])
y=rand()%n+1;
c[x]=true;
c[y]=false;
b[a[x]]--;
b[a[y]]++;
d[a[x]]++;
d[a[y]]--;
sum=0;
last=1;
while(c[last])
last++;
if(last<=n){
sum=1;
pre=last;
for(int i=last+1;i<=n;i++)
if(!c[i]&&a[pre]!=a[i]){
sum++;
pre=i;
}
}
for(int i=25;i<=32;i++)
if(d[i]&&!b[i])
sum++;
if(ans>sum)
ans=sum;
else{
de=sum-ans;
if((1.0/exp(de/T))*100<=rand()%100+1){
c[x]=false;
c[y]=true;
b[a[x]]++;
b[a[y]]--;
d[a[x]]--;
d[a[y]]++;
}
}
T*=0.99998;
}
printf("%d\n",ans);
//printf("%d %d\n",ans,tot);
return 0;
}
可惜这样得不了ac,然后ctime不能用,srand()也没办法,怎么办?手动改随机种子
这个95分
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<cstring>
using namespace std;
typedef long long ll;
ll read(){
ll ans=0;
char ch=getchar(),last=' ';
while(ch>'9'||ch<'0'){
last=ch;
ch=getchar();
}
while(ch<='9'&&ch>='0'){
ans=ans*10+ch-'0';
ch=getchar();
}
if(last=='-')
ans=-ans;
return ans;
}
int n,m,a[200],b[100],d[100],x,y,ans,sum,last,pre,tot;
bool c[105];
double T,de;
const double ze=1e-8;
int main(){
//freopen("bb.in","r",stdin);
for(int i=1;i<=160;i++)
n=rand();
n=read();
m=read();
for(int i=1;i<=n;i++)
a[i]=read(),b[a[i]]++;
T=1000000;
ans=n;
for(int i=1;i<=m;i++)
c[i]=true,b[a[i]]--,d[a[i]]++;
if(n==m){
ans=0;
for(int i=25;i<=32;i++)
if(d[i])
ans++;
printf("%d\n",ans);
return 0;
}
if(m==0){
printf("%d\n",n);
return 0;
}
while(T>ze){
//tot++;
x=rand()%n+1;
while(c[x])
x=rand()%n+1;
y=rand()%n+1;
while(!c[y])
y=rand()%n+1;
c[x]=true;
c[y]=false;
b[a[x]]--;
b[a[y]]++;
d[a[x]]++;
d[a[y]]--;
sum=0;
last=1;
while(c[last])
last++;
if(last<=n){
sum=1;
pre=last;
for(int i=last+1;i<=n;i++)
if(!c[i]&&a[pre]!=a[i]){
sum++;
pre=i;
}
}
for(int i=25;i<=32;i++)
if(d[i]&&!b[i])
sum++;
if(ans>sum)
ans=sum;
else{
de=sum-ans;
if((1.0/exp(de/T))*100<=rand()%100+1){
c[x]=false;
c[y]=true;
b[a[x]]++;
b[a[y]]--;
d[a[x]]--;
d[a[y]]++;
}
}
T*=0.99998;
}
printf("%d\n",ans);
//printf("%d %d\n",ans,tot);
//printf("%.6lf\n",exp(2));
return 0;
}
难道到极限了吗?当然没有,一定可以ac的,我调了半天一直WA,同学看了一下,只改了一个字符,就ac了(强)。
下面是ac的代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<cstring>
using namespace std;
typedef long long ll;
ll read(){
ll ans=0;
char ch=getchar(),last=' ';
while(ch>'9'||ch<'0'){
last=ch;
ch=getchar();
}
while(ch<='9'&&ch>='0'){
ans=ans*10+ch-'0';
ch=getchar();
}
if(last=='-')
ans=-ans;
return ans;
}
int n,m,a[200],b[100],d[100],x,y,ans,sum,last,pre,tot;
bool c[105];
double T,de;
const double ze=1e-8;
int main(){
//freopen("bb.in","r",stdin);
for(int i=1;i<=160;i++)
n=rand();
n=read();
m=read();
for(int i=1;i<=n;i++)
a[i]=read(),b[a[i]]++;
T=2000000;
ans=n;
for(int i=1;i<=m;i++)
c[i]=true,b[a[i]]--,d[a[i]]++;
if(n==m){
ans=0;
for(int i=25;i<=32;i++)
if(d[i])
ans++;
printf("%d\n",ans);
return 0;
}
if(m==0){
printf("%d\n",n);
return 0;
}
while(T>ze){
tot++;
x=rand()%n+1;
while(c[x])
x=rand()%n+1;
y=rand()%n+1;
while(!c[y])
y=rand()%n+1;
c[x]=true;
c[y]=false;
b[a[x]]--;
b[a[y]]++;
d[a[x]]++;
d[a[y]]--;
sum=0;
last=1;
while(c[last])
last++;
if(last<=n){
sum=1;
pre=last;
for(int i=last+1;i<=n;i++)
if(!c[i]&&a[pre]!=a[i]){
sum++;
pre=i;
}
}
for(int i=25;i<=32;i++)
if(d[i]&&!b[i])
sum++;
if(ans>sum)
ans=sum;
else{
de=sum-ans;
if((1.0/exp(de/T))*100<=(double)(rand()%100+1)){
c[x]=false;
c[y]=true;
b[a[x]]++;
b[a[y]]--;
d[a[x]]--;
d[a[y]]++;
}
}
T*=0.999978;
}
printf("%d\n",ans);
//printf("%d %d\n",ans,tot);
return 0;
}
调随机种子是很傻的做法(比赛时不可能完成),调参数才更有效。
同样AC,不要调随机种子,在退火概率中,还有一个系数是可以改的。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<cstring>
using namespace std;
typedef long long ll;
ll read(){
ll ans=0;
char ch=getchar(),last=' ';
while(ch>'9'||ch<'0'){
last=ch;
ch=getchar();
}
while(ch<='9'&&ch>='0'){
ans=ans*10+ch-'0';
ch=getchar();
}
if(last=='-')
ans=-ans;
return ans;
}
int n,m,a[200],b[100],d[100],x,y,ans,sum,last,pre,tot;
bool c[105];
double T,de;
const double ze=1e-8;
int main(){
n=read();
m=read();
for(int i=1;i<=n;i++)
a[i]=read(),b[a[i]]++;
T=2000000;
ans=n;
for(int i=1;i<=m;i++)
c[i]=true,b[a[i]]--,d[a[i]]++;
if(n==m){
ans=0;
for(int i=25;i<=32;i++)
if(d[i])
ans++;
printf("%d\n",ans);
return 0;
}
if(m==0){
printf("%d\n",n);
return 0;
}
while(T>ze){
tot++;
x=rand()%n+1;
while(c[x])
x=rand()%n+1;
y=rand()%n+1;
while(!c[y])
y=rand()%n+1;
c[x]=true;
c[y]=false;
b[a[x]]--;
b[a[y]]++;
d[a[x]]++;
d[a[y]]--;
sum=0;
last=1;
while(c[last])
last++;
if(last<=n){
sum=1;
pre=last;
for(int i=last+1;i<=n;i++)
if(!c[i]&&a[pre]!=a[i]){
sum++;
pre=i;
}
}
for(int i=25;i<=32;i++)
if(d[i]&&!b[i])
sum++;
if(ans>sum)
ans=sum;
else{
de=sum-ans;
if((1.0/(exp(de/(T*1.2))))*100<=(double)(rand()%100+1)){
c[x]=false;
c[y]=true;
b[a[x]]++;
b[a[y]]--;
d[a[x]]--;
d[a[y]]++;
}
}
T*=0.999978;
}
printf("%d\n",ans);
//printf("%d %d\n",ans,tot);
return 0;
}
于是,这种费正解也把这道题ac了。如果书的高度种类有很多的话,这种方法可以比正解更好用,难道不是吗?
本片文章纯属乱搞,神犇不要喷……