题目名称 |
buy |
slide |
divide |
输入 |
buy.in |
slide.in |
divide.in |
输出 |
buy.out |
slide.out |
divide.out |
每个测试点时限 |
1秒 |
1秒 |
1秒 |
内存限制 |
256MB |
256MB |
256MB |
测试点数目 |
10 |
10 |
10 |
每个测试点分值 |
10 |
10 |
10 |
是否有部分分 |
无 |
无 |
无 |
题目类型 |
传统 |
传统 |
传统 |
buy
description:
地主zxr想买一些长方形的土地,所有的土地可以分为若干组,每一组的土地的价格为这一组里的最长的长乘上最长的宽。土地的长和宽是不能交换的,例如一块2*5的土地和一块5*2的土地放在一起,价格为5*5=25。ZXR想知道最少花费多少钱可以买下所有的土地。
Input:
第一行一个数n表示一共有n块土地。
接下来n行每行两个数xi和yi分别表示每块土地的长和宽。
Output:
一行一个数表示最小价格。
Sampleinput:
4
100
1
15
15
20
5
1 100
Sampleoutput:
500
zxr分3组买这些土地:
第一组:100x1,
第二组1x100,
第三组20x5 和
15x15 plot.
每组的价格分别为100,100,300, 总共500.
HINT:
对于30%的数据:n<=2000。
对于100%的数据:n<=50000,长宽<=1e6
题解:先按x升序排序,再在此基础上按y降序排序,如果不满足此规则的点,则可以被覆盖,如:
X: 20 40 60
Y;50 25 46 显然二号点是没有用的,他可被三号节点覆盖;
于是我们得到了一个所有点都有用的序列,并且x,y的大小有序;
dp[i]表示选了1到 i 个点, dp[i] = dp[j] + Xi Yj+1 ,由这个形式我们可以想到斜率优化:如果有k>=j 并且k更优,则之后一定不会再用到j, k一定更优,通过转移方程得到:
dp[j] + Xi Yj+1 >= dp[k] +Xi Yk+1 , k>j
所以满足Xi >= (dp[k]-dp[j]) /(Yj+1 - Yk+1) 则k可跟新j,因为Xi递增,于是斜率越大越好, 所以我们就维护一个上凸包,使斜率满足上式且递增,则队首元素最优
#include <bits/stdc++.h> using namespace std; const int maxn = 50000+5; const long long inf = 100000000000008; long long dp[maxn]; int qq[maxn]; int n; inline void read(long long &x){ x=0;long long f=1;char s=getchar(); while(s<‘0‘||s>‘9‘){if(s==‘-‘)f=-1;s=getchar();} while(s>=‘0‘&& s<=‘9‘){x=x*10+s-‘0‘;s=getchar();} x*=f; } struct Point{ long long x, y; }p[maxn],q[maxn]; bool cmp(Point a, Point b){ return a.x == b.x ? a.y > b.y : a.x < b.x; } int main(){ freopen("buy.in","r",stdin); freopen("buy.out","w",stdout); scanf("%d",&n); int hd = 1; for(int i = 1; i <= n; i++) read(p[i].x), read(p[i].y); sort(p + 1, p + 1 + n, cmp); q[1] = p[1]; for(int i = 1; i <= n; i++){ while(hd && p[i].y >= q[hd].y) hd--; q[++hd] = p[i]; } int h = 1, t = 1, s = 1; qq[1] = 0; for(int i = 1; i <= hd; i++){ while(h < t && q[i].x * (q[qq[h]+1].y - q[qq[h+1]+1].y) > dp[qq[h+1]] - dp[qq[h]]) h++; dp[i] = q[qq[h]+1].y * q[i].x + dp[qq[h]]; while(h < t && (q[qq[t-1]+1].y - q[qq[t]+1].y) * (dp[i] - dp[qq[t]]) >= (dp[qq[t]] - dp[qq[t-1]]) * (q[qq[t]+1].y- q[i+1].y)) t--; qq[++t] = i; } printf("%I64d\n",dp[hd]); }
斜率优化要注意:加减符号和中间的变号过程; 斜率递增建下凸包,递减建上凸包
slide
description:
tony在一片雪地里滑雪,他从(1,1)出发,并只能从高的地方滑到低的地方,贪玩的tony想知道他到底能滑多远呢?
Input:
第一行两个数n,m表示雪地大小。
接下来n行每行m个数表示雪地,其中w[i][j] = x表示在i,j位置的高度为x,每个位置(除了边界)都可以滑到它的上下左右四个方向(从高往低)。
Output:
一行一个数表示滑行的最长长度。
Sample input:
22
4 3
21
Sample output:
3
Hint:对于30%的数据,n<=10,m<=10;
对于100%的数据,n<=300,m<=300。W[i][j]<=1e6;
题解:记忆化搜索的水题
#include <bits/stdc++.h> using namespace std; const int maxn = 305; int n,m; int mp[maxn][maxn],dp[maxn][maxn]; int a[4][2] = {{0,1},{1,0},{-1,0},{0,-1}}; int dfs(int x, int y){ if(dp[x][y]) return dp[x][y]; dp[x][y] = 1; int tmp = 0; for(int i = 0; i < 4; i++){ int nx = x + a[i][0], ny = y + a[i][1]; if(mp[nx][ny] < mp[x][y] && nx && ny && nx <=n &&ny <= m) tmp = max(tmp, dfs(nx, ny)); } return dp[x][y] += tmp; } int main(){ freopen("slide.in","r",stdin); freopen("slide.out","w",stdout); scanf("%d%d",&n,&m); for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) scanf("%d",&mp[i][j]); dfs(1, 1); printf("%d\n",dp[1][1]); }
divide
description:
tom想知道,把一个整数n划分为若干个正整数的形式,一共有多少种方案。例如当n=4时,他有5个划分,{4},{3,1},{2,2},{2,1,1},{1,1,1,1};
input:
一行一个数n。
Output:
一行一个数表示方案数%1e9+7。
Sample input:
4
Sample output:
5
Hint:
对于30%的数据,n<=50。
对于100%的数据,n<=5000。
题解:dp[i][j]表示i拆分成的数中最大的数是j;
i > j, dp[i][j] = dp[i-j][j] + dp[i][j-1] //有两种情况,一是我选择拆分的数中有j, 二是选择的数中不含j
i < j, dp[i][j] = dp[i][i] //之后会用到,意义也解释的通
i == j, dp[i][j] = 1 + dp[i][j-1]
#include <bits/stdc++.h> using namespace std; const int mod = 1e9+7; const int maxn = 5005; int ans,t, dp[maxn][maxn]; int main(){ freopen("divide.in","r",stdin); freopen("divide.out","w",stdout); int n; scanf("%d",&n); for(int i = 1; i <= n; i++)dp[i][1] = 1; for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++){ if(i == j)dp[i][j] = 1 + dp[i][i-1]; if(i > j)dp[i][j] = dp[i-j][j] + dp[i][j-1]; if(i < j)dp[i][j] = dp[i][i]; dp[i][j] %= mod; } printf("%d\n",dp[n][n]); }