1. 程式人生 > 實用技巧 >P2340 [USACO03FALL]Cow Exhibition G題解

P2340 [USACO03FALL]Cow Exhibition G題解

新的奇巧淫技

原題傳送門

眾所周知,模擬退火是一種很強大的演算法,DP很強,但我模擬退火也不虛,很多題你如果不會的話基本可以拿來水很多分。比如這道題,我用模擬退火可以輕鬆水過(雖然我是足足交了兩頁才過)但是沒有什麼大問題,如果模擬退火還不會,建議先看你穀日報學習一下。剩下的主要就是一些細節,看程式碼。

#include <bits/stdc++.h>
using namespace std;
#define gc getchar()
#define re register
double st=clock();
int n,tot,ans,x,y;
int I[1000000+10],E[1000000+10];
struct node{
	int IQ,EQ;
}a[1000000+10];//定義結構體,方便random_shuffle

inline int read(){
	int r=0,l=1;char ch=gc;
	while(!isdigit(ch)){if(ch=='-')l=-1;ch=gc;}
	while(isdigit(ch)){r=(r<<3)+(r<<1)+ch-'0';ch=gc;}
	return r*l;
}

inline void add(int x,int y){
	if(x<0&&y<0)return;//如果兩個都是0,都答案沒有任何貢獻,直接pass
	if(x>=0&&y>=0)
		return a[++tot]=(node){x,y},void();//如果兩個都>0,那麼最優解一定會有它,直接加進去即可
	if((x<0&&y>0&&abs(x)-y>=rand())||(x>0&&y<0&&abs(y)-x>=rand()))return;//如果兩個值是一正一負相差太大可以pass,但一定要注意是>rand(),這樣會有一定概率接受這個解,至於為什麼,留給讀者思考
	if(x+y<0)if(exp(-(x+y))>(double)rand()/(double)RAND_MAX)return;//如果兩個值相加為0,對答案會有負貢獻,但仍要有一定機率接受,好像和上面判重了,因為本人太菜,不知道去掉對不對,所以就寫上了~
	a[++tot]=(node){x,y};
}

inline void solve(){
	tot=0;for(re int i=1;i<=n;++i)add(I[i],E[i]);//初始化,重新選取合適牛
	random_shuffle(a+1,a+tot+1);//打亂重排
	x=0,y=0;
	for(re int i=1;i<=tot;++i){
		x+=a[i].IQ;y+=a[i].EQ;
		if(x<=0&&y<=0)//到此為止,答案已經不合法
			if(exp(ans-x-y)>(double)rand()/(double)RAND_MAX)return;//運用模擬退火思想,雖然已經不合法,但考慮後面可能有解加上後就合法,所以有一定概率接受這個解並繼續。這也是模擬退火演算法的優勢
		if(x>0&&y>0)ans=max(ans,x+y);//合法的話,更新答案
	}
}

int main(){
	srand(time(NULL));
	n=read();
	for(re int i=1;i<=n;++i)I[i]=read(),E[i]=read();//存取各頭牛的IQ和EQ,因為上方add時會有刪除,因為是隨機刪除,可能會造成第一次就把最優解中的一頭牛刪除,然後就掛了,上面我的64分就是這樣錯的
	int crl=15000;
	while(crl--)solve();
	printf("%d\n",ans);
	return 0;
}

random_shuffle大法好