DSA 2020-7

Posted zxyfrank

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DSA 2020-7相关的知识,希望对你有一定的参考价值。

若有恒,何必三更眠五更起;

最无益,莫过一日曝十日寒。

7.25

分割数组的最大值

区间dp

最大值可以方便的转移,所以可以直接应用区间dp

为什么这里的状态转移不一样?

对比最经典的区间dp 题目 石子合并

[j = i+l-1dp[i][j] = max_{kin [i,j)}(dp[i][k]+dp[k+1][j] +sum_{t = i}^{j}a[t])leq i lt j leq 2n ]

  1. dp[i][j]状态定义不同
    1. 合并石子:区间性质
    2. 分割数组:单侧区间,实际上直接枚举了区间长度
  2. 转移

[dp[i][j] = min(dp[i][j],max(dp[k][j-1],sum_{t =k+1}^{i}a[t])) ]

class Solution {
public:
    int splitArray(vector<int>& nums, int m) {
        int n = nums.size();
        long long MAX = LLONG_MAX;
        vector<vector<long long> > dp(n+1,vector<long long>(n+1,MAX));
        vector<long long> sum(n+1,0);
        for(int i = 0;i<n;i++){
            sum[i+1] = sum[i] + nums[i];
        }
        for(int i = 0;i<=n;i++){
            dp[0][i] = MAX;
        }
        dp[0][0] = 0;
        for(int i = 1;i<=n;i++){
            for(int j = 1;j<=min(i,m);j++){
                for(int k = 0;k<i;k++){
                    dp[i][j] = min(dp[i][j],max(dp[k][j-1],sum[i]-sum[k]));
                }
            }
        }
        return (int)dp[n][m];
    }
};

为什么可以用二分

验证方便(可以直接使用贪心策略)

分成m段的最大值的最小值

[ ext{given} m \\ans = min(max(forall f())) ]

-> 优化问题 -> 广义上是一个搜索解的问题,解空间是一定范围内的整数

??这里发现自己真的应该好好学逻辑,这种表示也不知道对不对,好蹩脚啊

采用贪心策略,尽可能少分,验证解是否合理(也就是判断一个解是不是在解空间里面)

bool test(vector<int> a,int s,int m){
    int n = a.size();
    int cnt = 0;
    long long sum = 0;
    for(int i = 0;i<n;i++){
        sum += a[i];
        if(sum>s){
            cnt++;sum = a[i];
            if(sum>s)return false;
            continue;
        }
    }
    cnt++;
    return cnt<=m;
}
int splitArray(vector<int>& nums, int m) {
    int n = nums.size();
    long long sum = 0;
    int _max = -1;
    for(int i = 0;i<n;i++){
        sum += nums[i];
        _max = max(_max,nums[i]);
    }
    long long l = _max;
    // long long l = 0;
    long long r = sum;
    long long mid;
    while(l!=r){
        mid = (l+r)/2;
        if(test(nums,mid,m)){
            r = mid;
        }else{
            l = mid + 1;
        }
    }
    return l;
}

7.26

矩阵中的最长递增路径

dfs + 记忆化搜索

?? What about the longest non-decreasing path in a matrix?

int longestIncreasingPath(vector<vector<int>>& matrix) {
    // 注意条件判断!
    int n = max(matrix.size(),matrix[0].size());
    // ...
}
class Solution {
public:
    int di[4] = {1,0,-1,0};
    int dj[4] = {0,1,0,-1};
    int imax;
    int jmax;
    int ans = -7;
    int l = 0;
    bool check(int i,int j){
        return (i>=0&&i<=imax&&j>=0&&j<=jmax);
    }
    int dfs(vector<vector<int>>& matrix,vector<vector<int>>& ms,
	vector<vector<int>>& vis,int i,int j){
        if(ms[i][j]!=-3){
            return ms[i][j];
        }
        else{
            int _ans = 0;
            for(int k = 0;k<4;k++){
                int ii = i+di[k];
                int jj = j+dj[k];
                if(check(ii,jj) && !vis[ii][jj] && matrix[ii][jj] > matrix[i][j]){
					vis[ii][jj] = 1;
                    _ans = max(_ans,dfs(matrix,ms,vis,ii,jj)+1);
                    vis[ii][jj] = 0;
                }
            }
            ms[i][j] = _ans;
            return ms[i][j];
        }
    }
    int longestIncreasingPath(vector<vector<int>>& matrix) {
        if(matrix.size() == 0)return 0;
        int n = max(matrix.size(),matrix[0].size());
        vector<vector<int> > ms(n+10,vector<int>(n+10,-3));
        vector<vector<int> > vis(n+10,vector<int>(n+10,0));
        imax = matrix.size()-1;
        jmax = matrix[0].size()-1;
        for(int i = 0;i<matrix.size();i++){
            for(int j = 0;j<matrix[i].size();j++){
                ans = max(ans,dfs(matrix,ms,vis,i,j)+1);
            }
        }
        return ans;
    }
};

