水题1:
最大子矩阵和:
给出一个矩阵,求一个子矩阵使得其和最大。
我们先来想一维的:
给出一个序列,求一个连续子序列使得和最大。
f[i] = max(f[i - 1] + seq[i],seq[i]);
考虑子矩阵的话,那么我们可以这样考虑:
我们考虑:
以(i,j)为左上角,(p,q)为右下角的矩阵,我们发现它的矩阵和是:pre_sum[p][j~q] - pre_sum[i,j~q],如果我们考虑暴力:
选择一个点,找出剩下的点,算出矩阵和然后求最大值:
O(n^4)。
而如果我们维护一个”前缀矩阵和”:
我们枚举这个矩阵的最上端和最下端,复杂度n^2,再枚举这个矩阵的宽,从而把复杂度弄成n^3。而暴力枚举的时候,我们的复杂度高在了计算上,而这次我们每次能用一个前缀的思想,使得每次计算矩阵和时间为O(1),复杂度为n^3、、、
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#define Rep(i,n) for(int i = 1; i <= n ; i ++)
#define RepG(i,x) for(int i = head[x] ;~ i ; i = edge[i].next)
#define Rep_d(i,n) for(int i = n ; i > 0 ; i --)
#define Rep_0(i,n) for(int i = 0 ; i < n ; i ++)
#define RD(i,x,n) for(int i = x; i <= n ; i ++)
#define CLR(a,b) memset(a,b,sizeof(a))
#define fl edge[i].f
#define vfl edge[i^1].f
#define v edge[i].to
using namespace std;
const int inf = 1 << 30;
typedef long long ll;
int read(){
char ch = getchar();
while(ch < '0' || ch > '9')ch = getchar ();
int x = 0;
while(ch >= '0' && ch <= '9')x = 10 * x + ch - '0',ch = getchar ();
return x;
}
int sig[105][105];
int maxn = - inf;
int sum = 0;
int main()
{
int n = read();
Rep(i,n)
Rep(j,n){
int a;
scanf("%d",&a);
sig[i][j] = sig[i - 1][j] + a;
}
Rep_0(i,n)
RD(j,i + 1,n){
sum = 0;
Rep(k,n)
{
sum >= 0 ? sum += sig[j][k] - sig[i][k] : sum = sig[j][k] - sig[i][k];
maxn = max(sum,maxn);
}
}
printf("%d\n",maxn);
return 0;
}
总感觉这复杂度好高 ……觉得标解应该不是这样……
水题2:
旅行商简化版(多进程DP):
有个人,他从西边出发,到达最东边,然后返回,但是返回的时候不能经过原来经过的点,所有点都需要走一遍。求最短距离。
没错这就是两次方格取数问题。
那么我们考虑:
一次方格取数的话:
f[i][j] = max(f[i - 1][j] ,f[i][j - 1] ) + a[i][j];
大概就是这样。而两次的话,我们是不能让它跑两遍最优解的。
我们可以这样来考虑:
考虑两个人一起走,f[i][j]代表:第1个人来到了i,第2个人来到了j的最短距离。
显然,我们对于w <= min(i,j)都已经决策过且不会影响之后的决策。设k = max(i,j) + 1,则有:
f[i][k] = min(f[i][k],f[i][j] + dis[j,k]);
f[k][j] = min(f[k][j],f[i][j] + dis[i,k]);
不难发现,f[j][i] = f[i][j],所以我们不妨:
我们考虑i + 1 = k,那么f[i][k] 的意思就是让走到j的那个人直接走向k,并且加上j到k的距离,看起来很对,但是仍然要质疑一下:j~i之间似乎这些点没有走过?
我们考虑一下:
f[i][k]这个点来讲,那么我们显然在前面已经推出过:
f[1 ~ i - 1][n]的状态,以及f[i][1~j]的所有状态,显然当j >i 时,f[i][j]能转移出f[i][j + 1]的状态,相当于第二个人直接走了一步,而如果对于j < i那么转移是:f[i][k] = f[i][j] + dis[j,k];
当i > j + 1时,那么i一定是在j + 1之后就一直是第一个人在走,而到了i = j + 1,那么i要么是从一个地方l走过来,要么是从j走过来,如果从l走过来,那么也就意味着从l–j这一段都是j一个人在走、、、也就意味着又能按照前面的思路……所以分析下来,f[i][j]一定是走了之前的所有点过来的。
排版好像出了些问题。
我们继续来说第三题:
第三题:
搭建双塔:
我觉得这是我做的最好的分类讨论了……
题意:给你N个石柱,每个石柱有高度,搭出最高的高度相同的塔。
思路:
考虑到无后效性,可以DP。
我们设f[i][j]代表在考虑第i个水晶时高度差为j时矮塔的高度。
则有:
f[i][j] = max{f[i - 1][j - h[i]],f[i - 1][j + h[i]] + h[i] ,f[i - 1][h[i] - j] + h[i] - j,f[i - 1][j]};
表示:1.放到高的塔上。
2.放到矮的塔上。
3.之前高度差不是很大,放上这个之后矮塔会变成高塔。
4.不要这个石柱。
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#define Rep(i,n) for(int i = 1; i <= n ; i ++)
#define RepG(i,x) for(int i = head[x] ;~ i ; i = edge[i].next)
#define Rep_d(i,n) for(int i = n ; i > 0 ; i --)
#define Rep_0(i,n) for(int i = 0 ; i < n ; i ++)
#define RD(i,x,n) for(int i = x; i <= n ; i ++)
#define CLR(a,b) memset(a,b,sizeof(a))
#define fl edge[i].f
#define vfl edge[i^1].f
#define v edge[i].to
using namespace std;
const int inf = 1 << 30;
typedef long long ll;
int read(){
char ch = getchar();
while(ch < '0' || ch > '9')ch = getchar ();
int x = 0;
while(ch >= '0' && ch <= '9')x = 10 * x + ch - '0',ch = getchar ();
return x;
}
int f[105][2005],sum[20005],h[2005];
int main()
{
memset(f,-0x7f,sizeof(f));
int n = read();
Rep(i,n)
h[i] = read();
sort(h + 1,h + 1 + n);
Rep(i,n)
sum[i] = sum[i - 1] + h[i];
f[1][0] = 0;f[1][h[1]] = 0;
RD(i,2,n)
for(int j = 0 ; j <= sum[i]; j ++)
{
int h1 = j - h[i];
if(h1 >= 0 && h1 <= sum[i - 1])
f[i][j] = max(f[i][j],f[i - 1][h1]);
int h2 = j + h[i];
if(h2 <= sum[i - 1])
f[i][j] = max(f[i][j] , f[i - 1][h2] + h[i]);
int h3 = h[i] - j;
if(h3 >= 0 && h3 <= sum[i - 1])
f[i][j] = max(f[i - 1][h3] + h3,f[i][j]);
int h4 = j;
if(h4 <= sum[i - 1])
f[i][j] = max(f[i - 1][j],f[i][j]);//Rig
}
if(f[n][0] > 0)printf("%d\n",f[n][0]);
else printf("Impossible\n");
return 0;
}
ps:这个代码是我照着标准程序调的。