递归二分不分家~~~~
递归
递归的宏观描述
将解答的应用场景扩大到原问题的状态空间,并且扩展过程中每个步骤有相似性,则可以考虑递归和递推。
推导路线难以确定,从路线上反向回溯的遍历方式是递归。
假如我们能够做到:缩小问题状态空间规模,尝试求解规模缩小后的问题,找到规模缩小后的问题可以将答案扩展,如果失败去寻找其他变换路线直到确定无解。
其中规模缩小后的子问题用原问题解决是递归,而求解子问题失败寻找其他路径是回溯。
换言之,递归的基本单元是缩小、求解和拓展。
递归的微观描述
不按顺序当然是因为懒
对于一个计算机来说,它将函数的参数依次入栈,然后执行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=1pB⋅kii
A
B
=
∏
i
=
1
n
p
i
B
⋅
k
i
然后约数之和为:
为什么的话。。我也解释不清,不过感觉展开以后挺对的(x)
一个直观的感受,我们可以带等比数列求和公式
快速幂+exgcd求逆元即可,复杂度似乎 O(n−−√log n) O ( n l o g n ) ?然而。。显然这个不是现在要做的。
lyd给了一种玄学的公式:
c为奇数: sum(p,c)=(1+p+...+pc−12)+pc+12(1+p+...+pc−12)=(1+pc+12)(1+p+...+pc−12)=(1+pc+12)sum(p,c−12) 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+...+pc2−1)+pc2(1+p+...+pc2−1)+pc=(1+pc2)(1+p+...+pc2−1)=(1+pc2)sum(p,c2−1)+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 <iostream>
#include <cstdio>
#include <vector>
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;
}
vector<pair<int, 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<<vpi[i].first<<" "<<vpi[i].second<<endl;
ans = (ans * solve(vpi[i].first, b * vpi[i].second)) % M;
}
cout<<ans<<endl;
}
仍然是细节极多。。
cf896A Nephren gives a riddle
本来这里应该是那道宽叔图。。但时间关系来不及了所以换一道类似的。。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
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<<x<<" "<<y<<endl;
if(x == 0) {
cout<<s[y];
return;
}
if(y < s1.size()) cout<<s1[y];
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<<s2[y - f[x - 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<<s3[y - f[x - 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
]
}
}
,其中
i−j>L
i
−
j
>
L
而对
min{S[j]}
m
i
n
{
S
[
j
]
}
的维护只需要在遍历过程中同步进行就可以,故总复杂度
O(nlogn)
O
(
n
l
o
g
n
)
。
#include <iostream>
#include <cstdio>
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
)