基于拓扑排序的动态规划

动态规划其实和记忆化搜索是同样的核心思想 最优子结构&无后效性&重复子问题

只不过记忆化搜索没有显式的状态转移

几个思考题比较有趣

为了让大家更好地理解这道题,小编出了四道思考题,欢迎感兴趣的同学在评论区互动哦。

  • [ ] 「方法一」中使用了记忆化存储和深度优先搜索,这里的深度优先搜索可以替换成广度优先搜索吗?
  • [ ] 「方法二」中基于拓扑排序对排序后的有向无环图做了层次遍历,如果没有拓扑排序直接进行广度优先搜索会发生什么?
  • [ ] 「方法二」中如果不使用拓扑排序,而是直接按照矩阵中元素的值从大到小进行排序,并依此顺序进行状态转移,那么可以得到正确的答案吗?如果是从小到大进行排序呢?
  • [ ] 「变式」给定一个整数矩阵,找出符合以下条件的路径的数量:这个路径是严格递增的,且它的长度至少是 33。矩阵的边长最大为 10^3,答案对 10^9 + 7取模。其他条件和题目相同。思考:是否可以借鉴这道题的方法?

7.27

POJ 数组抓牛

记忆化广度优先搜索

采用bfs的原因是因为目标就是要找到最优解,这种情况下dfs是没有边界的

?? 像这种情况下一定要注意利用短路求值方法,首先判断有没有数组越界

?? 复习一下在编译原理当中短路求值的实现方法

控制流的实现可以采用回填法

if ((a && b && c) || d) {
do_something();
}
if (a) {
if (b) {
 if (c) {
   goto then_label;
 } else {
   goto else_label;
 }
} else {
 goto else_label;
}
} else {
else_label:
if (d) {
then_label:
 do_something();
}
}

//作者:RednaxelaFX
//链接:https://www.zhihu.com/question/53273670/answer/134298223
//来源:知乎
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
bool check(int ii ){
	return  ii>=0 && ii<=N && vis[ii]==-1;
}

7.28

Friend Chains

邻接表+HashMap

