分治策略
Diogenes教授有n个被认为是完全相同的VLSI芯片,原则上它们是可以互相测试的。教授的测试装置一次可测二片,当该装置中放有两片芯片时,每一片就对另一片作测试并报告其好坏。一个好的芯片总是能够报告另一片的好坏,但一个坏的芯片的结果是不可靠的。这样,每次测试的四种可能结果如下:
A芯片报告 B芯片报告 结论
B是好的 A是好的 都是好的,或都是坏的
B是好的 A是坏的 至少一片是坏的
B是坏的 A是好的 至少一片是坏的
B是坏的 A是坏的 至少一片是坏的
Q:
1.证明如果超过n/2个芯片是坏的,使用任何基于这种逐对检测操作的策略,教授都不能确定哪些芯片是好的。
2.如果超过n/2个芯片是好的,如何O(n)找出所有好的芯片?
解:对于问题2,只需找出1个好的芯片,就能确定所有的芯片的好坏。 粗暴的方法是拿一个芯片与其他所有芯片配对,因为好的比坏的多,就可以根据多的确定该芯片的好坏,直到找到一个好芯片为止。复杂度是O(n2)的。同时也说明了Q1(否则无法确定好坏) O(n)怎么做?递归缩小问题规模。 假设有偶数个芯片。两两配对,有a对检测结果为都是好的,b对检测结果为一好一坏,c对检测结果为都是坏的。 取a对中每对的任意一个芯片,组成a个芯片的子问题。易证明该子问题依然满足好的芯片数量多于坏的芯片。(因为b+c对中坏的 >= 好的) 如果有奇数个芯片。两两配对,有a对检测结果为都是好的,b对检测结果为一好一坏,c对检测结果为都是坏的。还有一个零头。 1.如果零头是坏的,则a对中好的芯片 > 坏的芯片,用a对去检测零头,发现有一半以上的检测结果是坏的,反言之 小于一半的检测结果是好的。 2.如果零头是好的,则a对中好的芯片 >= 坏的芯片,用a对去检测零头,发现大于等于一半的检测结果是好的。 根据结果判断零头是好是坏。如果是1,把零头弃掉;如果是2,把零头加进来,这样该子问题依然满足好的芯片数量多于坏的芯片。 如此递归缩小问题规模,每次规模至少减半。 找出一个好零件的时间T1(n) = T1(n/2)+O(n) = O(n), 找出其他好零件的时间T2(n) = n−1 = O(n), 所以总的时间T(n)=O(n)。
#define NDEBUG #include <bits/stdc++.h> #define ll unsigned long long #define st first #define nd second #define pii pair<int, int> #define pil pair<int, ll> #define pli pair<ll, int> #define pll pair<ll, ll> #define pw(x) ((1LL)<<(x)) #define lson l, m, rt<<1 #define rson m+1, r, rt<<1|1 using namespace std; /***********/ template <class T> bool scan (T &ret) { char c; int sgn; if (c = getchar(), c == EOF) return 0; //EOF while (c != '-' && (c < '0' || c > '9') ) c = getchar(); sgn = (c == '-') ? -1 : 1; ret = (c == '-') ? 0 : (c - '0'); while (c = getchar(), c >= '0' && c <= '9') ret = ret * 10 + (c - '0'); ret *= sgn; return 1; } template<typename T> inline int sgn(T a) {return a>0?1:(a<0?-1:0);} template <class T1, class T2> bool gmax(T1 &a, const T2 &b) { return a < b? a = b, 1:0;} template <class T1, class T2> bool gmin(T1 &a, const T2 &b) { return a > b? a = b, 1:0;} const int inf = 0x3f3f3f3f; const ll INF = 1e18; /***********/ const ll Mod = 1e9+7, base = 997, l = 20, N = 500000; char s[N+10]; map<ll, string> ma; void init(){ srand(time(0)); int len = 0; while(len < N) s[len++] = rand()%26+'a'; } int main(){ init(); ll hash_pow_l = 1; for(int i = 1; i <= l; i++) hash_pow_l = (hash_pow_l * base) % Mod; ll val = 0, val2 = 0; for(int i = 0; i < l; i++) val = (val * base + s[i] - 'a' + 1) % Mod; ma.insert( {val, string(s, s+l)} ); string t1, t2; for(int i = l; i < N; i++){ val = (val*base+s[i]-'a'+1)%Mod; val = (val+Mod - (s[i-l]-'a'+1)*hash_pow_l%Mod) % Mod; if(ma.find(val) == ma.end()) ma.insert( {val, string(s+i+1-l, s+i+1)} ); else{ t1 = ma[val], t2 = string(s+i+1-l, s+i+1); break ; } } cout << t1 << endl; cout << t2 << endl; val = val2 = 0; for(int i = 0; i < t1.size(); i++){ val = (val*base+t1[i]-'a'+1)%Mod; val2 = (val2*base+t2[i]-'a'+1)%Mod; cout <<i << ':' << val <<' '<<val2 << endl; } return 0; }
2.特征序列
假设抛一枚标准的硬币n次,最长连续正面的序列的期望长度有多长?
答案与lgn等阶, O(log)。
建堆的复杂度是O(n)
计数排序:先统计一遍前缀和,然后逆序插入。
基数排序最好从低有效位开始,从高有效位开始需要分段递归。
================================================
快排期望复杂度的证明:http://www.cnblogs.com/fengty90/p/3768827.html
================================================
经典算法题集锦
1. 数组a是一个不下降数组。将数组a循环移位后,求某个数出现的最左位置。O(logn)时间复杂度, O(1)空间复杂度。提示:差分
2. 一个数组,其中某个数出现的次数超过了一半。求该数。O(n)时间复杂度, O(1)空间复杂度。扩展:在一个数组中找出出现次数超过n/3的数字。
3. 给定一个未排序数组。求该数组在排完序后,相邻两数差的最大值。 O(nlogn)时间复杂度,O(n+k)时间复杂度, O(n)时间复杂度。提示:桶排序
4 给定一个未排序数组。求该数组在排完序后,连续数字的最大长度。O(nlogn)复杂度,O(n)复杂度。(hashset后,对每个数组中的数字,找其所在段的最大长度,找的同时删去找到的数).
5. 完美洗牌算法。将一个原始数组进行置换排序。
6. 二叉排序树:在一棵排序二叉树上找与给定值的差值第k小的值。O(nlogn)? O(klogn)? O(k+logn)!
7. 给定一个长度为n的有序数组A,找到离目标数m最近的一个长度为k的区间。 O(k+logn)? O(lognlogA)?O(logn+logk)!
8.给定一个长度为n的无序数组,任意两数不同。请找到任意一个局部最小值。O(logn)
拓展:给定一个n*n的无序二维数组,任意两数不同,请找到一个四联通的局部最小值。O(n) 提示:十字架划分,找到最小值,然后再选择更小的子块接着找,同时保留之前找的最小值。