zoukankan      html  css  js  c++  java
  • 一些基础知识

    模拟

    按题中要求找规律,模拟操作,这种题可以巨难....

    1. 提前写好所有的流程
    2. 每个操作模块化(函数、结构体、类等)
    枚举:DFS/BFS
    • DFS:枚举所有可以到达目标状态的路径,对一个传入状态,遍历它的子状态,对每一个都进行判断,若合法,则递归搜索这一子状态,直到达成目标条件或者状态不合法,则回溯到父亲节点,搜索下一个子状态。求解某个方案数之类的问题。
    bool judge(参数列表){
        //参数不合法或越界时返回false 否则返回true
    }
    void dfs(传入状态){
        if(达成目标条件){
        	输出信息或者修改信息;
            return;
        }
        for(遍历传入状态的所有子状态){
            if(judge(子状态)){
                传入状态加标记;
                dfs(子状态);
                传入状态取消标记//回溯
            }
        }
    }
    
    • BFS: 找到最快到达目标的一条路径,借助队列实现,队列不为空时,弹出队首元素A,判断A,状态A的子状态中未标记过的状态均入队,加标记。(先搜索这一层的状态是否有最终目标,若找到目标,进行相关操作,结束bfs函数。否则,接着搜索这一层所有状态的子状态, ...)求解到达某方案的最短步骤之类的问题
    void judge(node){
        if(非法条件为真 || 范围越界) return false;
        return true;
    }
    void bfs(A){
        queue<node> q;
        q.push(A);
        while(!q.empty()){
            temp = q.front();
            q.pop();
            if(temp 达成目标条件){
                输出信息;
            }
            for(/*遍历所有子状态*/){
                if(judge(temp)){//子状态合法则入队
                    q.push(temp);
                    ++vis[temp];//每个节点只访问一次
                }     
            }
        }
    }
    
    • 双向bfs,起点与终点同时开始搜索,每到一层先检查两边的状态是否相接(判断相接状态),有解时,状态数远小于bfs
    二分,题目满足:最大,最小,二段性,单调性...
    • 实数二分
    while(r - l > res){
    	int mid = (r + l) / 2;
    	if(ans 在左区间) r = mid;//右区间相反
    	else l = mid;
    }
    

    Best Cow Fences:对一个数列,求一个平均值最大、长度不小于f的子段。前缀和 + 二分答案 范围0——2000 ,计算mid,a[i]减去mid后计算前缀和,看是否有一段长度 >= f 且 前缀和>= 0

    // 左区间...目标区间...右区间
    bool check(double mid){//判定当前值是否可取 
    	for(int i=1;i<=n;i++) sum[i] = val[i]-mid+sum[i-1];
    	double mi = 1e10,ans = -1e10;
    	for(int i=f;i<=n;i++){//区间长度最短为f
    		mi = min(mi,sum[i-f]);//前缀和最小的左区间 
    		ans = max(ans,sum[i]-mi);
    		if(ans >= 0) return true;
    	} 
    	return false;
    }
    int main(void){
    	ios_base::sync_with_stdio(0);
    	cin.tie(0),cout.tie(0);
    	double l,r,mid;
    	cin >> n >> f;
    	for(int i=1;i<=n;i++) cin >> val[i];
    	l = 0;r = 2000;
    	while(r-l > 1e-5){
    		mid = (l+r)/2;	
    		if(check(mid)) l = mid;
    		else r = mid;
    	}
    	cout << int(r*1000) << endl;
    	return 0;
    }
    
    • 整数二分(两种情况)
    while(r > l){
    	int mid = (r + l) / 2;
    	if(ans 在左区间) r = mid;
    	else l = mid + 1;
    }
    
    while(r > l){
    	int mid = (r + l + 1) / 2;//防止死循环
    	if(ans 在右区间) l = mid;
    	else r = mid - 1;
    }
    
    前缀和:(一维)预处理一个数组,每个元素的值为:另一数组当前索引元素和之前所有元素和
    • 计算区间和 -- O(1)

    • 只能处理静态数组

    • 二维:左上角元素和,计算区间和(容斥原理)

    //一维
    sum[i] = sum[ i - 1] + a[i];//索引从1开始,避免处理边界问题
    i~j的区间和:ans = sum[j] - sum[i - 1]
    //二维
    sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j]//索引处理同上
    a,b ~ c,d的区间和
    ans = sum[c][d] - sum[c][b - 1] - sum[a - 1][d] + sum[a - 1][b - 1]
    

    codeforces 1352E

    一个数组中,存在一些特殊元素:数组中存在一些区间和 == a[i];求出数组中特殊元素个数。

    • 1 <= n <= 8000,1 <= a[i] <= n
    • n最大直到8000,所以O(n^2)以内的算法均可。
    • 使用前缀和,直接计算所有的区间和,同时判断是否 <= n,若是,则记录下来(记录数组范围 <= n)
    • 遍历数组,计算数组中有多少个元素被记录
    int ans,t,v,n,a[8010],s[8010],book[8010];//需要处理的最大范围:[1,8000] 
    //数据量很小,暴力+前缀和 记录所有的长度大于2的子序列和 找符合条件的 
    int main(void){
    	ios::sync_with_stdio(false);
    	cin.tie(0);cout.tie(0);
    	cin >> t;
    	while(t--){
    		ans = s[0] = 0;
    		memset(book,0,sizeof(book));
    		cin >> n;
    		for(int i = 1; i <= n; i++){//索引从1开始,避免处理边界问题
    			cin >> a[i];
    			if(i > 1){
    				s[i] = s[i - 1] + a[i];
    				for(int j = 0; j < i - 1; j++){//r - l >= 2
    					v = s[i] - s[j];
    					if(v <= n && !book[v]) book[v] = 1;
    				} 
    			}else s[i] = a[i];
    		}
    		for(int i = 1; i <= n; i++) if(book[a[i]]) ++ans;
    		cout << ans << endl;
    	}
    	return 0;
    } 
    
    差分:是一种和前缀和相反的策略,数组中每一个元素记录原数组中与前一项的差值,对这个数组求前缀和即可恢复原数组
    • 维护多次对若干区间加上某个数,最后若干次询问某一位的数...

    • 每次对最左端的点a[1]先增加相应的值,对应修改a[l],a[r]更新区间和

    codedforces 1343D Constant Palindrome Sum

    输入由n个整数组成的数组a,和整数k,替换数组中de一些元素为[1,k]中的任一元素,使数组满足:

    1. a[i] <= k
    2. a[i] + a[n - i + 1] = x,即对称的元素和为定值( 2 <= x <= 2k)
    • 求最小替换元素的个数

    对于确定的x,每对元素的交换次数确定

    1. a + b = x
    2. x ∈[min(a,b) + 1,max(a,b) + k + 1),半开半闭的区间方便合并,获得最小和,最大和,差分,此区间 + 1--起始位置 + 2,左边界(闭)-1,右边界(开) +1
    3. 其它

    交换次数存在一个最大值,当x = k + 1,为 n / 2(所有元素值的区间[1,k]);对每一对元素,计算交换次数为1的区间,使用差分思想通过cnt数组在区间外+2,区间内+1,对每一对元素的和特殊处理;在枚举完所有的元素对后,将差分数组cnt修改为前缀和即为相应x的替换次数,ans 初始化为n / 2,遍历cnt数组寻找最小替换次数。。。

    const int kN = 2e5 + 10,kM = 4e5 + 5;
    int t,n,k,l,r,ans,a[kN],cnt[kM];
    //替换最少数量的元素,数组中对称元素和满足 a[i] + a[n - i + 1] = x;x为某个定值 
    int main(void){
    	ios::sync_with_stdio(false);
    	cin.tie(0);cout.tie(0);
    	cin >>t;
    	while(t--){
    		memset(cnt,0,sizeof(cnt));
    		cin >> n >> k;
    		for(int i = 1; i <= n; i++) cin >> a[i];
    		for(int i = 1; i <= n / 2; i++){
    			l = min(a[i],a[n - i + 1]) + 1;
    			r = max(a[i],a[n - i + 1]) + k + 1;//区间左闭右开,便于合并
    			cnt[1] += 2;//差分 边界[2,l) 需要两次 
    			cnt[l]--;//[l,r] 一次 
    			cnt[r]++;
    			cnt[a[i] + a[n - i + 1]]--;//0
    			cnt[a[i] + a[n - i + 1] + 1]++; 
    		} 
    		for(int i = 2; i <= 2 * k; i++) cnt[i] += cnt[i - 1];//计算各个x值的替换次数 
    		ans = n / 2;
    		for(int i = 1; i <= 2 * k; i++) ans = min(ans,cnt[i]);
    		cout << ans << endl;
    	}
    	return 0;
    }
    
    贪心:尽量选择当前最优状态
    • 排序后按最大值or最小值取(离线)
    • 每次都取当前最大or最小的xxx,更新xxx(在线)

    codeforces 1352G

    长为n的数组中,1~n每个数字只出现一次,在这个数组中,相邻元素差的绝对值的范围[2,4]。

    • 贪心:
      • n < 4,无解
      • 从大到小输出小于等于n的所有奇数。
      • 输出“4 2”
      • 输出所有小于等于n的偶数
      • method2::先输出偶数,但是n == 4的时候需要特判,之后输出"5,1,3",其它奇数升序
    int main(void){
    	ios::sync_with_stdio(false);
    	cin.tie(0);cout.tie(0);
    	cin >> t;
    	while(t--){
    		cin >> n;
    		if(n < 4) cout << "-1";
    		else{
    			s = n;
    			if(!(s & 1)) --s;//先奇数
    			while(s > 0){
    				cout << s << ' ';
    				s -= 2;
    			} 
    			cout << "4 2";
    			s = 6;
    			while(s <= n){
    				cout << ' ' << s;
    				s += 2;
    			}
    		}
    		cout << endl;
    	}
    	return 0;
    } 
    

    codeforces 1296E2

    E1思路:

    一个由小字字母组成的字符串,所有元素均被涂上了黑色 or 白色,相邻且异色的字符可交换,字符串中的字符是否可以升序排列,如果可以,给出染色方案:0 1表示两种颜色

    思路:

    异色字符才可以交换,同色字符相对顺序不会发生改变 -- 即同色字符不递减。

    贪心:s是否可分为两个不递减的子串

    • 使用c1,c2分别记录两个同色字符串的最大字符,遍历字符串,若s[i] >= c1,加入c1所在子串;若 s[i] >= c2,加入c2所在子串;若s[i] 小于 c1,c2则在同色子串中出现递减 --> "NO"

    E2与E1相比,字符的染色种类更多。需要输出染色种类最少的方案。

    思路:

    1. 最多26种染色,依然可以贪心,增加一个字符串数组存储当前各字符串的最大字符,对s[i],进行E1中相同的步骤,不同的是:标记当前s[i]是否可以加入现有的字符串中,若不可以则增加一个字符串(题目保证最多26种染色,有解。),增加一个ans数组,在选择过程中存储答案即可。
    const int kN = 2e5 + 5;
    int n,idx,ans[kN];
    bool flag;
    char c[27];
    string s;
    //26个字符,最多26种染色 
    int main(void){
    	ios::sync_with_stdio(false);
    	cin.tie(0);cout.tie(0);
    	cin >> n >> s;
    	for(int i = 1;i <= 26; i++) c[i] = 'a';
    	idx = 1;
    	for(int i = 0; i < s.length(); i++){
    		flag = true;
    		for(int j = 1; j <= idx && flag; j++){
    			if(s[i] >= c[j]){
    				c[j] = s[i];
    				ans[i] = j;
    				flag = false; 
    			}
    		}
    		if(flag){
    			c[++idx] = s[i];
    			ans[i] = idx;
    		}
    	}
    	cout << idx << endl ;
    	for(int i = 0; i < n; i++){
    		if(i) cout << ' ';
    		cout << ans[i];
    	}
    	return 0;
    }
    
  • 相关阅读:
    Add Two Numbers
    Reverse Linked List II
    Reverse Linked List
    Remove Duplicates from Sorted List
    Remove Duplicates from Sorted List II
    Partition List
    Intersection of Two Linked Lists
    4Sum
    3Sum
    2Sum
  • 原文地址:https://www.cnblogs.com/honey-cat/p/12990034.html
Copyright © 2011-2022 走看看