lyd读书笔记 0x03递归 0x04二分

递归二分不分家~~~~


递归

递归的宏观描述

将解答的应用场景扩大到原问题的状态空间,并且扩展过程中每个步骤有相似性,则可以考虑递归和递推。
推导路线难以确定,从路线上反向回溯的遍历方式是递归。
假如我们能够做到:缩小问题状态空间规模,尝试求解规模缩小后的问题,找到规模缩小后的问题可以将答案扩展,如果失败去寻找其他变换路线直到确定无解。
其中规模缩小后的子问题用原问题解决是递归,而求解子问题失败寻找其他路径是回溯。
换言之,递归的基本单元是缩小、求解和拓展。

递归的微观描述

不按顺序当然是因为懒
对于一个计算机来说,它将函数的参数依次入栈,然后执行call(address)语句。该指令将返回地址入栈,然后跳转到address。函数返回执行ret语句,将返回地址出栈,跳到该地址继续执行。局部变量在栈中复原,而作用范围超过此函数的变量、new或malloc分配空间则保存在堆中。栈指针、返回值、局部的运算通过寄存器完成。
因此,声明过多局部变量会造成栈溢出;非局部变量需要还原现场。
当然递归程序由此可以改写成非递归的,即手工模拟栈。
联赛似乎用不到的样子。。那就 挖个坑吧


递归基本模型

递归实现指数型枚举:直接讨论选和不选,然后回溯即可。
递归实现组合型枚举:加一个剪枝

if(chosen.size() > m || chosen.size() + (n - x + 1) < m)
    return;

递归实现排列型枚举:模板千千万

sumdiv

这题。。童年阴影啊
由唯一分解定理(这是啥别管,反正结论显然) A=ni=1pkii A = ∏ i = 1 n p i k i
AB=ni=1pBkii A B = ∏ i = 1 n p i B ⋅ k i
然后约数之和为:

k=1ni=0Bckpik ∏ k = 1 n ∑ i = 0 B ⋅ c k p k i

为什么的话。。我也解释不清,不过感觉展开以后挺对的(x)
一个直观的感受,我们可以带等比数列求和公式
S=k=1n1pBckk1pk S = ∏ k = 1 n 1 − p k B ⋅ c k 1 − p k

快速幂+exgcd求逆元即可,复杂度似乎 O(nlog n) O ( n l o g   n ) ?然而。。显然这个不是现在要做的。
lyd给了一种玄学的公式:
c为奇数: sum(p,c)=(1+p+...+pc12)+pc+12(1+p+...+pc12)=(1+pc+12)(1+p+...+pc12)=(1+pc+12)sum(p,c12) s u m ( p , c ) = ( 1 + p + . . . + p c − 1 2 ) + p c + 1 2 ( 1 + p + . . . + p c − 1 2 ) = ( 1 + p c + 1 2 ) ( 1 + p + . . . + p c − 1 2 ) = ( 1 + p c + 1 2 ) s u m ( p , c − 1 2 )
c为偶数: sum(p,c)=(1+p+...+pc21)+pc2(1+p+...+pc21)+pc=(1+pc2)(1+p+...+pc21)=(1+pc2)sum(p,c21)+pc s u m ( p , c ) = ( 1 + p + . . . + p c 2 − 1 ) + p c 2 ( 1 + p + . . . + p c 2 − 1 ) + p c = ( 1 + p c 2 ) ( 1 + p + . . . + p c 2 − 1 ) = ( 1 + p c 2 ) s u m ( p , c 2 − 1 ) + p c
这样分治加快速幂可A。

#include 
#include 
#include 

using namespace std;

#define LL long long
#define X first
#define Y second
#define M 9901
#define mp make_pair

inline LL ksm(LL a, LL b) {
    int ans = 1;
    while(b) {
        if(b & 1) ans = a * ans % M;
        a = a * a % M;
        b >>= 1;
    }
    return ans;
} 

inline LL solve(LL p, LL c) {
    if(!p) return 0;
    if(!c) return 1;
    if(c & 1) 
        return (1 + ksm(p, (c + 1 >> 1))) * solve(p, (c - 1) >> 1) % M;
    else 
        return ((1 + ksm(p, c >> 1)) * solve(p, (c >> 1) - 1) % M + ksm(p, c)) % M; 
}

vectorint, int> > vpi;
bool notprime[10003];
int prime[10003], cnt;
inline void getPrime() {
    prime[1] = 2; cnt = 1;
    for(int i = 3; i <= 10003; i += 2) {
        if(notprime[i]) continue;
        prime[++cnt] = i;
        for(int j = 2; i * j < 10003; ++j)
            notprime[i * j] = 1;
    }
}
inline void fj(LL x) {
    int qwq = 0;
    for(int i = 1; i <= cnt; ++i) {
        qwq = 0;
        while(x % prime[i] == 0)
            x /= prime[i], ++qwq;
        if(qwq) vpi.push_back(mp(prime[i], qwq));
    }
    if(x != 1) vpi.push_back(mp(x, 1));
}

