• 【题解】2023 DTS算法竞赛集训 第1次


    比赛地址:https://www.luogu.com.cn/contest/143650

    P1319 压缩技术

    https://www.luogu.com.cn/problem/P1319
    简单的签到模拟题

    #include //c++标准库
    using namespace std;
    int main(){
    	int a,n,t=0,i=0,b,s=0;//t判断有没有回车,i判断输出什么,s判断有没有输完
    	cin>>n;
    	while(s<n*n){
    		cin>>a;//循环输入a;
    		i++;
    		for(b=a;b>=1;b--){
    			if(t==n){cout<<endl;t=0;}//判断是否需要回车,回车后t要清零
    			if(i%2==1)cout<<0;
                else cout<<1;//判断是否i不被2整除,输出0,否则输出1,注意不要回车
    			t++;
    			s++;//t与s加一
    			}
    		}
    	cout<<endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    P8598 [蓝桥杯 2013 省 AB] 错误票据

    https://www.luogu.com.cn/problem/P8598
    这道题是判断输入的数字是否连续和重复的,那肯定是要让数字从小到大排序才能找到中断和重复数字。那排序复杂度最少是O(nlgn),是否有更快的方法?

    因为输入的数字不是按照大小排序的,非常自然的想到哈希表去处理。用哈希表h记录出现的数字的次数,最后去遍历,如果出现了0次,说明中断了,如果出现了1次以上,说明重复了。

    题目中给的数据范围是:正整数(不大于 1 0 5 10^5 105),因此哈希表的大小是1e5 + 5

    另外要注意,如果从头遍历哈希表,前面可能有许多0,要判断更多的情况,因此可以记录下输入的最大值amax和最小值amin,在这个边界[amin,amax]里去找0和大于1的值对应的下标。

    #include
    using namespace std;
    
    const int K = 1e5 + 5;
    int h[K];
    int main() {
        int N;
        cin >> N;
        
        int amin = 1e5;
        int amax = 0;
        int m, n; 
        int x;
        while (N--) {
            while (cin >> x) {
                if (++h[x] > 1) n = x;
                amin = min(amin, x);
                amax = max(amax, x);
            }
        }
        
        for (int i = amin; i <= amax; i++) {
            if (h[i] == 0) m = i;
        }
        cout << m << " " << n;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    P1115 最大子段和

    https://www.luogu.com.cn/problem/P1115
    一道经典的考研及面试题,有许多解法

    要求找出连续字串的最大和,那就需要确定左右区间[l,r],再计算这个区间和。

    1.暴力

    我们要枚举所有情况,也就是枚举出所有的区间情况,那么l取值是[0,len(s))r取值是[i,len(s)),两层for循环。确定区间后,还要遍历区间所有数字计算和,那么整体的复杂度就是O(n^3)。这个复杂度非常高。

    2.前缀和

    上面的暴力求解中,第三步计算区间和,我们理所当然的对应前缀和的知识点,可以用前缀和通过O(1)的时间去计算区间所有数字计算和。

    #include
    using namespace std;
    
    int maxSubarraySum(vector<int>& nums) {
        int n = nums.size();
        vector<int> prefixSum(n + 1, 0); // 前缀和数组,prefixSum[i]表示前i个元素的和
        for (int i = 1; i <= n; i++) {
            prefixSum[i] = prefixSum[i - 1] + nums[i - 1];
        }
    
        int maxSum = INT_MIN; // 最大和
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j <= n; j++) {
                int sum = prefixSum[j] - prefixSum[i]; // 计算从第i个元素到第j个元素的和
                maxSum = max(maxSum, sum);
            }
        }
    
        return maxSum;
    }
    
    int main() {
        int n;
        cin >> n;
    
        vector<int> nums(n);
        for (int i = 0; i < n; i++) {
            cin >> nums[i];
        }
    
        int maxSum = maxSubarraySum(nums);
        cout << maxSum;
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    但是用前缀和虽然把时间复杂度降到了O(n^2),但是依旧有的测试点过不了,我们还需要复杂度更低的代码。

    在这里插入图片描述

    3.贪心

    考虑更低的复杂度,我们思考如何用O(n)的时间解决,也就是遍历一遍这个数组。

    采用贪心的思想,记录最大和maxSum(当前为止最大的子串和)和当前和currentSum(当前为止选择的连续子串和)

    遍历每个数时更新这两个变量。maxSum=max(maxSum,currentSum)这个没什么好说的。在更新currentSum时,如果 c u r r e n t S u m < 0 currentSum<0 currentSum<0,就说明从之前的起点 l l l到当前下标 i i i这段 [ l , i ] [l,i] [l,i]的和 s u m [ l , i ] < 0 sum_{[l,i]}<0 sum[l,i]<0。往后再加后面的数字 a [ i + 1 ] a[i+1] a[i+1]时,如果 l l l不变,有 s u m [ l , i ] + a [ i + 1 ] < a [ i + 1 ] sum_{[l,i]}+a[i+1]sum[l,i]+a[i+1]<a[i+1],那我们肯定是要舍弃 [ l , i ] [l,i] [l,i]这一段的,从 i + 1 i+1 i+1开始重新计算,也就是令 l = i + 1 , c u r r e n t S u m = 0 l=i+1, currentSum=0 l=i+1,currentSum=0

    #include
    using namespace std;
    
    int maxSubarraySum(vector<int>& nums) {
        int n = nums.size();
        int maxSum = INT_MIN; // 最大和
        int currentSum = 0; // 当前和
    
        for (int i = 0; i < n; i++) {
            currentSum += nums[i];
    
            if (currentSum > maxSum) {
                maxSum = currentSum;
            }
    
            if (currentSum < 0) {
                currentSum = 0;
            }
        }
    
        return maxSum;
    }
    
    int main() {
        int n;
        cin >> n;
    
        vector<int> nums(n);
        for (int i = 0; i < n; i++) {
            cin >> nums[i];
        }
    
        int maxSum = maxSubarraySum(nums);
        cout << maxSum;
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    降低复杂度之后可以通过全部的样例点。

    在这里插入图片描述

    4.动态规划

    另一种思路是动态规划。

    可以令 d p [ i ] dp[i] dp[i]表示:以 i i i结尾的连续子串最大和。

    那么考虑所有的情况,结果应该是: r e s = m a x 1 ≤ i ≤ n d p [ i ] res=max_{1\leq i \leq n} dp[i] res=max1indp[i]

    重点是状态转移方程。遍历到 i i i时,因为要求区间连续,只有两种情况:用 [ l , i − 1 ] [l,i-1] [l,i1]和不用 [ l , i − 1 ] [l,i-1] [l,i1]。如果用的话,那新的区间是 [ l , i ] [l,i] [l,i];如果不用,那新的区间是 [ i , i ] [i,i] [i,i]。因此有: d p [ i ] = m a x ( d p [ i − 1 ] + a [ i ] , a [ i ] ) dp[i] = max(dp[i-1]+a[i],a[i]) dp[i]=max(dp[i1]+a[i],a[i])

    另外由于 d p [ i ] dp[i] dp[i]只跟 d p [ i − 1 ] dp[i-1] dp[i1]有关,dp数组可以用滚动数组优化空间。

    时间复杂度是 O ( n ) O(n) O(n),空间复杂度是 O ( 1 ) O(1) O(1),与贪心相同。

    #include
    using namespace std;
    
    int main() {
        int n;
        cin >> n;
        int ans = INT_MIN;
        int dp = 0;
        for (int i = 1; i <= n; i++) {
            int x;
            cin >> x;
            dp = max(x, dp + x);
            ans = max(ans, dp);
        }
        cout << ans;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    P1002 [NOIP2002 普及组] 过河卒

    https://www.luogu.com.cn/problem/P1002
    首先这道题如果用搜索,每个节点两种状态,需要用dfs递归很多层。因此看看能不能用动态规划去优化重复子问题。

    动态规划,每个位置只能从上面或右面走到,对应两个状态转移:
    d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] dp[i][j]=dp[i-1][j]+dp[i][j-1] dp[i][j]=dp[i1][j]+dp[i][j1]
    另外要注意,马对应上面的位置下标有可能越界,为了方便起见,我们将所有的坐标对应的+2

    #include 
    
    using namespace std;
    long long int dp[40][40], ma[40][40];
    int n, m, a, b;
    int main() {
    	cin >> n >> m >> a >> b;
    	n += 2, m += 2, a += 2, b += 2;
    	ma[a][b] = 1;
    	ma[a - 1][b + 2] = 1;
    	ma[a - 1][b - 2] = 1;
    	ma[a + 1][b - 2] = 1;
    	ma[a + 1][b + 2] = 1;
    	ma[a + 2][b - 1] = 1;
    	ma[a + 2][b + 1] = 1;
    	ma[a - 2][b - 1] = 1;
    	ma[a - 2][b + 1] = 1;
    	dp[1][2] = 1;
    	for (int i = 2; i <= n; i++) {
    		for (int j = 2; j <= m; j++) {
    			if (ma[i][j] == 1) {
    				continue;
    			}
    			else dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
    		}
    	}
    	cout << dp[n][m];
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
  • 相关阅读:
    身份认证——手机验证码的登录、邮箱密码登录、二维码登录等——cookie和session的原理
    java-net-php-python-java幼儿早教管理系统查重PPT计算机毕业设计程序
    工业级路由器如何异地组网及其作用
    element ui修改select选择框背景色和边框色
    机器人中的数值优化(十九)—— SOCP锥规划应用:时间最优路径参数化(TOPP)
    KEIL5添加沁恒的ch55x芯片(其他非arm和stm32芯片也可使用类似的方法)
    javaweb-响应字符数据
    学习平台助力职场发展与提升
    CSS盒子模型
    一网打尽!10款数据可视化软件介绍
  • 原文地址:https://blog.csdn.net/samsara_of_ice/article/details/134267876