SRM 605 T1 T2 T3

250:

题目大意:给定n(n <= 50)个物品,每个物品有两种属性ai和bi,现在要选出一个子集(可为空)。要求最大化Y * A,Y是选出的物品中ai的种类数,A为bi的总和。

基本思路:对每种ai做一次dp,求最大的bi的和,记为v[i]。然后再利用vi进行dp就好了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;

int f[200];

class AlienAndHamburgers{
public:
	int getNumber(vector <int> type, vector <int> taste){
		int n=type.size();
		for (int i=0; i<=100; ++i) f[i]=-200000;
		for (int i=0; i<n; ++i)
			f[type[i]]=max(f[type[i]],max(f[type[i]]+taste[i],taste[i]));
		sort(f,f+101);
		int ans=0,ans1=0;
		for (int i=100; i>=1; --i){
			ans1+=f[i];
			if (f[i]<-150000) break;
			ans=max(ans1*(101-i),ans);
		}  
		return ans;
	}
};

450:

题目大意:

给定 1..2n 一共2n个数,现在让你将这些数填在一个2行n列的矩阵里,使得每行的数都是按列递增的,每列的数的差值要大于等于K。N<= 50, 1 <= K <= 10

基本思路:

设F[i][j][opt]代表填了1..i这些数字,且第1行填了j个,第2行填了 i – j个数,默认第一行多于第二行,第一行多的数且差值与i小于K的集合为opt的方案。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

int f[101][101][1000];
int g[51];

const int P=1000000007;
int c[101][101];

int find(int x,int y){
	c[0][0]=1;
	for (int i=1; i<=x; ++i)
		for (int j=0; j<=min(i,y); ++j)
			c[i][j]=(j==0)?1:(c[i-1][j]+c[i-1][j-1])%P;
	return c[x][y];
}

class AlienAndSetDiv1{
public:
	int getNumber(int N, int K){
		if (K==1) return find(2*N,N);
		N*=2;
		memset(f,0,sizeof(f));
		f[0][0][0]=1;
		--K;
		int m=(1<<K)-1;
		for (int i=0; i<N; ++i)
			for (int j=0; j<=i; ++j)
				for (int k=0; k<=m; ++k)
					if (f[i][j][k]){
						int z=0,opt=((k<<1)+1) & m;
						if ((k & (1<<(K-1)))>0) ++z;
						f[i+1][j+z][opt]=(f[i+1][j+z][opt]+f[i][j][k])%P;
						opt=(k<<1) & m;
						if (j>0)
							f[i+1][j+z-1][opt]=(f[i+1][j+z-1][opt]+f[i][j][k])%P;
					}
		memset(g,0,sizeof(g));
		g[0]=1;
		N/=2;
		for (int i=1; i<=N; ++i)
			for (int j=0; j<i; ++j)
				g[i]=(g[i]+(long long)g[j]*f[(i-j)*2][0][0] %P)%P;
		return g[N]*2%P;
	}
};
1000:

题目大意:

给定一个n(n <= 200)的排列,每次可以对这个序列做如下操作:

1.      选择非空的连续一段子序列

2.      将一段所有元素都替换成他们之中的最大值。

问操作至多K次后,一共有多少种不同的序列。
基本思路:

很显然操作完后的序列为许多升序的段,所以设dp[i][j][k]代表处理完前i个位置了,且第i个位置填的数字为j,操作了至少K次的方案。有种比较暴力的方法是:枚举下一个位置的数字l,然后判断l是否能填在j后面,且能填在位置i,然后再进行转移即可。其实情况就三种,下个数字大于、等于或者小于当前数字,只要记个类似前缀和以及后缀和的东西即可。然后还有一些小细节要处理,最后的答案为sigma(i = 1 -> n)(j = 0 -> K)dp[n - 1][i][j]。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

int dp[205][205][205];
int ll[205], rr[205], pos[205];
const int P = 1e9 + 7;
int n;

void updata(int &x, int y) {
    x += y;
    if (x >= P) x -= P;
}

class AlienAndPermutation {
public:
    int getNumber(vector <int> P, int K) {
	n = P.size();
	for (int i = 0; i < n; ++i) {
	    int x = P[i];
	    pos[x] = i;
	    ll[x] = rr[x] = i;
	    while (ll[x] - 1 >= 0 && P[ll[x] - 1] < x)
		--ll[x];
	    while (rr[x] + 1 < n && P[rr[x] + 1] < x)
		++rr[x];
	}
	for (int i = 1; i <= n; ++i)
	    if (ll[i] <= 0 && i != P[0]) 
		dp[0][i][1] = 1;
	dp[0][n + 1][0] = 1;
	for (int i = 0; i + 1 < n; ++i)
	    for (int j = 0; j <= K; ++j) {	
		for (int k = 1; k <= n; ++k) 
		    if (i + 1 >= ll[k] && i + 1 <= rr[k])
			updata(dp[i + 1][k][j], dp[i][k][j]);
		if (rr[P[i]] >= i + 1)
		    updata(dp[i + 1][P[i]][j + 1], dp[i][n + 1][j]);
		for (int k = 1, res = 0; k <= n; ++k) {
		    if (ll[k] <= i + 1 && rr[k] >= i + 1 && pos[k] > i) {
			if (k != P[i + 1])
			    updata(dp[i + 1][k][j + 1], res);
			else updata(dp[i + 1][n + 1][j], res);
		    }
		    updata(res, dp[i][k][j]);
		    if (k == P[i]) updata(res, dp[i][n + 1][j]);
		}
		for (int k = n, res = 0; k > 0; --k) {
		    if (ll[k] <= i + 1 && rr[k] >= i + 1) {
			if (k != P[i + 1]) updata(dp[i + 1][k][j + 1], res);
			else updata(dp[i + 1][n + 1][j], res);
		    }
		    if (pos[k] <= i) {
			updata(res, dp[i][k][j]);
			if (k == P[i]) updata(res, dp[i][n + 1][j]);
		    }
		}
	    }
	int ans = 0;
	for (int i = 0; i <= K; ++i)
	    for (int j = 1; j <= n + 1; ++j)
		updata(ans, dp[n - 1][j][i]);
	return ans;
    }

};


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值