noip刷題筆記1

老曾哥發表於2018-05-19

本人是個oi蒟蒻,如果有什麼不對的地方望指正

這次我做了幾道動態規劃題,留個記錄

1.筷子(傳送門:http://www.caioj.cn/problem.php?id=1077)

首先這一題我們先定義一個陣列dp[i][j],表示前i只筷子裡挑出j雙,每雙筷子差的平方的和的最小值

然後將筷子長度從小到大排序.因為給出的筷子長度是亂的,如果排序能保證每次選取鄰近兩根的筷子比選取不臨近的兩跟筷子的差要小,不排序則要跳著選,dp方程不好寫

接著初始化dp陣列,再列舉i,j,若不選第i根,dp[i][j]=dp[i-1][j],若選擇,則需要再列舉一個m和i配對,dp[i][j]=min(dp[i][j],dp[m-1][j-1]+(t[i]-t[m])²)

程式碼如下:

#include <iostream>
#include <algorithm>

using namespace std;

int n,k;
int dp[101][61];//dp[i][j]表示前i箇中選出j雙的差平方最小值 
const int inf=1<<30;
int t[101];

int main() {
	cin >> n >> k;
	k+=3;
	for(int i=1;i<=n;i++) cin >> t[i];
	if(k*2>n) {
		cout << -1 << endl;
		return 0;
	} 
	sort(t+1,t+n+1);//排序是為了保證選擇相鄰的一雙筷子差值的平方最小
	fill(dp[0],dp[0]+101*61,inf);
	for(int i=0;i<=n;i++) dp[i][0]=0;//不組成一對 
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=k;j++) {//j雙筷子 
			dp[i][j]=dp[i-1][j];//第I根不選
			for(int m=1;m<i;m++) {//選另外一個並組成差平方最小的 
				dp[i][j]=min(dp[i][j],dp[m-1][j-1]+(t[i]-t[m])*(t[i]-t[m]));
			}
		} 
	} 
	cout << dp[n][k] << endl;
	return 0;
}

2.不重疊線段(傳送門:http://www.caioj.cn/problem.php?id=1078)

先定義一個陣列dp[i]表示覆蓋區間[1,i]最大覆蓋數字的數量

然後將線段的起點從小到大排序,方便後面dp

列舉每條線段,將這條線段的終點對應的覆蓋數與這條線段起點前對應的覆蓋數加上線段長度作比較,若終點覆蓋數更小則更新到較大值

然後將這條線段後的所有端點對應的覆蓋數更新為這個最大值

程式碼如下:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int n;
struct node {
	int x,y;
};
vector<node> v;
int dp[2002]={0};//dp[i]使用某些線段,覆蓋區間[1,n],最多可以覆蓋多少個數字 

bool cmp(node a,node b) {
	return a.x<b.x;
}

int main() {
	cin >> n;
	for(int i=1;i<=n;i++) {
		int a,b;
		cin >> a >> b;
		a++,b++;
		if(a>b) swap(a,b);//若線段是反的就挑頭 
		v.push_back((node){a,b});
	}
	sort(v.begin(),v.end(),cmp);//將每條線段按起點值從小到大排序,方便後面dp
	for(int i=0;i<v.size();i++) {
		if(dp[v[i].x-1]+(v[i].y-v[i].x+1)>dp[v[i].y]) {
			dp[v[i].y]=dp[v[i].x-1]+(v[i].y-v[i].x+1);//若這條線段前已經覆蓋的個數加上線段覆蓋長度大於當前的這條線段覆蓋個數,更新當前值 
			for(int j=v[i].y+1;j<=2001;j++) {//更新後面位置的最大覆蓋值 
				dp[j]=max(dp[j],dp[v[i].y]);
				if(dp[j]>dp[v[i].y]) break;//更新完畢 
			}
		}
	} 
	cout << dp[2001] << endl;
	return 0;
}


相關文章