// Friend Chians 
#include<stdio.h>
#include<queue>
#include<string.h>
#include<string> 
#include<algorithm>
#include<iostream>
#include<map>
using namespace std;
const int MAX = 0x3f3f3f3f;
const int N = 1050;
int ans;
int w[N][N];
bool vis[N];
int dis[N][N];
vector<int >e[N];
int n;
map<string, int > m;
string buf;
string buf1,buf2;
void init(){
	for(int i = 0;i<N;i++)vector<int >().swap(e[i]); 
	memset(w,0,sizeof(w));
	memset(dis,0,sizeof(dis));
	ans = -3;
	for(int i = 0;i<N;i++){
		for(int j  =0;j<N;j++){
			dis[i][j] = -3;
		}
	}
}
queue<int >q;
void bfs(int ori){
	memset(vis,0,sizeof(vis));
	queue<int> emp; 
	swap(emp,q);
	dis[ori][ori] = 0;
	vis[ori] = 1;
	// 
	q.push(ori);
	while(!q.empty()){
		int cur = q.front();q.pop();
		for(int i = 0;i<e[cur].size();i++){
			int to = e[cur][i];
			if(dis[ori][to]==-3 && !vis[to]){
				dis[ori][to] = dis[ori][cur]+1;
				q.push(to);
			}
		}
	}
}
int main(){
	while(1){
		scanf("%d",&n);
		init();
		if(n==0) return 0;
		else{
			for(int i = 0;i<n;i++){
				cin >> buf;
				m[buf] = i;
			}
		}
		// 
		int nn;
		scanf("%d",&nn);
		for(int i = 0;i<nn;i++){
			cin >>buf1;
			cin >>buf2;
			int u = m[buf1];
			int v = m[buf2];
			e[u].push_back(v);
			e[v].push_back(u);
		} 
		// 输入完成
		for(int i = 0;i<n;i++){
			bfs(i);
		} 
		ans = -MAX;
		bool has = true;
		for(int i = 0;i<n;i++){
			for(int j = 0;j<n;j++){
				if(dis[i][j] ==  -3){
					printf("-1
");
					has = false;
					break;					
				}else{
					ans = max(ans,dis[i][j]);
				}
			}
			if(!has)break;
		} 
		if(!has)continue;
		printf("%d
",ans); 
	} 
}

7.29

PAT1018

关键问题是如何评判一条路径,有多个评测尺度

题意的理解(后面的车不能用于前面的站点),这篇博客分析的很好

??这篇文章里面有一个神来之笔,想了好久没有头绪

所以用sent值来存储collect变化序列中最小的,当sent的值为负时,就代表需要从原点取自行车。

解决这个问题有一个朴素的思路

比如

when c/2 = 5 2 3 6 5 1 8 7 3
差值 -3 -2 1 0 -4 3 2 -2
累计差值 -3 -5 -4 -4 -8 -5 -3 -5
动作 sent 3 sent 2 collect 1 / sent
4-1 = 3
collect collect
位置 极小
sent 5
最小
3 = -8-(-5)

在每一次delta由负转正的时候,就做一次 collect - need,如果是复数,那么 sent addtional bikes,否则从collect当中拿取。

??这里作者说的累计差值的全局最小就是所需借出的车子是什么意思呢?

技术图片

凑合看吧 ??

累计值的意义是 一段时间内系统(好像有点高级hhh)将要向外界序求或者提供的资源。

最低的累计值意思就是,系统最多向外界需求的资源。

技术图片

也就是说,当满足最低累计值之后,相当于满足了之前所有的需求(红色箭头),注意这里面存在系统自给自足的部分(累计值上升)。

也可以这样理解:相当于给系统一个橙色线所示的 ,满足了系统自给自足的所有需求

啊,分析完了其实感觉还是没太分析到点子上

花了一两个小时啊~感觉效率挺低

我真的不太清楚原作者是怎么想到用这个指标来度量的~

柳婼大神其实是用了dfs 动态建立 temppath 并且模拟决策过程实现的,感觉这个还是现实一点~

但是那个累积最低值还是很神奇啊!

7.30

洛谷日报:尺取法小结

也叫双指针法

POJ 3061

// POJ 3061
#include<stdio.h>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 100000 + 100;
const int MAX = 0x3f3f3f3f;
int n;
int s;
ll sum;
int a[N];
int main(){
	int nn;
	scanf("%d",&nn);
	while(nn--){
		scanf("%d %d",&n,&s);
		for(int i = 0;i<n;i++){
			scanf("%d",&a[i]);
		}
		int l = 0;
		int r = 0;
		int ans = MAX;
		sum = 0;
		while(1){
			while(r<n&&sum<s)sum += a[r++];
			if(sum<s)break;
			ans = min(ans,r-l);
			sum -= a[l++];
			if(l>=n)break;
		}
		if(ans == MAX)printf("0
");
		else printf("%d
",ans); 
	}
	return 0;
}

7.31

ACM 80Days

// 80 Days 
#include<stdio.h>
typedef long long ll;
const int N = 2000100;
int n;
int c;
int a[N];
int b[N];
int main(){
	int nn;
	scanf("%d",&nn);
	while(nn--){
		scanf("%d %d",&n,&c);
		ll sum = c;
		for(int i = 0;i<n;i++){
			scanf("%d",&a[i]);
		}	
		for(int i = 0;i<n;i++){
			scanf("%d",&b[i]);
		}
		for(int i = 0;i<n;i++){
			a[i] = a[i]-b[i];
			a[i+n] = a[i];
		}
		int l = 0;
		int r = 0;
		while(1){
			while(r<2*n && sum >= 0 && r-l+1 <= n){
				sum += a[r]; 
				r ++;
			}
			if(sum>=0 && r-l+1 > n)break;
			while(sum<0 && l<r && l<n){
				// 不符合要求
				sum -= a[l];
				l ++;
			}
			if(l>=n)break;
		} 
		if(l>=n)printf("-1
");
		else printf("%d
",l+1);
	} 
}

两道尺取的题目分析一下

  • 如何获得区间
    • POJ 3061 要求的是最短区间长度,靠的区间右侧尽可能扩展,然后左侧收缩的方法
    • 80 Days 因为需要最左侧的可行区间,所有右边界不需要扩展的过大,只需要移动到构成长度为n的区间就可以了
  • 不同的题目需要考虑不同的区间生成策略



以上是关于DSA 2020-7的主要内容,如果未能解决你的问题,请参考以下文章

源代码防泄密-DSA数据安全隔离

使用GO代码实现 百度联盟媒体平台的DSA签名

使用GO代码实现 百度联盟媒体平台的DSA签名

OpenGL 纹理 DSA 不显示纹理

DSA_02:复杂度分析

c_cpp 示例代码为IOTM(类似脑力的语言),NTU DSA 2017,第42组。