两道正解都不是随机化的题被模拟退火 水过。。。
小猫爬山:题目大意是有N只小猫,第i只小猫的体重为ai,有很多缆车,每辆缆车最多承重w,求需要最少多少辆缆车能承载所有的猫。。。
(1<=n<=18,1<=ai,w<=1e8)本来想背包,发现w太大,无奈之下随机化水过。
模拟退火大法:先随机化小猫的排列,大约一千次,保存起来。对于每一种排列,再随机(大约50次),对于每一次随机出来的排列,从前到后贪心(这题直接贪心绝对错,但加上随机化正确率就高多了),如果贪心出的答案比之前的答案更小,则更新答案,同时更新之前的排列。。。然后就可以计算答案了,感觉正确率还是挺高的。
注:既然是模拟退火,那就还要控制温度,即:循环n次以上步骤,每次对排列进行的随机(就是打乱原序列)随着次数增加,随机的越来越不那么乱,这样就越来越接近正解的排列,这样贪心正确率就很高了。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<queue> 5 #include<stack> 6 #include<iostream> 7 #include<ctime> 8 using namespace std; 9 int n,w,rpath[1000][20],ans[1000],a[20]; 10 void adjust(int *p,int rn) 11 { 12 while(rn--) 13 { 14 int x=rand()%n+1; 15 int y=rand()%n+1; 16 swap(p[x],p[y]); 17 } 18 } 19 void get_rpath() 20 { 21 for(int i=0;i<1000;i++) 22 { 23 ans[i]=1e9; 24 for(int j=1;j<=n;j++)rpath[i][j]=j; 25 adjust(rpath[i],n-1); 26 } 27 } 28 int get_val(int *p) 29 { 30 int ret=0,sum=0; 31 for(int i=1;i<=n;i++) 32 { 33 sum+=a[p[i]]; 34 if(sum>w){ret++;sum=a[p[i]];} 35 } 36 if(sum)ret++; 37 return ret; 38 } 39 int main() 40 { 41 freopen("koneko.in","r",stdin);freopen("koneko.out","w",stdout); 42 srand(time(NULL)); 43 scanf("%d%d",&n,&w); 44 for(int i=1;i<=n;i++)scanf("%d",&a[i]); 45 get_rpath(); 46 int t=n,p[20]; 47 while(t--) 48 { 49 for(int i=0;i<1000;i++) 50 { 51 for(int j=0;j<50;j++) 52 { 53 memcpy(p,rpath[i],sizeof(p)); 54 adjust(p,t); 55 int tmp=get_val(p); 56 if(tmp<ans[i]) 57 { 58 ans[i]=tmp; 59 memcpy(rpath[i],p,sizeof(rpath[i])); 60 } 61 } 62 } 63 } 64 int res=ans[0]; 65 for(int i=1;i<1000;i++)res=min(res,ans[i]); 66 cout<<res<<endl; 67 return 0; 68 }
售货员的难题:经典TSP问题,有n座城市,告诉每两座城市的距离,售货员从1出发,要求不重复经过所有城市并回到1号城市,经过路径最小值。。。(n好像是20以内)
模拟退火大法:仍然是随机化排列,基本步骤和上面完全相同,不同是贪心那一部分,事实上,根本不需要贪心,对每一种排列直接进行计算即可。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<queue> 5 #include<stack> 6 #include<iostream> 7 #include<ctime> 8 using namespace std; 9 int rpath[1000][50],n,mp[50][50],ans[1000]; 10 void adjust(int *p,int rn) 11 { 12 while(rn--) 13 { 14 int x=rand()%(n-1)+1; 15 int y=rand()%(n-1)+1; 16 swap(p[x],p[y]); 17 } 18 } 19 void get_rpath() 20 { 21 for(int i=0;i<1000;i++) 22 { 23 ans[i]=1e9; 24 for(int j=1;j<n;j++)rpath[i][j]=j+1; 25 adjust(rpath[i],n-1); 26 } 27 } 28 int get_dis(int *p) 29 { 30 int q=1,ret=0; 31 for(int i=1;i<n;i++) 32 { 33 ret+=mp[q][p[i]]; 34 q=p[i]; 35 } 36 ret+=mp[q][1]; 37 return ret; 38 } 39 int main() 40 { 41 freopen("salesman.in","r",stdin);freopen("salesman.out","w",stdout); 42 srand(time(NULL)); 43 scanf("%d",&n); 44 for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&mp[i][j]); 45 get_rpath(); 46 int t=n-1,p[50]; 47 while(t--) 48 { 49 for(int i=0;i<1000;i++) 50 { 51 for(int j=0;j<50;j++) 52 { 53 memcpy(p,rpath[i],sizeof(p)); 54 adjust(p,t); 55 int tmp=get_dis(p); 56 if(tmp<ans[i]) 57 { 58 ans[i]=tmp; 59 memcpy(rpath[i],p,sizeof(rpath[i])); 60 } 61 } 62 } 63 } 64 int res=ans[0]; 65 for(int i=1;i<1000;i++)res=min(res,ans[i]); 66 cout<<res<<endl; 67 return 0; 68 }
其实感觉两份代码几乎一样,就只是贪心变成直接计算了而已。。。
区间众数:题目大意是有n个数的序列,m个询问,每次询问l到r区间内是否有某一个数字出现次数超过区间一半。。。
随机化大法:对每次询问,随机二十次区间里的任意一种颜色,可以log的时间内查找该颜色在区间内出现的次数,查找方法如下:每种颜色用一个vector保存出现的所有位置,查找时只需要用upper_bound(r)减去lower_bound(l)就可以得到次数。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<queue> 5 #include<stack> 6 #include<iostream> 7 #include<ctime> 8 using namespace std; 9 vector<int>v[10005]; 10 int n,a[300005],c; 11 int main() 12 { 13 freopen("color.in","r",stdin);freopen("color.out","w",stdout); 14 srand(time(NULL)); 15 scanf("%d%d",&n,&c); 16 int l,r,q; 17 for(int i=1;i<=n;i++) 18 { 19 scanf("%d",&a[i]); 20 v[a[i]].push_back(i); 21 } 22 scanf("%d",&q); 23 for(int i=1;i<=q;i++) 24 { 25 scanf("%d%d",&l,&r); 26 bool ok=0; 27 for(int j=20;j;j--) 28 { 29 int k=l+rand()%(r-l+1); 30 int sum=upper_bound(v[a[k]].begin(),v[a[k]].end(),r)-lower_bound(v[a[k]].begin(),v[a[k]].end(),l); 31 if(sum>(r-l+1)/2){printf("yes %d\n",a[k]);ok=1;break;} 32 } 33 if(!ok)puts("no"); 34 } 35 return 0; 36 }
最后再说一句,虽然是三份ac过的代码,但并不能保证每次都过,第二份正确率大约在80%,第一份我是一遍就过了,所以不清楚,应该正确率更高,第三份错误率比1/2^20还要低,所以过不了的话可以去买彩票了。前两份过不了请多次提交