int main() {
    getPrime();
    int a, b;
    cin>>a>>b;
    //a %= M; b %= M;
    fj(a);
    //puts("here");
    //a %= M, b %= M;
    LL ans = 1;
    for(int i = 0; i < vpi.size(); ++i) {
        //cout<
        ans = (ans * solve(vpi[i].first, b * vpi[i].second)) % M;
    }
    cout<

仍然是细节极多。。


cf896A Nephren gives a riddle

本来这里应该是那道宽叔图。。但时间关系来不及了所以换一道类似的。。

#include 
#include 
#include 
#include 
using namespace std;
typedef unsigned long long LL;
#define MAXN 100003
#define INF 1e18
const string s = "What are you doing at the end of the world? Are you busy? Will you save us?";
const string s1 = "What are you doing while sending \"";
const string s2 = "\"? Are you busy? Will you send \"";
const string s3 = "\"?";
LL f[MAXN];
LL sb = s.size();
void dfs(LL x, LL y) {
//  cout<
    if(x == 0) {
        cout<return;
    }
    if(y < s1.size()) cout<else if(y < s1.size() + f[x - 1]) dfs(x - 1, y - s1.size());
    else if(y < s1.size() + f[x - 1] + s2.size()) cout<1] - s1.size()];
    else if(y < s1.size() + f[x - 1] + s2.size() + f[x - 1]) dfs(x - 1, y - s1.size() - f[x - 1] - s2.size());
    else cout<1] - f[x - 1] - s1.size() - s2.size()];
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    f[0] = s.size();
    for(int i = 1; i <= 100000; ++i) {
        f[i] = (f[i - 1] << 1) + s1.size() + s2.size() + s3.size(); 
        if(f[i] > INF) f[i] = INF;
    }
    LL q, x ,y;
    cin>>q;
    while(q--) {
        cin>>x>>y;
        if(y > f[x]) cout<<".";
        else dfs(x, y - 1);
    }
    return 0;
}

(我以后一定填这坑.jpg)


二分

二分查找

x或x的后继

while(l < r){
    int mid = l + r >> 1;
    if(a[mid] >= x) r = mid;
    else l = mid + 1;
}

x或x的前驱

while(l < r){
    int mid = l + r + 1 >> 1;
    if(a[mid] <= x) l = mid;
    else r = mid - 1;
}

果然我是那90%啊。注意配套mid取法。
正确做法:
先确定左右半段哪一个是可行区间,以及mid归属
然后选择上面的两种形式。
如果最终二分终止在越界下标,那就表明a中不存在。
当然。。我这种蒟蒻一般是写lower_bound和upper_bound的。。


实数域上的二分

两种做法:
一种是制定eps精度,一种是固定循环迭代次数。


三分法

单峰函数指的是只有唯一极大值点的函数,单谷函数指的是只有唯一极小值点的函数。
对于单峰函数的极值我们通常使用三分法。
[L,R] [ L , R ] 内选取 lmid l m i d rmid r m i d 做三等分点,如果 f(lmid)<f(rmid) f ( l m i d ) < f ( r m i d ) ,则取 r=rmid r = r m i d ,否则取 l=lmid l = l m i d


二分答案转化为判定

然而这东西理论并没有用。。看栗子吧
最大化最小值是一个标志
然后我们就去实数域上二分T
判定的话暴力枚举一遍看看是否超m即可
二分最难的是写check啊。。


POJ2018 Best Cow Fences

对答案二分,难点还是在check。
我们要求的是,是否存在一个不小于L的子段使得平均数不小于二分的值。所以首先对数列每一个元素减去平均数,转换为一个求限定长度的最大子段和的问题
我们可以维护一个前缀和,然后进行讨论:
max{S[i]S[j]}=max{S[i]min{S[j]}} m a x { S [ i ] − S [ j ] } = m a x { S [ i ] − m i n { S [ j ] } } ,其中 ij>L i − j > L
而对 min{S[j]} m i n { S [ j ] } 的维护只需要在遍历过程中同步进行就可以,故总复杂度 O(nlogn) O ( n l o g n )

#include 
#include 
using namespace std;

#define db double

int N, L;
db A[100003], cf[100003], sum[100003], mi, ans; //A要开double。。

bool check(db x) { //传参数传double。。
    mi = 1e10, ans = -1e10;
    for(int i = 1; i <= N; ++i) cf[i] = A[i] - x;
    for(int i = 1; i <= N; ++i) sum[i] = sum[i - 1] + cf[i]; \
    for(int i = L; i <= N; ++i) {
        mi = min(mi, sum[i - L]);
        ans = max(ans, sum[i] - mi);
    }
    if(ans >= 0) return true;
    else return false;
}

int main() {
    //freopen("test.out", "w", stdout);
    scanf("%d%d", &N, &L);
    for(int i = 1; i <= N; ++i) scanf("%lf", &A[i]);
    db l = -1e6, r = 1e6, eps = 1e-5, mid;
    while(r - l > eps) {
        mid = (l + r) / 2;
        for(int i = 1; i <= N; ++i) cf[i] = A[i] - mid;
        for(int i = 1; i <= N; ++i) sum[i] = sum[i - 1] + cf[i];
        mi = 1e10, ans = -1e10;
        if(check(mid)) l = mid;
        else r = mid;
    }   
    cout << (int)(r * 1000) << endl;
    return 0; 
} 

Innovative Business

lyd原创题?
不是很懂。。第一次见到交互题
思路还是很显然的,假如我们前k个已经确定,那么就二分查找出一个合理的位置插入。复杂度 O(nlogn) O ( n l o g n )

你可能感兴趣的:(读书笔记,基础算法)