• 一些基础知识


    模拟

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

    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;
    }
    
  • 相关阅读:
    QT项目升级(QT4.6.3到QT5.2)时,遇到的问题和解决方法
    hdu 5312 Sequence(数学推导——三角形数)
    Windows 操作系统与 .NET Framework
    Cocos2d-x手机游戏开发与项目实践具体解释_随书代码
    Uva 10081 Tight words (概率DP)
    从零使用qemu模拟器搭建arm执行环境
    hdu 2255 奔小康赚大钱(KM算法)
    最大子序列和问题
    深入理解javascript之高级定时器
    openstack 镜像自动扩容 resize拉伸
  • 原文地址:https://www.cnblogs.com/honey-cat/p/12990034.html
Copyright © 2020-2023  润新知