柯氏模板(上)

柯氏模板(中)
柯氏模板(下)
pdf下载
本模板博主还在完善ing…谢谢大家观看
数据结构:
1.RMQ (区间最值,区间出现最大次数,求区间gcd)
2.二维RMQ求区间最大值 (二维区间极值)
3.线段树模板(模板为区间加法) (线段树染色) (区间最小值)
4.线性基 (求异或第k大)
5.主席树(静态求区间第k小) (区间中小于k的数量和小于k的总和) (区间中第一个大于或等于k的值)
6.权值线段树 (求逆序对)
7.动态主席树 (主席树+树状数组) (区间第k大带修改)
8.树上启发式合并 (查询子树的优化)
9,树状数组模板 (求区间异或和,求逆序对) 扩展
10.区间不重复数字的和 (树状数组)
11.求k维空间中离所给点最近的m个点,并按顺序输出(KD树)
12.LCA (两个节点的公共父节点)
动态规划:
1.LIS (最长上升子序列)
2.有依赖的背包 (附属关系)
3.最长公共子序列(LCS)
4.树形DP
5.状压DP-斯坦纳树
6.背包
7.dp[i]=min(dp[i+1]…dp[i+k]),multset
博弈:
1.NIM博弈 (n堆每次最少取一个)
2.威佐夫博弈(两堆每次取至少一个或一起取一样的)
3.约瑟夫环
4.斐波那契博弈 (取的数依赖于对手刚才取的数)
5.sg函数
数论:
1.数论 素数检验:普通素数判别 线性筛 二次筛法求素数 米勒拉宾素数检验
2.拉格朗日乘子法(求有等式约束条件的极值)
3.裂项(多项式分子分母拆分)
4.扩展欧几里得 (ax+by=c)
5.勾股数 (直角三角形三边长)
6.斯特林公式 (n越大越准确,求n!)
7.牛顿迭代法 (求一元多次方程一个解)
8.同余定理 (a≡b(mod m))
9.线性求所有逆元的方法求 (1~p modp的逆元)
10.中国剩余定理(n个同余方程x≡a1(modp1))
11.二次剩余((ax+k)2≡n(modp)(ax+k)^2≡n(mod p)(ax+k)2≡n(modp))
12.十进制矩阵快速幂(n很大很大的时候)
13.欧拉函数
14.费马小定理
15.二阶常系数递推关系求解方法 (a_n=p*a_{n-1}+q*a_{n-2})
16.高斯消元
17.矩阵快速幂
18.分解质因数
19.线性递推式BM(杜教)
20.线性一次方程组解的情况
21.求解行列式的逆矩阵,伴随矩阵,矩阵不全随机数不全
组合数学:
1.循环排列 (与环有关的排列组合)

数据结构:

1.RMQ(区间最值)

RMQ算法,是一个快速求区间最值的离线算法,预处理时间复杂度O(n*log(n)),查询O(1), 所以是一个很快速的算法,当然这个问题用线段树同样能够解决。

1、求区间的最大值和最小值!

#include <bits/stdc++.h>
using namespace std;
const int maxn=100117;
int n,query;
int num[maxn];int F_Min[maxn][20],F_Max[maxn][20];void init(){for(int i=1;i<=n;i++){F_Min[i][0]=F_Max[i][0]=num[i];}for(int i=1;(1<<i)<=n;i++){  //按区间长度递增顺序递推for(int j=1;j+(1<<i)-1<=n;j++){  //区间起点F_Max[j][i]=max(F_Max[j][i-1],F_Max[j+(1<<(i-1))][i-1]);F_Min[j][i]=min(F_Min[j][i-1],F_Min[j+(1<<(i-1))][i-1]);}}
}int Query_max(int l,int r){int k=(int)(log2(double(r-l+1)));return max(F_Max[l][k],F_Max[r-(1<<k)+1][k]);
}int Query_min(int l,int r){int k=(int)(log2(double(r-l+1)));return min(F_Min[l][k],F_Min[r-(1<<k)+1][k]);
}int main(){int a,b;scanf("%d %d",&n,&query);for(int i=1; i<=n;i++)scanf("%d",&num[i]);init();while(query--){scanf("%d %d",&a,&b);printf("区间%d到%d的最大值为:%d\n",a,b,Query_max(a,b));printf("区间%d到%d的最小值为:%d\n",a,b,Query_min(a,b));printf("区间%d到%d的最大值和最小值只差为:%d\n",a,b,Query_max(a,b)-Query_min(a,b));}return 0;
}

2、求区间内出现次数最多的数字出现的次数!
对上升序列如:1 1 2 2 2 3 3 4 5 5 … 统计区间出现次数最多数个数。
我们可以构造一个b[]数组,
if(a[i]==a[i-1])b[i]=b[i-1]+1;
else b[i]=1;
这样对上述例子,b[]数组有1 2 1 2 3 1 2 1 1 2
那么对询问区间[l,r],如果l在数与数交界处,那么直接查询l,r区间最大值。
否则要知道与a[l]相同延伸到end,那么这个区间大小end-l+1,与rmq(end+1,r)取最大值就是答案。

#include <bits/stdc++.h>
using namespace std;
const int maxn=100017;
int num[maxn],f[maxn],MAX[maxn][20];
int n;
int max(int a,int b){return a>b?a:b;
}
int rmq_max(int l,int r){if(l>r)return 0;int k=log2((double)(r-l+1));return max(MAX[l][k],MAX[r-(1<<k)+1][k]);
}
void init(){for(int i=1;i<=n;i++){MAX[i][0]=f[i];}int k=log2((double)(n+1));for(int i=1;i<=k;i++){for(int j=1;j+(1<<i)-1<= n;j++){MAX[j][i]=max(MAX[j][i-1],MAX[j+(1<<(i-1))][i-1]);}}
}
int main(){int a,b,q;while(scanf("%d",&n)&&n){scanf("%d",&q);for(int i=1;i<=n;i++){scanf("%d",&num[i]);}sort(num+1,num+n+1);for(int i=1;i<=n;i++){if(i==1){f[i]=1;continue;}if(num[i] == num[i-1]){f[i]=f[i-1]+1;}else{f[i]=1;}}init();for(int i=1;i<=q;i++) {scanf("%d%d",&a,&b);int t=a;while(t<=b&&num[t]==num[t-1]){t++;}int cnt=rmq_max(t,b);int ans=max(t-a,cnt);printf("%d\n",ans);}}return 0;
}

RMQ求区间gcd
f [ l , r ]=gcd(al,al+1,…,ara_l,a_{l+1},…,a_ral​,al+1​,…,ar​),问f [ l , r ]的值和有多少对(L,R)使得f [ L, R ] = f [ l , r ]。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
int dp[100010][32];
int a[100010];
int n,m,g,j;
int gcd(int a,int b){return b?gcd(b,a%b):a;
}void rmq_init(){for(int j=1;j<=n;j++) dp[j][0]=a[j];for(int i=1;(1<<i)<=n;i++){for(int j=1;j+(1<<i)-1<=n;j++){dp[j][i]=gcd(dp[j][i-1],dp[j+(1<<(i-1))][i-1]);}}
}int rmq_qui(int l,int r){   //查询的即是区间gcdint k=(int)log2(double(r-l+1));return gcd(dp[l][k],dp[r-(1<<k)+1][k]);
}map<int,long long>mp;
void setTable(){mp.clear();for(int i=1;i<=n;i++){g=dp[i][0],j=i;  while(j<=n){int l=j,r=n;while(l<r){int mid=(l+r+1)>>1;if(rmq_qui(i,mid)==g) l=mid;else r=mid-1;}mp[g]+=l-j+1;  //相当于二分枚举相加j=l+1;g=rmq_qui(i,j);}}
}int main(){int t,l,r;int cas=1;scanf("%d",&t);while(t--){printf("Case #%d:\n",cas++);scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%d",&a[i]);rmq_init();setTable();scanf("%d",&m);for(int i=0;i<m;i++){scanf("%d %d",&l,&r);int g=rmq_qui(l,r);printf("%d %lld\n",g,mp[g]);}}
}

2.二维RMQ(求区间最大值)

建表复杂度O(nmlog(n)log(m),查询复杂度O(1)
还有一种做法是把每一行当作一维区间,求最大值(建表O(n
m*log(m)),查询O(n))

#include<bits/stdc++.h>
using namespace std;
int n,m,a[400][400];
int dp[400][400][20][20];
int p;
int r1,c1,r2,c2,k1,k2,ans;int main(){scanf("%d%d",&n,&m);  //二维区间的范围for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){scanf("%d",&a[i][j]);dp[i][j][0][0]=a[i][j];  //初始化}}int u1=0,u2=0;while((1<<(u1+1))<=n) u1++;while((1<<(u2+1))<=m) u2++;for(int i=1;i<=n;i++){for(int k=1;k<=u2;k++)for(int j=1;j+(1<<(k-1))<=m;j++){  //列建立RMQdp[i][j][0][k]=max(dp[i][j][0][k-1],dp[i][j+(1<<(k-1))][0][k-1]);}}for(int i=1;i<=m;i++){for(int k=1;k<=u1;k++){for(int j=1;j+(1<<(k-1))<=n;j++){   //行建立RMQdp[j][i][k][0]=max(dp[j][i][k-1][0],dp[j+(1<<(k-1))][i][k-1][0]);}   }}for(int i=1;i<=u1;i++){for(int j=1;j<=u2;j++){for(int k=1;k+(1<<(i-1))<=n;k++){for(int p=1;p+(1<<(j-1))<=m;p++){   //行列合并dp[k][p][i][j]=max(dp[k][p][i][j],dp[k][p][i-1][j-1]);dp[k][p][i][j]=max(dp[k][p][i][j],dp[k+(1<<(i-1))][p][i-1][j-1]);dp[k][p][i][j]=max(dp[k][p][i][j],dp[k][p+(1<<(j-1))][i-1][j-1]);dp[k][p][i][j]=max(dp[k][p][i][j],dp[k+(1<<(i-1))][p+(1<<(j-1))][i-1][j-1]);}}}}scanf("%d",&p);while(p--){scanf("%d%d%d%d",&r1,&c1,&r2,&c2);//矩形的两个对角线顶点k1=k2=0;while((1<<(k1+1))<=(r2-r1+1)) k1++;  //等于int(log2(r2-r1+1))while((1<<(k2+1))<=(c2-c1+1)) k2++;  //等于int(log2(c2-c1+1))ans=0;//下面相当于分成四块矩形 ans=max(ans,dp[r1][c1][k1][k2]);//从(r1,c1)开始长度为2^k1和2^k2ans=max(ans,dp[r2-(1<<k1)+1][c2-(1<<k2)+1][k1][k2]);//从(r2-2^k1,c2-2^k2)开始长度为2^k1和2^k2ans=max(ans,dp[r2-(1<<k1)+1][c1][k1][k2]);//从(r2-2^k1,c1)开始长度为2^k1和2^k2ans=max(ans,dp[r1][c2-(1<<k2)+1][k1][k2]);//从(r1,c2-2^k2)开始长度为2^k1和2^k2printf("%d\n",ans);}return 0;
}

线段树

a) 模板为区间加法

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1000005;
struct TREE {int tree[maxn * 4];  // 线段树int lz[maxn * 4];    // 延迟标记void init() {  //初始化memset(tree, 0, sizeof(tree));memset(lz, 0, sizeof(lz));}// 创建线段树void build(int root, int l, int r) {if (l == r) {scanf("%d", &tree[root]);return;}int mid = (l + r) >> 1;build(root << 1, l, mid);build(root << 1 | 1, mid + 1, r);tree[root] = tree[root << 1] + tree[root << 1 | 1];  //可取模return;}// 单点更新,n为更新值,index为更新点,lr为更新范围void update(int root, int n, int index, int l, int r) {if (l == r) {tree[root] = n;  // 更新方式,可以自由改动return;}int mid = (l + r) >> 1;// push_down(root,mid-l+1,r-mid); 若既有点更新又有区间更新,需要这句话if (index <= mid)update(root << 1, n, index, l, mid);elseupdate(root << 1 | 1, n, index, mid + 1, r);tree[root] = tree[root << 1] + tree[root << 1 | 1];  //可取模}void push_down(int root, int l,int r) {  //懒标记下传,若需取模,懒标记均可取模if (lz[root]) {int mid = (l + r) >> 1;lz[root << 1] += lz[root];lz[root << 1 | 1] += lz[root];tree[root << 1] += 1LL * (mid - l + 1) * lz[root];tree[root << 1 | 1] += 1LL * (r - mid) * lz[root];lz[root] = 0;}}// 区间更新,lr为更新范围,LR为线段树范围,add为更新值void update_range(int root, int l, int r, int L, int R, int add) {push_down(root, L, R);  //应放在最开始,否则会出错if (l <= L && r >= R) {lz[root] += 1LL * add;tree[root] += 1LL * (R - L + 1) * add;  //更新方式return;}int mid = (L + R) >> 1;if (mid >= l) update_range(root << 1, l, r, L, mid, add);if (mid < r) update_range(root << 1 | 1, l, r, mid + 1, R, add);tree[root] = tree[root << 1] + tree[root << 1 | 1];  //可取模}// 区间查找ll query_range(int root, int L, int R, int l, int r) {push_down(root, L, R);  //应放在最开始,否则会出错if (l <= L && r >= R) return tree[root];int mid = (L + R) >> 1;ll sum = 0;if (mid >= l) sum += query_range(root << 1, L, mid, l, r);  //可取模if (mid < r)sum += query_range(root << 1 | 1, mid + 1, R, l, r);  //可取模return sum;}
};
TREE trees;
int main() {trees.init();int N;scanf("%d", &N);// build(int root,int l,int r)//建树,数组在树中输入,root=1,lr为线段树范围trees.build(1, 1, N);// update_range(int root,int l,int r,int L,int R,int add)//区间更新 root为1,lr为更新范围,LR为线段树范围,add为更新值int x, y, k;trees.update_range(1, x, y, 1, N, k);// update(int n,int index,int l,int r,int root)// 单点更新,n为更新值,index为更新点,lr为线段树范围int i, k;trees.update(1, k, i, 1, N);// query_range(int root,int L,int R,int l,int r)// 区间查找 root为1,lr为更新范围,LR为线段树范围int x, y;trees.query_range(1, 1, N, x, y);return 0;
}

b)线段树染色

在一条线上画一些彩色的部分,一些先前画的部分可能会被后面的部分覆盖。
下面代码可以直接生成涂色后的颜色分布状况。(可以将每个点的颜色输出出来)

#include <cstdio>
#include <cstring>
#include <algorithm>
typedef long long ll;
const int maxn=1e5+10;
using namespace std;
int tree[maxn<<2];//对节点染色
void PushDown(int root){ //下传,线段树维护颜色if(tree[root]!=-1){ tree[root<<1]=tree[root<<1|1]=tree[root];tree[root]=-1;}
}
//update(1,1,8000,a+1,b,k);
void update(int root,int L,int R,int l,int r,int k){if(l<=L&&r>=R){tree[root]=k;return;}PushDown(root);int mid=(L+R)>>1;if(l<=mid)update(root<<1,L,mid,l,r,k);if(r>mid)update(root<<1|1,mid+1,R,l,r,k);
}int rec[maxn];//存储i节点的颜色
int top=0;
void query(int root,int l,int r){if(l==r){rec[top++]=tree[root];//记录节点的颜色return ;}int mid=(l+r)>>1;PushDown(root);query(root<<1,l,mid);query(root<<1|1,mid+1,r);
}int num[maxn];//记录颜色出现的段数
int main(){int N;while(scanf("%d",&N)!=EOF){ //操作次数memset(tree,-1,sizeof(tree));int a,b,k;for(int i=1;i<=N;i++){scanf("%d%d%d",&a,&b,&k); //左右范围a,b和颜色cupdate(1,1,8000,a+1,b,k); //对应颜色数 先加一}memset(rec,-1,sizeof(rec));top=0;//初始化query(1,1,8000);memset(num,0,sizeof(num));//初始化for(int i=0;i<top;i++){printf("%d\n",rec[i]);  //可以将每个点的颜色输出出来}}return 0;
}

区间最小值

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1000005;
struct TREE {int tree[maxn * 4];  // 线段树int lz[maxn * 4];    // 延迟标记void init() {  //初始化memset(tree, 0, sizeof(tree));memset(lz, 0, sizeof(lz));}// 创建线段树void build(int root, int l, int r) {if (l == r) {// scanf("%d", &tree[root]);return;}int mid = (l + r) >> 1;build(root << 1, l, mid);build(root << 1 | 1, mid + 1, r);tree[root] = tree[root << 1] + tree[root << 1 | 1];  //可取模return;}void push_down(int root, int l,int r) {  //懒标记下传,若需取模,懒标记均可取模if (lz[root]) {int mid = (l + r) >> 1;lz[root << 1] += lz[root];lz[root << 1 | 1] += lz[root];tree[root << 1] += lz[root];tree[root << 1 | 1] += lz[root];lz[root] = 0;}}// 区间更新,lr为更新范围,LR为线段树范围,add为更新值void update_range(int root, int l, int r, int L, int R, int add) {push_down(root, L, R);  //应放在最开始,否则会出错if (l <= L && r >= R) {lz[root] += 1LL * add;tree[root] += add;  //更新方式return;}int mid = (L + R) >> 1;if (mid >= l) update_range(root << 1, l, r, L, mid, add);if (mid < r) update_range(root << 1 | 1, l, r, mid + 1, R, add);tree[root] = min(tree[root << 1], tree[root << 1 | 1]);  //可取模}// 区间查找ll query_range(int root, int L, int R, int l, int r) {push_down(root, L, R);  //应放在最开始,否则会出错if (l <= L && r >= R) return tree[root];int mid = (L + R) >> 1;ll val = 0x3f3f3f3f;if (mid >= l)val = min(val, query_range(root << 1, L, mid, l, r));  //可取模if (mid < r)val = min(val,query_range(root << 1 | 1, mid + 1, R, l, r));  //可取模return val;}
};struct node {ll a, b;
} f[maxn];
bool cmp(node t, node b) {if (t.a != b.a) return t.a > b.a;return t.b > b.b;
}
pair<ll, int> p[maxn];TREE trees;
int main() {trees.init();int N;scanf("%d", &N);// build(int root,int l,int r)//建树,数组在树中输入,root=1,lr为线段树范围trees.build(1, 1, N);// update_range(int root,int l,int r,int L,int R,int add)//区间更新 root为1,lr为更新范围,LR为线段树范围,add为更新值int x, y, k;trees.update_range(1, x, y, 1, N, k);  //区间修改int x, y;cout << trees.query_range(1, 1, N, x, y) << endl;  //输出区间最小值return 0;
}

4.线性基(求异或第k大)

构造出 n 个数的线性基,然后将每一位相互独立出来,然后看有多少个不唯一的,存在一个新的数组里,如果在新的数组中有K个元素,则异或出来的最终答案最多有 2^k , 当然这里面是包括 0 的,但是并不是所有的都可以异或出来 0 ,那要怎么确定这个能否异或出来 0 呢?如果构造出来的线性基有 k 位不为 1,那么说明每个数都提供了 1,则说明不会异或出 0 ,否则可以。
新数组只会对那一位产生影响。不会影响到其他。然后就子集2^k。 (二进制状态)i&k,然后ans^=p[j](新数组);即可获得第k大。

#include<bits/stdc++.h>
using namespace std;
#define ll long longll n;
ll a[65];void insert(ll x){   //插入线性基(正常创建方法,可以根据题目往里面增加条件)for(ll i=60;i>=0;i--){if ((1ll<<i)&x){if (!a[i]){a[i]=x; return;}x^=a[i];}}
}
ll cnt=0;
ll p[65];void rbuild(){     //构建新数组for(ll i=60;i>=0;i--){ for(ll j=i-1;j>=0;j--){if ((1ll<<j)&a[i]) a[i]^=a[j];}}for(ll i=0;i<=60;i++){  if (a[i]) p[cnt++]=a[i];}
}ll query(ll x){  //询问if (x>=(1ll<<cnt)) return -1;ll ans=0;for(ll j=60;j>=0;j--){if ((1ll<<j)&x){ans^=p[j];}}return ans;
}int main() {ll t,q,x,kase=1;scanf("%lld",&t);while(t--){memset(a,0,sizeof(a));memset(p,0,sizeof(p));cnt=0; scanf("%lld",&n);  //个数for(ll i=1;i<=n;i++){scanf("%lld",&x);   insert(x);        }rbuild();cin>>q;printf("Case #%d:\n",kase++);for(ll i=1;i<=q;i++){scanf("%lld",&x);if (cnt!=n) x--; printf("%lld\n",query(x)); }   }return 0;
}

模板:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;const int MAXL = 60;
struct LinearBasis {ll e[MAXL + 5];LinearBasis() {memset(e, 0, sizeof(e));}void insert(ll x) {for (int i = MAXL; i >= 0; --i)if (x & (1ll << i)) {if (e[i])x ^= e[i];else {e[i] = x;for (int k = i - 1; k >= 0; --k)if (e[i] & (1ll << k))e[i] ^= e[k];for (int k = i + 1; k <= MAXL; ++k)if (e[k] & (1ll << i))e[k] ^= e[i];break;}}}/**/ll query_max_XOR_sum(ll x) {for (int i = MAXL; i >= 0; --i)if ((x ^ e[i]) > x)x ^= e[i];return x;}ll query_min_XOR_sum(ll x) {for (int i = MAXL; i >= 0; --i)if ((x ^ e[i]) < x)x ^= e[i];return x;}
};
LinearBasis LB;int main() {int n;cin >> n;ll t;while (n--) {cin >> t;LB.insert(t);}cout << LB.query_max_XOR_sum(0);return 0;
}

5.主席树

a)静态求区间第k小

主席树图解:

主席树是权值线段树,权值线段树的下标是代表数字的值,那么节点的权值就是代表数字出现的次数。

看root(指的是当前节点) 的左节点,设它的值为 x
如果 x>=k, 那么就是往下搜索 第 k大
如果 x<k, 那么就是往左搜索 第 k−x 大

建树:时间和空间复杂度都是O(nlogn)
查询:O(logn)

下面代码可以找到区间第k小的树:

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int L[maxn<<5],R[maxn<<5], tree[maxn<<5]; //20到30倍,L每个节点的左子树,R每个节点的右子树,tree存所有的节点
int tot;//结点编号
int a[maxn],f[maxn],h[maxn];
int build(int l,int r) {int rt=++tot;tree[rt]=0;//空树没有数字存在if(l<r){int mid=(l+r)>>1;L[rt]=build(l,mid);R[rt]=build(mid+1,r);}return rt;
}
int update(int pre,int l,int r,int x) {int rt=++tot;L[rt]=L[pre];R[rt]=R[pre];//复制线段树tree[rt]=tree[pre]+1; // 先继承前面的线段树,比上一颗树数字个数多1if(l<r){int mid=(l+r)>>1;if(x<=mid) L[rt]=update(L[pre],l,mid,x); //只有更新左子树,右子树状态和上一颗树一样else R[rt]=update(R[pre],mid+1,r,x); //只更新右子树,左子树状态和上一颗树一样}return rt;
}
int query(int ql,int qr,int l,int r,int k){ // 区间[ql, qr]从小到大排序后正数第 k 个数if(l>=r) return l;//查询到叶子结点为止int mid=(l+r)>>1;int num=tree[L[qr]]-tree[L[ql]];  //第v棵树减去第u-1棵树就是当前查询区间的数字个数if(num>=k) return query(L[ql],L[qr],l,mid,k); //答案在左子树上else return query(R[ql],R[qr],mid+1,r,k-num);
}
int main() {int n,m;tot=0;scanf("%d%d",&n,&m);for(int i=1; i<=n;i++) {scanf("%d",&a[i]);h[i]=a[i];}sort(h+1,h+1+n);int d=unique(h+1,h+1+n)-h-1;//去重离散化f[0]=build(1,d);//建空树for(int i=1;i<=n;i++){int x=lower_bound(h+1,h+1+d,a[i])-h;f[i]=update(f[i-1],1,d,x);}while(m--){int l,r,k;scanf("%d%d%d",&l,&r,&k);int x=query(f[l-1],f[r],1,d,k);//查询的区间的状态由第y棵树减第x-1棵树得到printf("%d\n",h[x]);}return 0;
}

b)区间中小于k的数量和小于k的总和

一般与区间分划成两部分的都是主席树来解决。此代码可以求得区间中小于k的数量和小于k的总和。若要大于,直接拿总数减去即可。

//主席树是权值线段树,权值线段树的下标是代表数字的值,那么节点的权值就是代表数字出现的次数。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int L[maxn<<5],R[maxn<<5];//L每个节点的左子树,R每个节点的右子树,
long long tree[maxn<<5],num[maxn<<5],sum[maxn]; //20到30倍,tree存和,num存数量,sum前缀和
int tot;//结点编号
int n,q;
int f[maxn];
int su,nu;
int build(int l,int r) {int root=++tot;tree[root]=0;   //空树没有数字存在num[root]=0;if(l<r){int mid=(l+r)>>1;L[root]=build(l,mid);R[root]=build(mid+1,r);}return root;
}int update(int pre,int l,int r,int x) {int root=++tot;L[root]=L[pre];R[root]=R[pre];  //复制线段树tree[root]=tree[pre]+x; //先继承前面的线段树,比上一颗树数字个数多xnum[root]=num[pre]+1; //先继承前面的线段树,比上一颗树数字个数多1if(l<r){int mid=(l+r)>>1;if(x<=mid) L[root]=update(L[pre],l,mid,x); //只有更新左子树,右子树状态和上一颗树一样else R[root]=update(R[pre],mid+1,r,x); //只更新右子树,左子树状态和上一颗树一样}return root;
}void query(int ql,int qr,int l,int r,int k){ if(l>k) return;  //查询到叶子结点为止if(r<=k){   //这个很关键,当这个的时候,得到的就是比k小的树的信息su+=tree[qr]-tree[ql]; //比他小的主席树直接求得sunu+=num[qr]-num[ql];   //有多少个比他低的数量nureturn;}int mid=(l+r)>>1;query(L[ql],L[qr],l,mid,k); //找左子树query(R[ql],R[qr],mid+1,r,k); //找右子树
}int main() {tot=0;memset(sum,0,sizeof(sum));scanf("%d%d",&n,&q);  //n个值,q个询问//这道题不用离散化//sort(sum+1,sum+1+n);//int d=unique(sum+1,sum+1+n)-sum-1;//去重离散化f[0]=build(1,n);//建空树for(int i=1;i<=n;i++){int hi;scanf("%d",&hi);  //输入数值sum[i]=sum[i-1]+hi;     //前缀和高度f[i]=update(f[i-1],1,n,hi);  }while(q--){int l,r,high;scanf("%d %d %d",&l,&r,&high); //区间l,r和分界线highsu=0,nu=0;query(f[l-1],f[r],1,n,high);  //比他小的主席树直接求得su,有多少个比他低的数量nuprintf("%d %d\n",su,nu);//su范围内小于或等于的总和,nu范围内小于或等于的数量}return 0;
}

c)区间中第一个大于或等于k的值

大于等于k:找k
大于不等于k:找k+1
小于于等于k:找k和k-1
小于不等于k-1:找k-1

下面主席树可以直接找到最接近k的大于等于k的数。

#include <bits/stdc++.h>
using namespace std;typedef long long ll;
typedef unsigned long long ull;const int INF=0x3f3f3f3f;
const int MOD=1000000007;
const int maxn=2e5+7;int a[maxn],b[maxn],T[maxn];
int len = 0,tot;
struct node{int L,R;int sum;
}tree[maxn*30];//主席树
int update(int root, int x,int l,int r) {int newroot=++tot;tree[newroot]=tree[root];tree[newroot].sum++;if(l==r) return newroot; int mid=(l+r)/2;if(x<=mid) tree[newroot].L=update(tree[root].L,x,l,mid);else tree[newroot].R=update(tree[root].R,x,mid+1,r);return newroot;
}int search(int left_root,int right_root,int l,int r,int k) {if(tree[right_root].sum==tree[left_root].sum) return INF;if(l==r) return l;int ans=INF;int mid=(l+r)/2;if(k<=mid) ans=search(tree[left_root].L,tree[right_root].L,l,mid,k);if(ans==INF)ans=search(tree[left_root].R,tree[right_root].R,mid+1,r,k);return ans;
}int main() {int Case; scanf("%d",&Case); //输入样例while(Case--){tot=0;int n,m; scanf("%d%d",&n,&m); //输入个数和查询mfor(int i=1;i<=n;i++)scanf("%d",&a[i]); //n个值len=n+1;T[0]=0;tree[0].sum=tree[0].L=tree[0].R=0;for(int i=1;i<=n;i++)T[i]=update(T[i-1],a[i],1,len);T[n+1]=update(T[n],n+1,1,len);int ans=0;while(m--){int l,r,k;scanf("%d%d%d",&l,&r,&k); //查询l到r第一个大于或等于k的值ans=search(T[l],T[r+1],1,n+1,k);printf("%d\n",ans);} }return 0;
}

6.权值线段树


a)求逆序对的个数

#include<bits/stdc++.h>
using namespace std;
int n,a[500005],tmp[500005],size;
long long ans,tree[2000005];void init(){memset(a,0,sizeof(a));memset(tree,0,sizeof(tree));memset(tmp,0,sizeof(tmp));size=0;ans=0;
}void build(int root,int l,int r,int x){if ((l==r)&&(l==x)){ tree[root]++; //与普通线段树不同的地方,计算个数return;   }int mid=(l+r)>>1;if(x<=mid) build(root<<1,l,mid,x);else if(x>mid) build(root<<1|1,mid+1,r,x);tree[root]=tree[root<<1]+tree[root<<1|1];  //个数和
}long long query(int root,int L,int R,int l,int r){if ((l<=L)&&(r>=R)) return tree[root];long long anss=0;int mid=(L+R)>>1;if (l<=mid) anss+=query(root<<1,L,mid,l,r);if (r>mid) anss+=query(root<<1|1,mid+1,R,l,r);return anss;
}int main(){scanf("%d",&n);  //输入个数init();for (int i=1;i<=n;i++){scanf("%d",&a[i]); //输入数列tmp[i]=a[i];}sort(tmp+1,tmp+1+n);size=unique(tmp+1,tmp+1+n)-(tmp+1); //去重离散化for (int i=1;i<=n;i++)a[i]=lower_bound(tmp+1,tmp+1+size,a[i])-(tmp+1)+1;for (int i=1;i<=n;i++){build(1,1,n,a[i]);ans+=query(1,1,n,a[i]+1,n);}printf("%lld\n",ans);return 0;
}

7.动态主席树(区间第k大带修改)

动态主席树就是在静态主席树的基础上增加了一批用树状数组思维维护的线段树。
离散化建树过程和静态主席树有一点不同,我们必须把所有询问先存起来并且把改变的数也加入到原序列中再离散化建树,会导致空间复杂度和静态有所区别。

1.离散化
2.同静态一样建空树,按原序列前缀建树
3.修改操作,我们新建一批线段树来记录更新,这些线段树以树状数组的思维来维护。更新我们按树状数组的思想更新。

我们可以发现,对于改变i处的数这个操作,对于T[i], T[i+1]… …T[n]这些树的影响是相同的,都只改变了 “原来i处的数 的数量” 和 “现在i处的数 的数量” 这两个值而已,我们只要在原来的基础上增加一类树, 用它们来维护更新掉的数。 即用树状数组来记录更新,每次更新logn棵树。树状数组的每个节点都是一颗线段树。

对于原序列n个数,m次询问
空间复杂度
因为这里我们要加入询问中数离散化后再建树,所以建树4*(n+m)
时间复杂度都是时间和空间复杂度都是O(n∗log2(n))O(n*log^2(n))O(n∗log2(n))

n是原序列个数
T[i]表示第i棵线段树的根节点编号
S[i]表示树状数组思维建的第i棵线段树的根节点编号
L[i]表示节点i的左子节点编号
R[i]表示节点i的右子节点编号
sum[i]表示节点i对应区间中数的个数。

题意:n个数,q个询问 (n<=50000, q<=10000)
Q x y z 代表询问[x, y]区间里的第z小的数
C x y 代表将(从左往右数)第x个数变成y

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) (x&(-x))
const int maxn=200000+100;
const int maxm=100000+100;int T[maxn];//T[i]表示第i棵线段树的根节点编号
int S[maxn]; //S[i]表示树状数组思维建的第i棵线段树的根节点编号
int L[maxn*400]; //L[i]表示节点i的左子节点编号
int R[maxn*400]; //R[i]表示节点i的右子节点编号
int sum[maxn*400]; //sum[i]表示节点i对应区间中数的个数。
int a[maxn],h[maxn];
int ul[maxn],ur[maxn];
//ul,ur含义就是为了把树状数组的节点跟随主席树的节点向下移,开两个数组是为了避免重复下移导致出错
int tot,num,n,q;struct Query{ //储存询问int l,r,k;bool flag;
}Q[maxm];void build(int& rt,int l,int r){  //普通的建空树rt=++tot;sum[rt]=0;if(l==r)return;int mid=(l+r)/2;build(L[rt],l,mid);build(R[rt],mid+1,r);
}void update(int& rt,int pre,int l,int r,int x,int val){  //将点存入主席树(常规)rt=++tot;L[rt]=L[pre];R[rt]=R[pre];sum[rt]=sum[pre]+val;if(l==r)return;int mid=(l+r)/2;if(x<=mid)update(L[rt],L[pre],l,mid,x,val);else update(R[rt],R[pre],mid+1,r,x,val);
}   void add(int x,int val){   //单点修改时,更改树状数组int res=lower_bound(h+1,h+1+num,a[x])-h;  //找到离散化后的大小while(x<=n){ update(S[x],S[x],1,num,res,val); //树状数组的更新操作 x+=lowbit(x);}
}int Sum(int x,bool flag){ //树状数组的区间查询int res=0;while(x){  //树状数组的求和if(flag)res+=sum[L[ur[x]]];else res+=sum[L[ul[x]]];x-=lowbit(x);}return res;
}int query(int s,int e,int ts,int te,int l,int r,int k){  //主席树的查询if(l==r)return l;int mid=(l+r)/2;int res=Sum(e,1)-Sum(s,0)+sum[L[te]]-sum[L[ts]]; //关键if(k<=res){ //主席树查询for(int i=e;i;i-=lowbit(i)) ur[i]=L[ur[i]]; //把树状数组的节点跟随主席树的节点向下移for(int i=s;i;i-=lowbit(i)) ul[i]=L[ul[i]];return query(s,e,L[ts],L[te],l,mid,k);}else{for(int i=e;i;i-=lowbit(i)) ur[i]=R[ur[i]]; //把树状数组的节点跟随主席树的节点向下移for(int i=s;i;i-=lowbit(i)) ul[i]=R[ul[i]];return query(s,e,R[ts],R[te],mid+1,r,k-res);}}int main(){int t=1;//cin>>t;while(t--){char str[5];//输入个数和查询个数cin>>n>>q;for(int i=1;i<=n;i++)scanf("%d",&a[i]),h[++num]=a[i]; for(int i=1;i<=q;i++){scanf("%s",str);if(str[0]=='Q'){ //询问scanf("%d%d%d",&Q[i].l,&Q[i].r,&Q[i].k); //[l,r]第k小Q[i].flag=true; //询问为1}else{scanf("%d%d",&Q[i].l,&Q[i].r); //将l改为rh[++num]=Q[i].r; //关键Q[i].flag=false; //修改为0}}sort(h+1,h+1+num);num=unique(h+1,h+1+num)-h-1; //离散化tot=0;build(T[0],1,num); //建空树for(int i=1;i<=n;i++) update(T[i],T[i-1],1,num,lower_bound(h+1,h+1+num,a[i])-h,1); //将值存入主席树中for(int i=1;i<=n;i++)S[i]=T[0];for(int i=1;i<=q;i++){if(Q[i].flag){for(int j=Q[i].r;j;j-=lowbit(j)) ur[j]=S[j]; //把树状数组的节点跟随主席树的节点向下移for(int j=Q[i].l-1;j;j-=lowbit(j)) ul[j]=S[j]; cout<<h[query(Q[i].l-1,Q[i].r,T[Q[i].l-1],T[Q[i].r],1,num,Q[i].k)]<<"\n";}else{add(Q[i].l,-1); //修改时,存入树状数组a[Q[i].l]=Q[i].r;add(Q[i].l,1); }         }}return 0;
}
//ZOJ 2112

8.树上启发式合并(查询子树的优化)

当每个节点的答案是其子树的叠加,考虑利用这个性质处理问题
我们先遍历其它的所有儿子(它们都是轻儿子),依次完成它们各自的子树内的所有询问,然后我们就头铁地修改,即进入子树时该更新哪种颜色的数量 就更新哪种颜色的数量,退出这棵子树时暴力减掉子树内的所有答案(即各个颜色在这棵子树内的变化量),再遍历其它子树。

简单地说,第一次遍历所有轻儿子,解决它们子树内的询问,然后出来时减掉影响。

最后我们再遍历重儿子,同样处理子树内的询问,但出来时保留里面统计的答案,并把其它轻儿子对应子树的所有点的颜色都重新统计上。这样就可以得到整棵子树的答案, 并返回它了。

具体方法为
1.轻重链剖分,找出重儿子。(预处理)
2.先处理轻儿子,不记录 贡献。
3.处理重儿子,记录贡献。
4.将当前节点和轻儿子的贡献和重儿子合并。
5.如果当前节点是轻儿子的话,消除影响。
算法的正确性很容易理解。重儿子不用清空了, 因为我的兄弟节点都已经处理完了,我不会影响到兄弟节点。

例:给你一个有根树,每个节点都有一种颜色,询问某个节点的子树中颜色最多的编号和

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
struct node{int v,nxt;
}edge[N*2];
int tot;
ll maxx,sum,ans[N];
int col[N],cnt[N],sz[N],big[N],head[N];void add(int u,int v){ //存图edge[tot].v=v;edge[tot].nxt=head[u];head[u]=tot++;
}//color,size,重儿子,计数器
void dfsbig(int u,int fa){sz[u]=1;for(int i=head[u];i!=-1;i=edge[i].nxt){int v=edge[i].v;if(v==fa)continue;dfsbig(v,u);sz[u]+=sz[v]; if(sz[v]>sz[big[u]]||!big[u]) //判断每个节点的轻重儿子big[u]=v;}
}void update(int u,int fa,int k){cnt[col[u]]+=k;if(k>0&&cnt[col[u]]>=maxx){if(cnt[col[u]]>maxx)sum=0,maxx=cnt[col[u]];sum+=col[u];}for(int i=head[u];i!=-1;i=edge[i].nxt){int v=edge[i].v;if(v==fa)continue;update(v,u,k);}
}
//重儿子 保留答案
void dfs(int u,int fa){for(int i=head[u];i!=-1;i=edge[i].nxt){int v=edge[i].v;if(v==fa||v==big[u])continue;dfs(v,u);update(v,u,-1); //轻儿子,则清除信息maxx=0;sum=0;}if(big[u])dfs(big[u],u);for(int i=head[u];i!=-1;i=edge[i].nxt){int v=edge[i].v;if(v==fa||v==big[u])continue;update(v,u,1);//重儿子,统计所需信息}cnt[col[u]]++;if(cnt[col[u]]>=maxx){if(cnt[col[u]]>maxx)sum=0,maxx=cnt[col[u]];sum+=col[u];}ans[u]=sum;
}int main(){memset(head,-1,sizeof head);int n;scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%d",&col[i]);//第i个顶点的颜色。for(int i=1;i<n;i++){int u,v;scanf("%d%d",&u,&v); //-树的边add(u,v); //存图add(v,u);}dfsbig(1,0); //用来找轻重儿子dfs(1,0);for(int i=1;i<=n;i++)printf("%lld ",ans[i]);puts("");return 0;
}

9.树状数组模板

a)求区间异或和

求a(l)^a(l+1)^a(l+2)^…a(r).
可修改。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define INF 0x3f3f3f3f
const int N=10010;
int c[N],d[N];int lowbit(int x){return x&(-x);
}inline void add(int k,int val,int n){while (k<=n){c[k]^=val;k+=lowbit(k);}
}inline int getsum(int k){int sum=0;while (k){sum^=c[k];k-=lowbit(k);}return sum;
}int main(){int tcase,i,ops,x,val,q,n,l,r;scanf("%d",&tcase);for(int w=1; w<=tcase; w++){memset(c,0,sizeof(c));memset(d,0,sizeof(d));scanf("%d%d",&n,&q); //n个数,q个询问for(i=1; i<=n; i++){scanf("%d",&d[i]);add(i,d[i],n);}printf("Case %d:\n",w);for(i=1; i<=q; i++){scanf("%d",&ops);if(ops==1){scanf("%d%d",&l,&r); //查询l到rprintf("%d\n",getsum(r)^getsum(l-1));}else if(ops==2){scanf("%d%d",&x,&val); //修改add(x,val^d[x],n);  //修改时多异或上一个的值即可d[x]=val;}}}return 0;
}

扩展:

1.对每个i,求区间[i+1,i+Mp[i]]中j的个数,满足j-Mp[j]<=i(即两个范围相互包含时求个数)
树状数组:
枚举i从0到n

  • 将满足最左刚好是i (i=j-Mp[j])的存入树状数组中
  • 每次查询范围内树状数组的和

2.(树结构的树状数组)给你一颗树,最初每个节点上都有一个苹果,有两种操作:修改(即修改某一个节点,修改时这一个节点苹果从有到无,或从无到有)和查询(查询某一个节点他的子树上有多少个苹果)
首先建一个这样的树:

void DFS(int node) { //为每一个node添加一个左值和右值,表示这个节点所Left[node] = key;for(int i=0; i<Edge[node].size(); i++) {key+=1;DFS(Edge[node][i]);}Right[node] = key;
}

b的子树就是[Left[b], right[b]]
然后正常的树状数组修改即可。

3.在数轴上有一列互不相同点xN(1 <= N <= 20,000 , 1 <= xi <= 20,000 ),每个点有一个权值vi(1 <= vi <= 20,000),对每对点xi,xj,算出|xi - xj| * max{vi,vj},然后求这些结果的和。

  1. 首先将这n个点按照v值从小到大排序(后面说的排在谁的前面,都是基于这个排序)。这样,当i<j时有max{vi,vj}=vj,
  2. 用两个树状数组,一个记录比xi小的点的个数a(往树状数组加1),一个记录比xi小的点的位置之和b(往树状数组加x[i]),然后,我们可以快速地求出xi与比xi小的点的所有距离的绝对值之和:a*x[i]-b,也可以方便地求出xi与比xi大的点的所有距离的绝对值之和:所有距离-b-(i-1-a)*x[i]。将二者相加,乘上vi,再把所有结果相加,搞定。
    复杂度:
    排序很重要,排哪个元素的序更重要。

b)求逆序对

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;struct node {ll x,id;
}a[500001];
ll c[500001<<2],n,ans,bj[500001];
bool cmp(const node& a , const node& b ){if(a.x==b.x)return a.id>b.id;return a.x>b.x;
}
ll lowbit(ll x){return x&(-x);
}
ll sum(ll x){ll ans=0;while(x>0){ans+=c[x];x-=lowbit(x);}return ans;
}
void add(ll x,ll v){while(x<=n){c[x]+=v;x+=lowbit(x);}
}
int main(){scanf("%lld",&n);for(ll i=1;i<=n;i++)a[i].id=i,scanf("%lld",&a[i].x);sort(a+1,a+1+n,cmp);for(ll i=1;i<=n;i++){ans+=sum(a[i].id),add(a[i].id,1);//第a[i].id个的逆序对是sum(a[i].id); }printf("%lld",ans);return 0;
}

10.区间不重复数字的和 (树状数组)

#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f
using namespace std;
const int maxn = 210000;
struct query {int x,y,id;
} q[maxn];
ll tree[51000];
int pre[51000],loc[1100000],n,m,d[51000];
ll ans[maxn];
bool cmp(const query &a,const query &b) {return a.y<b.y;
}
int lowbit(int x) {return x&(-x);
}
void update(int x,int val){for(;x<=n;x+=bowbit(x)){tree[x]+=val;}
}
ll sum(int x){ll ans=0;for(;x;x-=lowbit(x))ans+=tree[x];return ans;
}
int main() {int t,i,j;scanf("%d",&t);while(t--) {memset(loc,-1,sizeof(loc));memset(tree,0,sizeof(tree));scanf("%d",&n); //n个数 for(i=1;i<=n;i++) {scanf("%d",d+i); //ai pre[i]=loc[d[i]];loc[d[i]]=i;update(i,d[i]); //加入树中 }scanf("%d",&m); //m个询问 for(i=1;i<=m;i++) {scanf("%d %d",&q[i].x,&q[i].y); //左区间,右区间 q[i].id=i;}sort(q+1,q+m+1,cmp); //按y排序,离线操作 int y 0;for(i=1;i<=m;i++) {for(j=y+1;j<=q[i].y;j++) { //枚举y if(pre[j]!=-1) update(pre[j],-d[j]); //说明有重复,删除 }y=q[i].y;ans[q[i].id]=sum(q[i].y)-sum(q[i].x-1); //区间求和 }for(i=1;i<=m;i++) {printf("%I64d\n",ans[i]);}}return 0;
}

11.求k维空间中离所给点最近的m个点,并按顺序输出(KD树)

#include <bits/stdc++.h>
#define ll long longusing namespace std;const int N=50007;
const int K=6;int n,m;struct point {int a[K];int div;  // 按哪个维度划分bool lef;  // 是否是叶子节点ll dis;  // 离询问点的距离。注意这个在读入建树时不会用到,在进入队列时才用到void print() {printf("%d",a[0]);for (int i=1; i<m; i++)printf(" %d",a[i]);puts("");}bool operator < (const point &t) const {return dis<t.dis;}point() {}point(point &t,ll d) {for (int i=0; i<m; i++) a[i]=t.a[i];dis=d;}
} p[N],tar;int cmp_NO;
inline bool cmp(point x,point y) {return x.a[cmp_NO]<y.a[cmp_NO];
}inline ll dis(point x,point y) {ll ret=0;for (int i=0; i<m; i++)ret+=(x.a[i]-y.a[i])*(x.a[i]-y.a[i]);return ret;
}inline void bulid_kdt(int L,int R,int d) {if (L>R) return;int mid=(L+R)>>1;cmp_NO=d;nth_element(p+L,p+mid,p+R+1,cmp);p[mid].div=d;if (L==R) {p[L].lef=true;return;}bulid_kdt(L,mid-1,(d+1)%m);bulid_kdt(mid+1,R,(d+1)%m);
}priority_queue<point> que;
int num,nownum;
ll ans;inline void find_kd(int L,int R) {if (L>R) return;int mid=(L+R)>>1;ll d=dis(p[mid],tar);if (p[mid].lef) {if (nownum<num) {nownum++;que.push(point(p[mid],d));ans=max(ans,d);} else if (ans>d) {que.pop();que.push(point(p[mid],d));ans=que.top().dis;}return;}int t=tar.a[p[mid].div]-p[mid].a[p[mid].div];if (t>0) {find_kd(mid+1,R);if (nownum<num) {nownum++;que.push(point(p[mid],d));ans=max(ans,d);find_kd(L,mid-1);} else {if (ans>d) {que.pop();que.push(point(p[mid],d));ans=que.top().dis;}if (ans>t*t)find_kd(L,mid-1);}} else {find_kd(L,mid-1);if (nownum<num) {nownum++;que.push(point(p[mid],d));ans=max(ans,d);find_kd(mid+1,R);} else {if (ans>d) {que.pop();que.push(point(p[mid],d));ans=que.top().dis;}if (ans>t*t)find_kd(mid+1,R);}}
}inline void put() {if (que.empty()) return;point pp=que.top();que.pop();put();pp.print();
}int main() {while (~scanf("%d%d",&n,&m)) { //点数n,尺寸数/维度 k,for (int i=0; i<n; i++) {for (int j=0; j<m; j++)scanf("%d",&p[i].a[j]);p[i].lef=false;}bulid_kdt(0,n-1,0);  // 这一步相当于将原数组重新排了个序,先访问到的点放在中间int q;scanf("%d",&q);while (q--) { //每个查询包含两行 ,第一行中的k个整数表示给定点。 在第二行中,有一个整数m,即应找到的最近点的数量 for (int i=0; i<m; i++)scanf("%d",&tar.a[i]);while (!que.empty()) que.pop();scanf("%d",&num);nownum=0;ans=-1;find_kd(0,n-1);printf("the closest %d points are:\n",num);put();}}return 0;
}

12.LCA (两个节点的公共父节点)

LCA的定义吧?(两个节点的公共父节点)如果我们求两个点的LCA的使用暴力求解(DFS找出要求点的深度,一个一个往上跳,一次一次查询),在卡时间的竞赛中是肯定会炸掉的。那么,我们就使用另一种方法,树上倍增法:

我们设 表示 的 倍祖先,那么很容易知道,就是当前节点的父亲(记住,当前节点可以代表当前深度的所有节点因为它是一颗树!),就是当前节点的父亲的父亲也就是 , 就是当前节点的父亲的父亲的父亲,也就是也等于…(以此类推).那么,我们就可以计算出当前节点到它所有的父亲要走的路(因为它是一棵树)
基于father数组我们可以计算LCA了。

我们先设 depth[x] 和 depth[y] 为当前节点的深度,那么,基于二进制拆分的思想,把x,y调到同一深度。

之后,我们又运用二进制拆分的思想,让他们一起走到同一个点。(尝试走2^(log(depth[x]-depth[y])(向下取整))步,2^(log(depth[x]-depth[y]-1)(向下取整))步…1步)

#include <bits/stdc++.h>
using namespace std;
struct node {int t, nex;
} e[500001 << 1];
int depht[500001], father[500001][22], lg[500001], head[500001];
int tot;
inline void add(int x, int y) {e[++tot].t = y;e[tot].nex = head[x];head[x] = tot;
}
inline void dfs(int now, int fath) {depht[now] = depht[fath] + 1;father[now][0] = fath;for (register int i = 1; (1 << i) <= depht[now]; ++i)father[now][i] = father[father[now][i - 1]][i - 1];for (register int i = head[now]; i; i = e[i].nex) {if (e[i].t != fath) dfs(e[i].t, now);}
}
inline int lca(int x, int y) {if (depht[x] < depht[y]) swap(x, y);while (depht[x] > depht[y]) x = father[x][lg[depht[x] - depht[y]] - 1];if (x == y) return x;for (register int k = lg[depht[x]]; k >= 0; --k)if (father[x][k] != father[y][k]) x = father[x][k], y = father[y][k];return father[x][0];
}
int n, m, s;
int main() {// freopen("1.txt","r",stdin);scanf("%d%d%d", &n, &m, &s);for (register int i = 1; i <= n - 1; ++i) {int x, y;scanf("%d%d", &x, &y);add(x, y);add(y, x);}dfs(s, 0);for (register int i = 1; i <= n; ++i)lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);for (register int i = 1; i <= m; ++i) {int x, y;scanf("%d%d", &x, &y);printf("%d\n", lca(x, y));}return 0;
}

动态规划:

1.LIS(最长上升子序列)

有两种复杂度的算法,n^2的算法可以算出长度的同时记录每个选择的点,而nlogn的算法只能算出长度

【n^2的模板】

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define MAXN 5010
int a[MAXN], dp[MAXN], f[MAXN];
//dp[i]表示以a[i]为终点的最长上升子序列的长度,f[i]记录最优解父亲节点void pt(int s)//输出一种最长上升子序列
{if (-1 == s)return;pt(f[s]);printf ("%d ", a[s]);
}
int main ()
{#ifdef SHYfreopen("e:\\1.txt", "r", stdin);#endifint n;scanf ("%d%*c", &n);memset(f,-1,sizeof(f));dp[0] = 1;//把只有一个数的序列最长子序列长度设置为1,边界处理for (int i = 0; i < n; i++)scanf ("%d%*c", &a[i]);int ans = 1, ss;for (int i = 1; i < n; i++){dp[i] = 1;for (int j = 0; j < i; j++)//枚举终点不算的时候左边最长的子序列长度if (a[j] < a[i] && dp[j]+1 > dp[i])dp[i] = dp[j]+1, f[i] = j;if (ans < dp[i])//因为最后一个点不一定是最长子序列的终点,所以要去所有点为终点的最长的那一个ans = dp[i], ss = i;}printf ("%d\n", ans);pt(ss);return 0;
}

nlogn版本不能输出子序列

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 100100;
int a[maxn], n, lis[maxn], len;
int main()
{ios::sync_with_stdio(false);cin>>n;for(int i = 1; i <= n; i++) cin>>a[i];lis[++len] = a[1];for(int i = 2; i <= n; i++){if(a[i] > lis[len])lis[++len] = a[i];else{int pos = lower_bound(lis+1, lis+1+len, a[i])-lis;lis[pos] = a[i];}}for(int i = 1; i <= len; i++) cout<<lis[i]<<" ";cout<<endl;cout<<len;return 0;
}

2.有依赖的背包(正解)

  • 1.将每一个主件对应的附件集合先做一次01背包。附带配件时的最优加进主件小组
  • 2.转化成一个分组背包(从每个主件小组中找到符合条件的)

例: 如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有0个、1个或2个附件。 附件不再有从属于自己的附件。金明想买的东西很多,肯定会超过妈妈限定的N元。于是,他把每件物品规定了一个重要度,分为5等:用整数1-5表示,第5等最重要。他还从因特网上查到了每件物品的价格(都是10元的整数倍)。他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大

若是将本题每项主件与其附件的每种搭配情况在递推式内枚举,使得代码递推式部分冗长臃肿,且不适于附件较多的情况。
本代码属于非树形有依赖的背包问题(只有两类物品:主件,附件),下面给出具体解题思路。
(依赖背包的常规解法)可以先对每种主件的 附件的集合 进行一次 01 背包处理,就可以先求出 对于每一种主件包括其附件的组合中,每种花费的最大价值

对于每一种主件的01背包处理,这样可以得到主件 k 的附件中费用依次为 0~n-v[k] 时的相应最大价值 f[0 ~ n-v[k]],那么我们就得到了主件 k 及其附件集合的 n-v[k]+1 种不同选择情况,其中费用为 v[k]+t 的物品的价值就是 f[t]+v[k]*p[k]
对于每一个主件处理出的情况,在 n-v[k]+1n−v[k]+1 种情况之中只能最多选择一种选入最终答案之中,原问题便转化成一个分组背包问题。

#include <bits/stdc++.h>
using namespace std;
struct cas{int v,p,q;
}a[60],pat[60][60];
int n,m,t[60],V[60][10],P[60][10],cnt[60],f[32000],ans;
int main(){scanf("%d%d",&n,&m); //总钱数,希望购买物品的个数for(int i=1;i<=m;i++){scanf("%d%d%d",&a[i].v,&a[i].p,&a[i].q);//正常的读入,价格,重要度,是否为附件 0主键,1附件if(a[i].q){//如果这个物品是附件t[a[i].q]++;pat[a[i].q][t[a[i].q]].v=a[i].v;pat[a[i].q][t[a[i].q]].p=a[i].p;pat[a[i].q][t[a[i].q]].q=a[i].q;  //把它存在相应的主件的分组中}}for(int i=1;i<=m;i++){ //01背包处理if(t[i]){    //如果当前物品为主件memset(f,-1,sizeof(f)); //恰好背包的处理,-1表示不恰好取到此价值f[0]=0;  //恰好背包的处理for(int j=1;j<=t[i];j++)  //附件的个数for(int k=n-a[i].v;k>=pat[i][j].v;k--) //能买配件的钱if(f[k]<f[k-pat[i][j].v]+pat[i][j].v*pat[i][j].p&&f[k-pat[i][j].v]!=-1)//恰好背包的判断f[k]=f[k-pat[i][j].v]+pat[i][j].v*pat[i][j].p; //很平常的01状态转移for(int j=0;j<=n-a[i].v;j++) //钱if(f[j]!=-1){ //恰好背包的判断,这种附件组合满足题意cnt[i]++;V[i][cnt[i]]=j+a[i].v;P[i][cnt[i]]=f[j]+a[i].v*a[i].p; //把此情况存在主件i的分组中,为分组背包做好处理}}if(!a[i].q) {//只买主件的时候cnt[i]++;V[i][cnt[i]]=a[i].v;P[i][cnt[i]]=a[i].v*a[i].p;//存储}}memset(f,0,sizeof(f));//分组背包,先枚举每一组,再枚举体积,最后枚举每组中的物品。(每组只能取一个)for(int i=1;i<=m;i++) //元件数for(int j=n;j>=0;j--) //价格for(int k=1;k<=cnt[i];k++) //每组的物品if(j>=V[i][k])f[j]=max(f[j],f[j-V[i][k]]+P[i][k]);//分组背包的计算for(int i=0;i<=n;i++)ans=max(ans,f[i]);printf("%d",ans);//输出return 0;
}

3.最长公共子序列(LCS)

a)朴素模板

c[i,j]表示:(x1,x2…xi) 和 (y1,y2…yj) 的最长公共子序列的长度。
复杂度O(len1*len2)

#include<bits/stdc++.h>
using namespace std;
int n,m,maxl,ans,f[2005];
char a[2005],b[2005];
int main(){scanf("%s%s",a,b);n=strlen(a); m=strlen(b);for(int i=0;i<n;i++){maxl=0;for(int j=0;j<m;j++){int ll=f[j];if(a[i]==b[j]&&f[j]<maxl+1) f[j]=maxl+1;maxl=max(maxl,ll);}}for(int i=0;i<m;i++)if(ans<f[i]) ans=f[i]; printf("%d\n",ans);return 0;
}

b)优化版
asdfasdfhuasihfahfiosajdfoa
a:0 1 5 5 5 5 11 11 11 11 11 11 16 16 16 16 16 1061109567 1061109567 1061109567 1061109567
预处理每个字符的下一个位置,如上,然后跑最小位置即可
可以跑n(1e4)m(1e5)

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int Maxn=2e5+5;
const int INF=0x3f3f3f3f;
string str1,str2;
int dis[Maxn],dp[30][Maxn];
int main(){int n,m;scanf("%d%d",&n,&m);cin>>str1>>str2;for(int i=0;i<26;i++) dp[i][m+1]=INF;for(int i=m;i>=1;i--){for(int j=0;j<26;j++) dp[j][i]=dp[j][i+1]; //先初始化int ch=str2[i-1]-97;dp[ch][i]=i;   //第几个字符在哪里 }for(int i=1;i<=n;i++) dis[i]=INF; //初始化INFfor(int i=1;i<=n;i++){int ch=str1[i-1]-97; //跑第一个字符串for (int j=i-1;j>=0;j--) //dis[j]存的是上一个的匹配的字符的最小位置if (dis[j]<INF) dis[j+1]=min(dis[j+1],dp[ch][dis[j]+1]); }for(int i=n;i>=0;i--)if(dis[i]<INF){printf("%d\n",i);break;}return 0;
}

c)基于位运算的动态规划
复杂度O(n*m/b)b为0~9或a~z的个数(字符串的种类)




4.树形DP

遍历树的方式:
a) 顺推

//最好是从根节点开始遍历
//father[MAX];点i的父节点
void dp_tree(int node) {step[node]=1; for (int i=1;i<=N;i++) {if (father[i]!=node||step[i]==1) continue;dp_tree(i); dp[node][1]+=dp[i][0];  //状态转移方程dp[node][0]+=Max(dp[i][0],dp[i][1]); //状态转移方程}
}

b) 倒推

//father[MAX];点i的父节点,用来找根节点
//vector<int>G[maxn];以i的父节点的变长数组
int dp[maxn][2],father[maxn];
vector<int>G[maxn];
void dfs(int root){for(int i=0;i<G[root].size();i++){dfs(G[root][i]);}for(int i=0;i<G[root].size();i++){dp[root][0]+=dp[G[root][i]][1];dp[root][1]+=min(dp[G[root][i]][0], dp[G[root][i]][1]);}
}

5.状压DP-斯坦纳树

最小生成树是最小斯坦纳树的一种特殊情况
最小生成树是在给定的点集和边中寻求最短网络使所有点连通
而最小斯坦纳树允许在给定点外增加额外的点,使生成的最短网络开销最小
模板题:
给定N个寺庙,和M个另外的地方。
然后给定点权,表示在这个点挖水井需要的代价。
再给定边权,为建造无向边i,j的代价。
然后求怎样弄最小的代价使得前N个点,就是寺庙都能从挖的井里得到水。
解析:因每个寺庙只需找到一口井,也就是寺庙的位置到井的位置只需一条路,所以我们的最后的最优解一定得到的是一棵树,可以增加一个0点来连接所有的点,其边权就是挖井的花费,所以最终以0点做为树的根节点,这样只要是到达0点则说明井己挖好。因任意两个寺庙只有两种情况(到同一口井或不同的井),到同一口井时可能最近公共父节点到井相隔几个点。不同井则最近公共父节点一定是0点。
DP[寺庙的组成状态][子树的根节点]:最小花费。
dp[ j ][ i ]=min{ dp[ j ][ i ],dp[ k ][ i ]+dp[ l ][ i ] },其中k和l是对j的一个划分。
dp[ j ][ i ]=min{ dp[ j ][ i ],dp[ j ][ i’ ]+w[ i’ ][ i ] },其中i和i’之间有边相连。

#include<stdio.h>
#include<vector>
#include<queue>
using namespace std;#define move(a) (1<<(a))
const int N = 1010;
const int inf = 0x3fffffff;
struct EDG {int v,c;
};int n,m,dp[1<<7][N],dis[N][N];
vector<EDG>map[N];void spfa() { //求两点之间的最短距离int inq[N]={0},ts,k;queue<int>q;for(int s=0;s<=n+m;s++) {for(int j=0;j<=n+m;j++)dis[s][j]=inf;dis[s][s]=0;q.push(s);while(!q.empty()) {ts=q.front();q.pop();inq[ts]=0;k=map[ts].size();for(int j=0;j<k;j++) {int v=map[ts][j].v;if(dis[s][v]>dis[s][ts]+map[ts][j].c) {dis[s][v]=dis[s][ts]+map[ts][j].c;if(!inq[v])q.push(v),inq[v]=1;}}}}
}
void countDp() {for(int sta=1;sta<move(n+1);sta++)for(int i=0;i<=n+m;i++)dp[sta][i]=inf;for(int i=0;i<=n;i++)for(int j=0;j<=n+m;j++)dp[move(i)][j]=dis[i][j];for(int sta=1;sta<move(n+1);sta++)if(sta&(sta-1)) {for(int i=0;i<=n+m;i++) {for(int s=sta;s>0;s=(s-1)&sta)if(dp[sta][i]>dp[sta^s][i]+dp[s][i])dp[sta][i]=dp[sta^s][i]+dp[s][i];}for(int i=0;i<=n+m;i++)for(int j=0;j<=n+m;j++)if(dp[sta][i]>dp[sta][j]+dis[j][i])dp[sta][i]=dp[sta][j]+dis[j][i];}
}
int main() {int p,a,b;EDG e;while(scanf("%d%d%d",&n,&m,&p)>0) { //表僧侣的数量,m代表可以使用的地方数量,p代表这些地方之间的道路数量for(int i=0;i<=n+m;i++)map[i].clear();for(int i=1;i<=n+m;i++) {scanf("%d",&e.c); //在第i个地方挖一个井的消耗 e.v=i;map[0].push_back(e);//用挖井的花费作为i-->0边的花费,所以最终1~n点都要归于一点0e.v=0;map[i].push_back(e);}while(p--){scanf("%d%d%d",&a,&b,&e.c); //a和b之间建立一条道路将花费ce.v=b;map[a].push_back(e);e.v=a;map[b].push_back(e);}spfa();countDp();printf("%d\n",dp[move(n+1)-1][0]);}
}

6.背包

a)01背包

有N件物品和一个容量为V的背包。第i件物品的费用是w[i],价值是v[i],求将哪些物品装入背包可使价值总和最大。

​for (int i=1;i<=n;i++)for (int j=V;j>=w[i];j--)f[j]=max(f[j],f[j-w[i]]+v[i]);

常数的优化

for (int i = 1; i <= n; i++) {int bound = max(V - sum{w[i]...w[n]}, w[i]);for (int j = V; j >= bound, j--)f[j] = max(f[j], f[j - w[i]] + v[i]);
}

b)完全背包

有N种物品和一个容量为V的背包,每种物品都有无限件可用。第iii种物品的费用是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
O(VN)的算法

for (int i=1;i<=n;i++)for (int j=w[i];j<=V;j++)f[j]=max(f[j],f[j-w[i]]+v[i]);

c)多重背包

有N种物品和一个容量为V的背包。第iii种物品最多有p[i]件可用,每件费用是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
将原问题转化为了复杂度为O(V∗Σlog(p[i]))的01背包问题,是很大的改进。二进制拆分代码如下:

for (int i = 1; i <= n; i++) {int num = min(p[i], V / w[i]);for (int k = 1; num > 0; k <<= 1) {if (k > num) k = num;num -= k;for (int j = V; j >= w[i] * k; j--)f[j] = max(f[j], f[j - w[i] * k] + v[i] * k);}
}

O(VN)的算法

//p:某类物品数量,w:某类物品花费,v:某类物品价值,V:商品总价值
void MultiPack(int p, int w, int v) {for (int j = 0; j < w; j++) { //j为w的所有组int head = 1, tail = 0;for (int k = j, i = 0; k <= V / 2; k += w, i++) {int r = f[k] - i * v;while (head <= tail and r >= q[tail].v) tail--;q[++tail] = node(i, r);while (q[head].id < i - p) head++; //需要的物品数目f[k] = q[head].v + i * v;}}
}

d)混合背包问题

如果将前面三个背包混合起来,也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包),应该怎么求解呢?
01背包与完全背包的混合
考虑到在01背包和完全背包中给出的伪代码只有一处不同,故如果只有两类物品:一类物品只能取一次,另一类物品可以取无限次,那么只需在对每个物品应用转移方程时,根据物品的类别选用顺序或逆序的循环即可,复杂度是O(VN)。
再加上多重背包
如果再加上有的物品最多可以取有限次,那么原则上也可以给出O(VN)的解法:遇到多重背包类型的物品用单调队列解即可。但如果不考虑超过NOIP范围的算法的话,用多重背包中将每个这类物品分成O(log(p[i]))个01背包的物品的方法也已经很优了。当然,更清晰的写法是调用我们前面给出的三个相关过程。代码:

p[i]:每个物品的件数,0代表无穷个
for (int i = 1; i <= n; i++)if (p[i] == 0)for (int j = w[i]; j <= V; j++)f[j] = max(f[j], f[j - w[i]] + v[i]);elsefor (int k = 1; k <= p[i]; k++)for (int j = V; j >= w[i]; j--)f[j] = max(f[j], f[j - w[i]] + v[i]);

e)二维费用背包

二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价1和代价2,第iii件物品所需的两种代价分别为w[i]和g[i]。两种代价可付出的最大值(两种背包容量)分别为V和T。物品的价值为v[i]。

算法
费用加了一维,只需状态也加一维即可。设f[i][j][k]表示前iii件物品付出两种代价分别为j和k时可获得的最大价值。状态转移方程就是:f[i][j][k]=max(f[i−1][j][k],f[i−1][j−w[i]][k−g[i]]+v[i])如前述方法,可以只使用二维的数组:当每件物品只可以取一次时变量j和k采用逆序的循环,当物品有如完全背包问题时采用顺序的循环。当物品有如多重背包问题时拆分物品。代码:

for (int i = 1; i <= n; i++)for (int j = V; j >= w[i]; j--)for (int k = T; k >= g[i]; k--)f[j][k] = max(f[j][k], f[j - w[i]][k - g[i]] + v[i]);

物品总个数的限制
有时,“二维费用”的条件是以这样一种隐含的方式给出的:最多只能取M件物品。这事实上相当于每件物品多了一种“件数”的费用,每个物品的件数费用均为1,可以付出的最大件数费用为M。换句话说,设f[i][j]表示付出费用iii、最多选j件时可得到的最大价值,则根据物品的类型(01、完全、多重)用不同的方法循环更新,最后在f[0…V][0…M]范围内寻找答案。

复数域上的背包问题
另一种看待二维背包问题的思路是:将它看待成复数域上的背包问题。也就是说,背包的容量以及每件物品的费用都是一个复数。而常见的一维背包问题则是实数域上的背包问题。(注意:上面的话其实不严谨,因为事实上我们处理的都只是整数而已。)所以说,一维背包的种种思想方法,往往可以应用于二位背包问题的求解中,因为只是数域扩大了而已。

f)分组背包问题

有N件物品和一个容量为V的背包。第i件物品的费用是w[i],价值是v[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
\是选择本组的某一件,还是一件都不选。也就是说设f[k][j]f[k][j]f[k][j]表示前kkk组物品花费费用jjj能取得的最大权值,则有:
f[k][j]=max(f[k−1][j],f[k−1][j−c[i]]+w[i]∣物品i属于组k)
伪代码如下:

for (所有的组k)for (int j = V; j >= 0; j--)for (所有属于组k的i)f[j] = max{f[j], f[j - w[i]] + v[i]}

注意这里的三层循环的顺序,for(j…0)这一层循环必须在for(所有的i属于组k)之外。这样才能保证每一组内的物品最多只有一个会被添加到背包中。
另外,显然可以对每组内的物品应用完全背包中“一个简单有效的优化”。
小结
分组的背包问题将彼此互斥的若干物品称为一个组,这建立了一个很好的模型。不少背包问题的变形都可以转化为分组的背包问题(例如有依赖的背包),由分组的背包问题进一步可定义“泛化物品”的概念,十分有利于解题。

g)有依赖背包问题

这种背包问题的物品间存在某种“依赖”的关系。也就是说,i依赖于j,表示若选物品i,则必须选物品j。为了简化起见,我们先设没有某个物品既依赖于别的物品,又被别的物品所依赖;另外,没有某件物品同时依赖多件物品。
我们可以对主件i的“附件集合”先进行一次01背包,得到费用依次为0…V−c[i]所有这些值时相应的最大价值f′[0…V−c[i]]。那么这个主件及它的附件集合相当于V−c[i]+1个物品的物品组,其中费用为c[i]+k的物品的价值为f′[k]+w[i]。也就是说原来指数级的策略中有很多策略都是冗余的,通过一次01背包后,将主件i转化为V−c[i]+1个物品的物品组,就可以直接应用分组背包的算法解决问题了。

综合代码:

#include <iostream>
#include <cstdio>
#include <complex>
#define A 1000010
using namespace std;
int f[A], w[A], v[A];
/*---------0-1背包----------*/
int knapsack01(int n, int V) {memset(f, 0xc0c0c0c0, sizeof f);f[0] = 0; //需要装满memset(f, 0, sizeof f); //不需要装满for (int i = 1; i <= n; i++)for (int j = V; j >= w[i]; j--)f[j] = max(f[j], f[j - w[i]] + v[i]);return f[V];
}
/*-----------完全背包----------*/
int Fullbackpack(int n, int V) {for (int i = 1; i <= n; i++)for (int j = w[i]; j <= V; j++)f[j] = max(f[j], f[j - w[i]] + v[i]);return f[V];
}
/*-------多重背包二进制拆分-------*/
int number[A];
int MultiplePack1(int n, int V) {for (int i = 1; i <= n; i++) {int num = min(number[i], V / w[i]);for (int k = 1; num > 0; k <<= 1) {if (k > num) k = num;num -= k;for (int j = V; j >= w[i] * k; j--)f[j] = max(f[j], f[j - w[i] * k] + v[i] * k);}}return f[V];
}
int newv[A], neww[A], cnt;
int MultiplePack2(int n, int V) {for (int i = 1; i <= n; i++) {for (int j = 1; j <= c[i]; j <<= 1) {newv[cnt] = j * v[i];neww[cnt++] = j * w[i];c[i] -= j;}if (c[i] > 0) {newv[cnt] = c[i] * v[i];neww[cnt++] = c[i] * w[i];}}for (int i = 1; i <= cnt; i++)for (int j = V; j >= neww[i]; j--)f[j] = max(f[j], f[j - neww[i]] + newv[i]);return f[V];
}
/*------------多重背包单调队列优化------------*/
void MultiPack(int p, int w, int v) {for (int j = 0; j < cost; j++) {int head = 1,tail = 0;for (int k = j, i = 0; k <= V / 2; k += w, i++) {int r = f[k] - i * v;while (head <= tail and r >= q[tail].v) tail--;q[++tail] = node(i, r);while (q[head].id < i - num) head++;f[k] = q[head].v + i * v;}}
}
/*-----------二维费用背包----------*/
int t[A], g[A], dp[B][B];
int Costknapsack(int n, int V, int T) {for (int i = 1; i <= n; i++)for (int j = T; j >= w[i]; j--)for (int k = V; k >= g[i]; k--)dp[j][k] = max(dp[j][k], dp[j - w[i]][k - g[i]] + v[i]);return dp[T][V];
}
/*--------------分组背包--------------*/
int a[B][B];
int Groupingbackpack() {for (int i = 1; i <= n; i++)for (int j = 1; j <= m; j++)scanf("%d", &a[i][j]);for (int i = 1; i <= n; i++)for (int j = m; j >= 0; j--)for (int k = 1; k <= j; k++)f[j] = max(f[j], f[j - k] + a[i][k]);return f[m];
}
/*------------K优解---------------*/
int kth(int n, int V, int k) {for (int i = 1; i <= n; i++) {for (int j = V; j >= w[i]; j--) {for (int l = 1; l <= k; l++) {a[l] = f[j][l];b[l] = f[j - w[i]][l] + v[i];}a[k + 1] = -1;b[k + 1] = -1;int x = 1, y = 1, o = 1;while (o != k + 1 and (a[x] != -1 or b[y] != -1)) {if (a[x] > b[y]) f[j][o] = a[x], x++;else f[j][o] = b[y], y++;if (f[j][o] != f[j][o - 1]) o++;}}}return f[V][k];
}
int main(int argc, char const *argv[]) {}

7.dp[i]=min(dp[i+1]…dp[i+k])

题意:
给定n个房间,每个房间1或0表示这个房间有没有路由器。给定k,表示一个路由器影响的范围,假设路由器位置在i,则其影响范围为[max(1,i−k),min(n,i+k)]。位置i连上网的花费是i(如果有路由器也是i)。问怎样连网使得全部房间连上网且花费最小。

可以用multset维护区间dp[i+1]…dp[i+k]的最小值,也可以用线段树维护

#include <bits/stdc++.h>
#define ll long long
#define It list<node>::iterator
using namespace std;const ll N = 2e5+5;
string str;
ll n, k, dp[N];//dp[i] 代表使得编号为i到(n-1)的房间连上网的最小花费(房间编号从0到(n-1))。
vector<ll> del[N];
multiset<ll> mins, vals;//mins代表的是{dp[i+1],...dp[i+k]}这个集合,利用multiset可以很方便的取出这个集合中的最小值。
//vals记录的是第i位之后可放置wifi的点的最小花费集合,同时这个点可以覆盖到点i。int main() {ios::sync_with_stdio(false);cin>>n>>k>>str;dp[n] = 0;mins.insert(0);//初始化for (ll i = n-1; i >= 0; i--) {dp[i] = dp[i+1]+(i+1);//直接在i上接网,花费为i+1(编号0~n-1)if (i+k+2 <= n) { //类似滑动窗口,将在k范围外的dp值删去mins.erase(mins.find(dp[i+k+2]));}for (auto it : del[i]) { //更新vals数组,将不覆盖点i的值删去vals.erase(vals.find(it));}if (!vals.empty()) { //后面存在可放置wifi的集合同时点i能被其覆盖dp[i] = min(dp[i], *vals.begin());//取出这里面的最小值更新dp[i]}if (str[i] == '1') { //该点可放置wifi//val为在该点放置wifi再加上{dp[i+1],...dp[i+k]}这个集合的最小值(保证i~n-1全覆盖)ll val = (mins.empty() ? 0 : *mins.begin())+(i+1);dp[i] = min(dp[i], val); //更新dp[i]vals.insert(val); //将val插入到vals中if (i-k-1 >= 0) {//将val保存在del[i-k-1]中用来后面更新vals集合del[i-k-1].push_back(val);}}mins.insert(dp[i]);//更新mins}cout<<dp[0]<<endl;//dp[0]即是答案return 0;
}

单调栈也能做这道题(更简单)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=2e6+10;
int n,m,l,r,st[N],k;
char s[N];
ll dp[N];int main() {scanf("%d%d",&n,&k);scanf("%s",s+1);l=r=1;st[1]=0;for(int i=1; i<=n+k; i++) {dp[i]=dp[i-1]+i;if(i>k&&s[i-k]=='1') {dp[i]=min(dp[i],dp[st[l]]+i-k);} if(st[l]<i-2*k) l++; while(l<=r&&dp[st[r]]>=dp[i]) r--; st[++r]=i;}ll ans=1e18;for(int i=n; i<=n+k; i++) ans=min(ans,dp[i]);cout<<ans;return 0;
}

题目集

1)从给定的集合中选出最多的数构成等差数列
数字排序后,设dp[i][j]表示等差数列最后一个数字为a[i],倒数第二个数字为a[j]的最大个数。然后对于每一位枚举 i,lower_bound()找有无合法的 j 即可。复杂度O(n2log(n))。

2)题意:给出n个篱笆,每个篱笆高度为a,一米的造价为b,要求相邻的两个篱笆高度不能相同,且每个篱笆的高度只能增加,求符合条件的最小造价;

思路:首先:可以确定的是每个篱笆的增加高度不会大于2。例如:假设一个篱笆高度为1,左边为1,右边为2,则它的增加高度为2。如果左右都为1,只需增加1。知道了篱笆的最大增加高度,就可以用DP解决问题。设一个dp[ i ][ j ],意为[1, i ]个篱笆符合条件并且第i个篱笆增加j高度的最小造价,数组pre[ i ]表示第i个篱笆增加k个单位后的高度(0<=k<=2),故可得转移方程:dp(i,j)=j∗b+min{dp(i−1,k)|k∈[0;2]}

博弈:

1.NIM博弈(一堆每次最少取一次)

有n堆石头,两个人依次对某一堆取石头,每次取石头最少为1块,谁先取完谁获胜。
计算n堆石头数目的异或和,判断是否为0即可。

2.威佐夫博弈(两堆每次取至少一个或一起取一样的)

有两堆各若干个物品,两个人轮流从任意一堆中取出至少一个或者同时从两堆中取出同样多的物品,规定每次至少取一个,至多不限,最后取光者胜利。

奇异局势:(0,0),(1,2),(3,5),(4,7),(6,10),(8,13)
黄金比例 t = (sqrt(5.0) + 1) / 2.0;
假设差值为 k,必败态为: 当(int(k * t),int(k*t)+k)
奇异局势先手输。

ll a,b;
double t=(sqrt(5.0)+1)/2.0;
while(~scanf("%lld%lld",&a,&b)){if((ll)(fabs(a-b)*t)==min(a,b)) cout<<0<<endl;else cout<<1<<endl;
}

3.约瑟夫环

你和另外 n-1 个人围成一个圈,按 1,2,…,n 依次编号。第一个人从 1 开始报数,数到 k 的人会被杀掉,然后下一个人重新从 1 开始报数。如此往复,直到最后只剩下一个人。问题是,你应该如何选择自己的初始位置,才能保证最后不被杀掉呢?
复杂度O(n)。

#include <stdio.h>
int main(){int n,m,i,ans=0;printf ("N M = ");scanf("%d%d", &n, &m);for (i=2;i<=n;i++){ans=(ans+m)%i;//i为每阶约瑟夫环的总人数}printf ("\nThe winner is %d\n",ans+1);//编号是从零开始,这里加1变成符合习惯的计数
}

具体数学里有:
当每次选两个人的时候,设J(n)为最后存活的人。那么n向左移(最左跑到最右)即为答案。
如:n=100=(1100100)2n=100=(1100100)_2n=100=(1100100)2​,J(n)=J((1100100)2)=(1001001)2=73J(n)=J((1100100)_2)=(1001001)_2=73J(n)=J((1100100)2​)=(1001001)2​=73

4.斐波那契博弈(取的数依赖于对手刚才取的数)

又叫做Fibonacci Nim,肯定和Fibonacci数列:f[n]:1,2,3,5,8,13,21,34,55,89,…有密切的关系。如果试验一番之后,可以猜测:先手胜当且仅当n不是Fibonacci数。换句话说,必败态构成Fibonacci数列。

先看看FIB数列的必败证明
1、当i=2时,先手只能取1颗,显然必败,结论成立。
2、假设当i<=k时,结论成立。
则当i=k+1时,f[i] = f[k]+f[k-1]。
则我们可以把这一堆石子看成两堆,简称k堆和k-1堆。
(一定可以看成两堆,因为假如先手第一次取的石子数大于或等于f[k-1],则后手可以直接取完f[k],因为f[k] < 2*f[k-1])
对于k-1堆,由假设可知,不论先手怎样取,后手总能取到最后一颗。下面我们分析一下后手最后取的石子数x的情况。
如果先手第一次取的石子数y>=f[k-1]/3,则这小堆所剩的石子数小于2y,即后手可以直接取完,此时x=f[k-1]-y,则x<=2/3*f[k-1]。
我们来比较一下2/3*f[k-1]与1/2*f[k]的大小。即4*f[k-1]与3*f[k]的大小,由数学归纳法不难得出,后者大。
所以我们得到,x<1/2*f[k]。
即后手取完k-1堆后,先手不能一下取完k堆,所以游戏规则没有改变,则由假设可知,对于k堆,后手仍能取到最后一颗,所以后手必胜。
即i=k+1时,结论依然成立。
对于不是FIB数,首先进行分解。
分解的时候,要取尽量大的Fibonacci数。
比如分解85:85在55和89之间,于是可以写成85=55+30,然后继续分解30,30在21和34之间,所以可以写成30=21+9,
依此类推,最后分解成85=55+21+8+1。
则我们可以把n写成 n = f[a1]+f[a2]+……+f[ap]。(a1>a2>……>ap)
我们令先手先取完f[ap],即最小的这一堆。由于各个f之间不连续,则a(p-1) > ap + 1,则有f[a(p-1)] > 2*f[ap]。即后手只能取f[a(p-1)]这一堆,且不能一次取完。
此时后手相当于面临这个子游戏(只有f[a(p-1)]这一堆石子,且后手先取)的必败态,即先手一定可以取到这一堆的最后一颗石子。
同理可知,对于以后的每一堆,先手都可以取到这一堆的最后一颗石子,从而获得游戏的胜利。

sg函数


组合游戏:
1)游戏有2名参与者。
2)游戏过程中任意时刻有确定的状态。
3)参与者操作时可以的操作时将游戏从当前状态改变为另一状态,规则规定了在任意一状态时,可以到达的状态集合。
4)参与者轮流进行操作。
5)在游戏出于某状态,当前参与者不能进行操作时,游戏结束。此时参照规则决定胜负。
6)无论参与者做出怎样的操作游戏在有限部数之内结束(没有平局)。
7)参与者拥有游戏本身,和游戏过程的所有信息,比如规则、以前自己和对手的操作。

问题一:有1堆石头,每次必须取走一堆的1至m个石头,无法取石头者败
问题二:有2堆石头,每次必须取走选取一堆或者两堆取走相同任意数量的石头,无法取石头者败
问题三:有n堆石头,每次必须取走一堆的任意个石头,无法取石头者败
问题四:有n堆石头,每次必须取走一堆的任意个石头,无法取石头者胜

问题五:有n堆石头,每次必须取走一堆的1至m个石头,无法取石头者败
问题六:有n堆石头,每次必须取走x个石头,无法取石头者败,x属于给定集合f,例如f ={1,3,4},x只能为1 or 3 or 4

(以下的 必胜或必败 都是对于 需要面对当前状态的玩家 而言,即 都是对于先手而言,必胜即先手必胜,必败即后手必胜

(一)巴什博奕(Bash Game)
问题一:有1堆石头,每次必须取走一堆的1至m个石头,无法取石头者败
结论:n%(m+1)!=0,必胜;n%(m+1)==0,必败
证明:
如果n为m+1的倍数,先手拿x个石头(1<=x<=m),后手拿只要拿m+1-x个,使得石头数量保持在m+1的倍数,一定是后手先拿完
如果n不为m+1的倍数,先手拿x个石头,使n变为m+1的情况即可
即n==k*(m+1)+s
如果s==0,后手保持 先手+后手==m+1,先手必败
如果s!=0,先手拿s,后保证 后手+先手==m+1,先手必胜

(二)威佐夫博奕(Wythoff Game)
问题二:有2堆石头,每次必须取走选取一堆或者两堆取走相同任意数量的石头,无法取石头者败
石头分别a,b个,当min(a,b)==floor((b-a)*((sqrt(5.0)+1)/2))時,先手必败
证明:
(坑,待施工)(和下面的Nim博弈一样是必胜态和必败态互相转化)

(三)尼姆博奕(Nimm Game)
问题三:有n堆石头,每次必须取走一堆的任意个石头,无法取石头者败

定义一:
T态:各堆石头数量的异或和为0
S态:各堆石头数量的异或和不为0

定理一:
S态 总能 转化为T态
证明:
假设有n堆石头,n>=2,每堆有A(i)个石头,那么既然现在处于S态,
有 sum = A(1) ^A(2) ^… ^A(n) > 0
把 sum 表示成二进制,记 sum 的二进制数的最高位为第p位,则必然存在一个 A(t),它二进制的第p位也是1(若所有的A(i)的第p位都是0,则异或结果 sum 的第p位不会为1,所以必定存在A(t)
那么就 令 x = A(t)^c ,则得到x < A(t)(因为 A(t) 和 sum 的第p位都为1,所以x的第p位的异或之后一定为0,所以x < A(t)
将第t堆石头留下x个,即取走 A(t)-x 个石头,则剩下的石头的异或和
= A(1) ^A(2) ^… ^x ^… ^A(n) A(t)变为x
= A(1) ^A(2) ^… ^A(t) ^sum ^… ^A(n) 代入 x == A(t) ^sum
= A(1) ^A(2) ^… ^A(t) ^A(1) ^A(2) ^… ^A(t) ^… ^A(n) ^… ^A(n) 代入 sum == A(1) ^A(2) ^… ^A(t) ^… ^A(n)
= 0 全部抵消
即一定存在一个A(t)和x,使玩家取走A(t)中的A(t)-x个石头后,让石头数量异或和为0,S态一定可以转变为T态
证毕

定理二:
T态 只能 转化为S态
证明:
反证,假设玩家从A(t)堆中取走石头变为A(t’)堆
原 = A(1) ^A(2) ^… ^A(t) ^… ^A(n) = 0
现 = A(1) ^A(2) ^… ^A(t’) ^… ^A(n) = 0
原 ^现 = 0 = A(1) ^A(2) ^… ^A(t) ^… ^A(n) ^ A(1) ^A(2) ^… ^A(t’) ^… ^A(n) = A(t) ^A(t’)
显然A(t) ^A(t’)!=0
即 T态不存在一个取法可以转化为T态,T态一定会变为S态
证毕

定理三:
对于S态,己方方法正确则 必胜(由定理一,己方总能转化为T态给对方,由定理二,对方只能转化为S态给己方,对方一直为T态,己方一直为S态,而全0的状态为T态,必胜
定理四:
对于T态,对方方法正确则 必败(同理,由定理一和二,对方一直为S态,己方一直为T态,必败

博弈过程:
S->T->…->S->T(全0)
游戏必定在S和T态间变换
先手为S态必胜

问题四:有n堆石头,每次必须取走一堆的任意个石头,无法取石头者胜

定义二:
孤单堆:石头等于1的堆
充裕堆:石头大于等于2的堆
S0态、T0态:充裕堆数量等于0的S态、T态
S1态:充裕堆数量等于1的S态(充裕堆数量只有1不会是T态)
S2态、T2态:充裕堆数量大于等于2的S态、T态

定理五:
S0态 必败(奇数个孤单堆,后手必胜
定理六:
T0态 必胜(同理,偶数个孤单堆,先手必胜
定理七:
S1态,己方方法正确 必胜(把充裕堆 转变为孤单堆或把一堆全部取走,调节孤单堆数量使对方成为S0先手
定理八:
T2态只能转变为S2态或S1态(无法一次性消除两个充裕堆,无法达到S0
定理九:
S2态只能转变为T2态(同理,无法一次性消除两个充裕堆,无法达到T0,而又不存在T1态
定理十:
T2态,对方方法正确 必败(由定理八:T2态只能转变为S2或S1,对方为S1态先手,己方必败,对手为S2态,一定会转变为T2态回来,直到进入S1态为止
定理十一:
S2态,己方方法正确 必胜(同理,S2只能转变为T2,循环,直到我方进入S1态,必胜

博弈过程:
S2->T2->…->S2->T2->S1->S0->T0…->S0->T0(全0)(细化问题一的博弈过程
S2->T2->…->S2->T2->S1->T0->S0…->T0->S0->T0(全0)(问题二
注意到唯一可变位置为 S1到T0或S0 的位置,除此之外 游戏一定是在S和T态中轮换,并且先手是S态時一定可以控制S1,后手为S态時亦然
所以得出结论:
对于问题二,先手为S2态、S1态、T0态 必胜

(四)SG函数
SG(x) = 最小 不属于SG(x后继状态)的 状态
意义:如果SG(0)为败,则有:对于任意x,如果SG(x)==0,则x状态必败
解释:SG(0)即为0个石头的状态,如果没有石头的可取的時候,玩家为败,则有后面的成立,即 有SG(x)==0,则x状态必败
证明:
由SG函数的定义可知,如果SG(x)==0,则所有 SG(后继局面) 值一定不为0,其意义为,必败态的后继状态一定为必胜态。
若SG(x)!=0,则 SG(后继局面) 值一定存在0,其意义为,必胜态可以转化为必败态。
即:己方面对必胜态时,由于必胜态可以转化为必败态,所以一定可以把必败态传给对方,
而必败态的后继状态一定为必胜态,对方就不得不把必胜态给己方,
所以当SG(x)!=0時,先手一定可以让自己永远处于必胜态,SG(x)==0则相反,必败。
证毕

小优化版SG函数模版

const int MAXN=1000+10;
int SG[MAXN];//SG[x]的值
int s[MAXN];//后继状态集合
int f[MAXN];//可选操作集,用于生成后继状态
int k;//操作集元素个数
void getSG(int n)
{SG[0] = 0; //主要是让终止状态的SG为0memset(s, -1, sizeof(s));for (int i = 1; i <= n; i++){for (int j = 1; j <= k && f[j] <= i; j++){s[SG[i - f[j]]] = i; //将所有后继的SG标记为i,然后找到后继的SG没有出现过的最小正整数//优化:注意这儿是标记成了i,刚开始标记成了1,这样每次需初始化mk,而标记成i就不需要了}int j = 0;while (s[j] == i)j++;SG[i] = j;}
}

(五)SG定理
n堆石头就有n个SG值,而整个游戏的SG值即为n个SG值的异或
也就是说,整个游戏的SG值就是子游戏的SG值的异或和
证明:
(待施工)
对于问题五:有n堆石头,每次必须取走一堆的1至m个石头,无法取石头者败
为问题一和问题三的组合
SG(x) = x>m ? 0 : x
每个石头堆都是独立的子问题

问题三:有n堆石头,每次必须取走一堆的任意个石头,无法取石头者败
可以取走任意石头,所以对于每一堆石头A(i),都有SG(A(i)) = mex(0,1,2…,A(i)-1) = A(i)
SG(A(1)) ^(SG(A(2))) ^… ^SG(A(n)) == A(i) ^A(2) ^… ^A(n) == 0
SG函数异或为0 就是 T态 ,必败

问题五:有n堆石头,每次必须取走x个石头,无法取石头者败,x属于给定集合f,例如f ={1,3,4},x只能为1 or 3 or 4
sg函数的k=3,f={1,2,3},即为答案

模板1:打表

//f[]:可以取走的石子个数
//sg[]:0~n的SG函数值
//hash[]:mex{}
int f[N],sg[N],hash[N];
void getSG(int n) {int i,j;memset(sg,0,sizeof(sg));for(i=1; i<=n; i++) {memset(hash,0,sizeof(hash));for(j=1; f[j]<=i; j++)hash[sg[i-f[j]]]=1;for(j=0; j<=n; j++) { //求mes{}中未出现的最小的非负整数if(hash[j]==0) {sg[i]=j;break;}}}
}

模板二:DFS

//注意 S数组要按从小到大排序 SG函数要初始化为-1 对于每个集合只需初始化1遍
//n是集合s的大小 S[i]是定义的特殊取法规则的数组
int s[110],sg[10010],n;
int SG_dfs(int x) {int i;if(sg[x]!=-1)return sg[x];bool vis[110];memset(vis,0,sizeof(vis));for(i=0; i<n; i++) {if(x>=s[i]) {SG_dfs(x-s[i]);vis[sg[x-s[i]]]=1;}}int e;for(i=0;; i++)if(!vis[i]) {e=i;break;}return sg[x]=e;
}

当我们面对由n个游戏组合成的一个游戏时,只需对于每个游戏找出求它的每个局面的SG值的方法,就可以把这些SG值全部看成Nim的石子堆,然后依照找Nim的必胜策略的方法来找这个游戏的必胜策略了!(Nim其实就是n个从一堆中拿石子的游戏求SG的变型,总SG=n个sg的异或)。(very important)

数论:

1.素数检验

普通素数判别 线性筛:略
二次筛法求素数[L,R]
当我们的素数很大很大,但区间跨度不大时。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
bool Prime[50010];      //存50000内素数判断结果
int Primer[1000010];    //存放区间[L,R]之间的素数
bool Prime1[1000010];   //判断区间[L,R]中的数是否为素数int IsPrime(){//第一次筛50000内的素数int num=0;for(int i=2;i<=50000;i++)Prime[i]=true;for(int i=2;i<=50000;i++){if(Prime[i]){Primer[num++]=i;for(int j=i+i;j<=50000;j+=i)Prime[j]=false;}}return num;     //num为50000范围内的素数个数
}int IsPrime2(ll a,ll b){/*
在第一次筛素数的基础上,利用50000以内的素数,筛去范围【a,b】之间的素数倍数,
剩下则为素数
*/int num=IsPrime();memset(Prime1,true,sizeof(Prime1));//Prime1数组用来存放范围【a,b】的素性判断if(a==1)  //这里注意1不是素数Prime1[0]=0; //这里表示0+1不为素数for(ll i=0;i<num&&Primer[i]*Primer[i]<=b;i++){ll begins=a/Primer[i]+(a%Primer[i]!=0);//上边的a/Primer算出应a为素数Primer[i]的多少倍//(a%Primer[i]!=0)表示应从Primer[i]的a/Primer[i]倍开始筛,还是a/Primer[i]+1倍筛if(begins==1)//若得出结果为所被筛素数的1倍,则从该素数的2倍开始筛begins++;for(begins=begins*Primer[i];begins<=b; begins+=Primer[i])Prime1[begins-a]=false;}//这里重新利用Primer数组,用来存放区间【a,b】间的素数,num为素数个数memset(Primer,0,sizeof(Primer));num=0;for(ll i=a;i<=b;i++)if(Prime1[i-a]==1)Primer[num++]=i-a;return num;     //num为区间[a,b]的素数个数
}int main(){ll a,b;scanf("%lld %lld",&a,&b);cout<<IsPrime2(a,b)<<endl; //输出区间[a,b]的素数个数
}

米勒拉宾素性检验
如果区间跨度很大,例如在1e14到1e18,用筛法求素数时数组开不了那么大。
所以我们改用米勒拉宾算法对素数进行检验。米勒拉宾素性测试算法是概率算法,不是确定算法。但是判断错误的概率非常非常小,速度又很快。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;bool Miller_Rabin(ll n){if(n<2)return false;else if(n==2)return true;ll q=0,m=n-1;while(m%2==0){  //当m为偶数时m>>=1;  //除2++q;   //计数}ll a=rand()%(n-2)+2;ll x1=(ll)pow(a,m)%n,x2;for(int i=1;i<=q;++i){x2=(x1*x1)%n;if(x2==1&&x1!=1&&x1!=n-1)return false;x1=x2;}if(x2!=1)return false;elsereturn true;
}int main(){srand((unsigned)time(NULL));long long num;while(cin>>num){if(num>1){if(Miller_Rabin(num))cout<<"true"<<endl;elsecout<<"false"<<endl;}}return 0;
}

Miller-Rabin素数检测算法

其基于以下两个定理。

使用了快速幂模和快速幂加模板mod_sys。下面代码只是miller-rabin核心代码。

 // 如果只是int范围内,可以将pow_v2改为pow,mlt改为普通乘法bool miller_rabin(ll a, ll n, ll q, ll m, mod_sys& mod) {a = mod.pow_v2(a, m);bool is_ordinary = true;for (int i = 0; i < q; ++i) {if (a == 1) {return is_ordinary;} else {is_ordinary = (a == n-1);a = mod.mlt(a,a);}}return (a==1)&&(is_ordinary); // 最后一项}// 使用miller_rabin检测是否是素数const int kCheckCnt = 8;// 为了随机数random_device rd;mt19937_64 gen(rd());bool miller_rabin(ll n) {if (n == 2) return true;if ((n <= 2) || (n&1^1)) return false;// 2^q×m表示原本输入的n-1ll m = n, q = 0;do { m >>= 1; ++q; } while(m&1^1);// 随机数生成,[1,n-1] 均匀分布uniform_int_distribution<> dis(1, n-1);mod_sys mod;mod.set_mod(n);for (int i = 0; i < kCheckCnt; ++i)if (!miller_rabin(dis(gen), n, q, m, mod))return false;return true;}

2.拉格朗日乘子法(求有等式约束条件的极值)



3.裂项(多项式分子分母拆分)

4.扩展欧几里得

x*a + y*b = 1.求x,y。
本代码可以得到a*x+b*y=c的x和y的最小正整数解

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;ll exgcd(ll a,ll b,ll &x,ll &y) {if(b==0){x=1;y=0;return a;}ll ans=exgcd(b,a%b,x,y);ll temp=x;x=y;y=temp-a/b*y;return ans;
}ll cal(ll a,ll b,ll c){ll x,y;ll gcd=exgcd(a,b,x,y);if(c%gcd!=0) return -1; //a*x+b*y=c 有解的充要条件:c%gcd(a,b)==0x*=c/gcd;    //a*x+b*y=gcdb/=gcd;if(b<0) b=-b; //得mod正数ll ans=x%b;   //求最小的解if(ans<=0) ans+=b;  //把解换成正值return ans;
}int main(){ll a,b;while(scanf("%lld %lld",&a,&b)!=EOF){ll ans=cal(a,b,1);  //a*x+b*y=1;这些常数可以随意换if(ans==-1) printf("sorry\n");else printf("%lld %lld\n",ans,(1-ans*a)/b);//x,y分别为ans,(1-ans*a)/b}return 0;
}

5.勾股数(直角三角形三边长)

当n > 2时:
当n为奇数时,另外两数为floor(n*n/2),floor(n*n/2)+1;
当n为偶数时,另外两数为n*n/4-1,n*n/4+1。

6.斯特林公式 (n越大越准确,求n!)

5.斯特林公式(n越大越准确)

求阶乘:

求阶乘位数:

7.牛顿迭代法 (求一元多次方程一个解)


下面代码输入方程的各个系数,即可得出有解或无解。

#include <bit/stdc++.h>
using namespace std;
typedef long long ll;int n;
ll a[50],b[50];double fun1(double x){ //原函数double ans=0;for(int i=0;i<=n;i++){double ans1=1;for(int j=1;j<=n-i;j++){ans1=ans1*x;}ans+=ans1*a[i];}return ans;
}double fun2(double x){  //求导函数double ans=0;for(int i=0;i<n;i++){double ans1=1;for(int j=1;j<=n-i-1;j++){ans1=ans1*x;}ans+=ans1*b[i];}return ans;
}int main(){int T;scanf("%d",&T);while(T--){int flag=0;scanf("%d",&n);for(int i=0;i<=n;i++){scanf("%lld",&a[i]);}for(int i=0;i<n;i++){  //求导后的系数b[i]=a[i]*(n-i);}double f1,f2,x,d;int count=0;  //统计迭代的次数x=100;    //随机赋予的方程的解do {f1=fun1(x);   //方程的值 f2=fun2(x);   //方程的导数 d=f1/f2; x-=d; //更新方程的值 count ++;//cout<<"第"<<count<<"次迭代方程的值为: "<<fun1(x);//cout<<"     当前的近似根为: "<<x<<endl;if(count==3000) {flag=1;break;} //没有结果就及时跳出,防止无限循环} while(fabs(d)>1e-5); //这里可以自行改精度if(flag==1){printf("Yes\n");  //有结果}else{printf("No\n");  //无结果}cout<<"总迭代次数:"<<count<<endl;cout<<"最终的近似根为:"<<x;
}
return 0;
}

8.同余定理(a≡b(mod m))

给定一个正整数m,如果两个整数a和b满足(a-b)能够被m整除,即(a-b)/m得到一个整数,那么就称整数a与b对模m同余,记作a≡b(mod m)。

  • 反身性:a≡a (mod m)
  • 对称性:若a≡b(mod m),则b≡a(mod m)
  • 传递性:若a≡b(mod m),b≡c(mod m),则a≡c(mod m)
  • 同余式相加:若a≡b(mod m),b≡c(mod m),则a ± c≡b ± d(mod m)
  • 同余式相乘:若a≡b(mod m),b≡c(mod m),则ac≡bd(mod m)
  • 线性运算:如果a≡b(mod m),c≡d(mod m),那么a ± c≡b ± d(mod m),且a *c≡b*d(mod m)
  • 除法:若ac≡bc(mod m) c≠0则 a≡b(mod m/gcd(c,m)) 其中gcd(c,m)表示c,m的最大公约数。特殊地,gcd(c,m)=1 则a≡b(mod m)
  • 幂运算:如果a≡b(mod m),那么a^n≡b^n(mod m)
  • 若a≡b(mod m),n|m,则a≡b(mod n)
  • 若a≡b (mod mi) (i=1,2…n) 则 a≡b (mod [m1,m2,…mn]) 其中[m1,m2,…mn]表示m1,m2,…mn的最小公倍数

9.线性求所有逆元的方法(求1~p modp的逆元)

10.中国剩余定理(n个同余方程x≡a1(modp1))





下面代码可以解决m1,m2…不互质的情况。但是要小心因为可能的答案太大二爆了long long而出现差错。

#include <bits/stdc++.h>
using namespace std;
const int maxn=1001;
typedef long long ll;
ll a[maxn],r[maxn];
ll cal_axb(ll a,ll b,ll mod){ll sum = 0;while (b){if (b&1)sum=(sum+a)%mod;b>>=1;a=(a+a)%mod;}return sum;
}void extendgcd(ll a, ll b, ll &d, ll &x, ll &y){if(b==0){d=a;x=1;y=0;return;}extendgcd(b,a%b,d,y,x);y-=x*(a/b);
}ll Multi_ModX(ll m[], ll r[], int n){ll m0,r0;m0=m[0];r0=r[0];for (int i=1;i<n;i++){ll m1=m[i],r1=r[i];ll k0,k1;ll tmpd;extendgcd(m0,m1,tmpd,k0,k1);if ((r1-r0)%tmpd!=0)return -1;k0*=(r1-r0)/tmpd;m1*=m0/tmpd;r0=(cal_axb(k0,m0,m1)+r0)%m1;m0=m1;}return (r0%m0+m0)%m0;
}int main(){int k;ll M;scanf("%d %lld",&k,&M); //几个方程,M是mod数for(int i=0;i<k;i++)scanf("%lld %lld",&a[i],&r[i]); //方程的a,方程的模数mll ans=Multi_ModX(a,r,k);if(ans==-1)printf("no answer");elseprintf("%lld", ans);return 0;
}

11.二次剩余((ax+k)2≡n(modp)(ax+k)^2≡n(mod p)(ax+k)2≡n(modp))



下面代码是解决模板:
(x)2≡n(modp)(x)^2≡n(mod p)(x)2≡n(modp)输入n,p得到解 x (无解,一个解,两个解)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll w;
struct num{ll x,y;
};num mul(num a,num b,ll p){num ans={0,0};ans.x=((a.x*b.x%p+a.y*b.y%p*w%p)%p+p)%p;ans.y=((a.x*b.y%p+a.y*b.x%p)%p+p)%p;return ans;
}ll powwR(ll a,ll b,ll p){ll ans=1;while(b){if(b&1)ans=1ll*ans%p*a%p;a=a%p*a%p;b>>=1;}return ans%p;
}
ll powwi(num a,ll b,ll p){num ans={1,0};while(b){if(b&1)ans=mul(ans,a,p);a=mul(a,a,p);b>>=1;}return ans.x%p;
}ll solve(ll n,ll p){n = (n%p + p)%p; //注意这句话if(n==0) return 0; //注意这句话if(p==2)return n;if(powwR(n,(p-1)/2,p)==p-1)return -1;//不存在ll a;while(1){a=rand()%p;w=((a*a%p-n)%p+p)%p;if(powwR(w,(p-1)/2,p)==p-1)break;}num x={a,1};return powwi(x,(p+1)/2,p);
}int main(){srand(time(0));int t;scanf("%d",&t);while(t--){ll n,p;scanf("%lld%lld",&n,&p);ll ans1=solve(n,p),ans2;if(ans1==-1) {printf("Hola!\n");continue;} //无解// if(!ans1){printf("0\n");continue;}else{ans2=p-ans1;if(ans1>ans2)swap(ans1,ans2);  if(ans1==ans2)printf("%lld\n",ans1); //只有一个解else printf("%lld %lld\n",ans1,ans2); //两个解}}
}

12.十进制矩阵快速幂(n很大很大的时候)

n的位数是1e6以上(long long存不了)的时候的矩阵快速幂,可以使用十进制矩阵快速幂。
假设推导矩阵如下:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+10;
ll mod;
char n[maxn];struct Mat{ll mat[4][4];Mat(){memset(mat,0,sizeof(mat));}void init(){for(int i=1;i<=2;i++)mat[i][i]=1;}Mat operator*(const Mat &a)const{  //矩阵乘法Mat ans;for(int i=1;i<=2;i++){   for(int j=1;j<=2;j++){for(int k=1;k<=2;k++){ans.mat[i][j]+=mat[i][k]*a.mat[k][j]%mod;ans.mat[i][j]%=mod;}}}return ans;}
};Mat pow(Mat a, ll b){ //矩阵快速幂Mat ans;ans.init();while(b){if(b&1)ans=ans*a;a=a*a;b>>=1;}return ans;
}int main(){ll a,b,x1,x0;scanf("%lld%lld%lld%lld",&x0,&x1,&a,&b);scanf("%s%lld",n,&mod);int len=strlen(n);Mat ans;ans.init();Mat res;res.mat[1][1]=a;res.mat[1][2]=b;   //矩阵初始化,根据题目而定res.mat[2][1]=1;for(int i=len-1;i>=0;i--){ans=ans*pow(res,n[i]-'0'); res=pow(res,10ll);   //用快速幂跑}Mat f;f.mat[1][1]=x1;f.mat[2][1]=x0;f=ans*f;printf("%lld\n",f.mat[2][1]);return 0;
}

13.欧拉函数

性质一:

性质二:

性质三:

性质四:

性质五:

性质六:

性质七:

性质八:
性质九:

性质十:

性质十一:

性质十二:

直接根据公式求欧拉函数

//euler(x) = x*(1 - 1/p1)*(1 - 1/p2)*(1 - 1/p3)...(1 - 1/pn)   p1 p2..是x的所有的质因子且各不相同  x != 0
//**质因子之和是euler(x)*x / 2
#include <iostream>
using namespace std;
int Euler(int n){int res=n,a=n;for(int i=2;i*i<=a;i++)if(a%i==0){  //i一定是素数res=res/i*(i-1);  //根据公式while(a%i==0)  //把相同的除数排除a/=i;}if(a>1)  //最后只剩下 小于4的素数  或者n本身就是素数res=res/a*(a-1);return res;
}
int main(){int n;while(cin>>n)cout<<Euler(n)<<endl;
}

用埃筛求欧拉函数
时间复杂度O(nloglogn)

void euler(int n){for (int i=1;i<=n;++i) phi[i]=i;for (int i=2;i<=n;++i)if (phi[i]==i)//这代表i是质数for (int j=i;j<=n;j+=i)phi[j]=phi[j]/i*(i-1);//把i的倍数更新掉
}

用欧拉筛求欧拉函数

void euler(int n){phi[1]=1;//1要特判 for (int i=2;i<=n;++i){if (flag[i]==0){//这代表i是质数 prime[++num]=i;phi[i]=i-1;}for (int j=1;j<=num&&prime[j]*i<=n;++j) {//经典的欧拉筛写法 flag[i*prime[j]]=1;//先把这个合数标记掉 if (i%prime[j]==0){phi[i*prime[j]]=phi[i]*prime[j];//若prime[j]是i的质因子,则根据计算公式,i已经包括i*prime[j]的所有质因子 break;//经典欧拉筛的核心语句,这样能保证每个数只会被自己最小的因子筛掉一次 }else phi[i*prime[j]]=phi[i]*phi[prime[j]];//利用了欧拉函数是个积性函数的性质 }}
}

14.费马小定理

对于质数p,任意整数a,均满足:a^p≡a(modp)

如果a不是p的倍数,也可以写成:a^(p−1)≡1(modp)

15.二阶常系数递推关系求解方法 (an=p∗an−1+q∗an−2)(a_n=p*a_{n-1}+q*a_{n-2})(an​=p∗an−1​+q∗an−2​)


16.高斯消元

带取mod的

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll p=1e9+7;
ll a[405][805];
ll inv(ll a,ll b, ll p) {ll cnt=1;while (b) {if (b&1)cnt=cnt*a%p;a=a*a%p;b>>=1;}return cnt;
}
void Gauss(int n){for (int i=1;i<=n;i++){for (int j=i;j<=n;j++){if (a[j][i]) {for (int k=1;k<=n<<1&&!a[i][i];k++)swap(a[i][k],a[j][k]);}}if (!a[i][i]) {puts("No Solution");return ;}ll inv_=inv(a[i][i],p-2,p);for (int j=i;j<=n<<1;j++)a[i][j]=a[i][j]*inv_%p;for (int j=1;j<=n;j++) {if (j!=i) {ll m=a[j][i];for (int k=i;k<=n<<1;k++)a[j][k]=(a[j][k]-m*a[i][k]%p+p)%p;}}}for (int i=1;i<=n;i++) {for (int j=n+1;j<=n<<1;j++)printf("%lld ",a[i][j]);printf("\n");}
}
int main() {int n;scanf("%d",&n);memset(a,0,sizeof(a));for (int i=1;i<=n;i++) {for (int j=1;j<=n;j++)scanf("%lld",&a[i][j]);a[i][n+i]=1;}Gauss(n);return 0;
}

16.矩阵快速幂

重载的:


#include<stdio.h>
#include<string.h>
#include<math.h>
typedef long long ll;
int mod;struct mat {int r,c;ll m[4][4];        //经测试最大开成590*590的 ll 型矩阵mat() {}mat(int r,int c):r(r),c(c) {}void clear() {memset(m,0,sizeof(m));}mat operator+(mat a)const {mat ans(r,c);for(int i=1; i<=r; i++) {for(int j=1; j<=c; j++) {ans.m[i][j]=(m[i][j]+a.m[i][j])%mod;}}return ans;}mat operator*(mat a)const {mat tmp(r,a.c);int i,j,k;for(i=1; i<=tmp.r; i++) {for(j=1; j<=tmp.c; j++) {tmp.m[i][j]=0;for(k=1; k<=c; k++) {tmp.m[i][j]=(tmp.m[i][j]+(m[i][k]*a.m[k][j])%mod)%mod;}}}return tmp;}mat operator^(int n)const {       //需要时可以用 ll n,注意运算符优先级比较低,多用括号;mat ans(r,r),tmp(r,r);memcpy(tmp.m,m,sizeof(tmp.m));ans.clear();for(int i=1; i<=ans.r; i++) {ans.m[i][i]=1;}while(n) {if(n&1)ans=ans*tmp;n>>=1;tmp=tmp*tmp;}return ans;}void print()const {for(int i=1; i<=r; i++) {for(int j=1; j<=c; j++) {printf("%lld",m[i][j]);if(j==c)printf("\n");else printf(" ");}}}};

不重载的:

#include<iostream>
#include<cstring>
#define mod 1000000007
#define ll long long
using namespace std;
struct Mat {ll m[101][101];
};//结构体存矩阵
Mat a,e;//a是输入的矩阵,e是单位矩阵
ll n,p;
Mat Mul(Mat x,Mat y) { //矩阵乘Mat c;for(int i=1; i<=n; i++)for(int j=1; j<=n; j++)c.m[i][j]=0;for(int i=1; i<=n; i++)for(int j=1; j<=n; j++)for(int k=1; k<=n; k++)c.m[i][j]=c.m[i][j]%mod+x.m[i][k]*y.m[k][j]%mod;return c;
}
Mat pow(Mat x,ll y) { //矩阵快速幂Mat ans=e;while(y) {if(y&1)ans=Mul(ans,x);x=Mul(x,x);y>>=1;}return ans;
}int main() {//输入cin>>n>>p;for(int i=1; i<=n; i++)for(int j=1; j<=n; j++)cin>>a.m[i][j];//算法核心for(int i=1; i<=n; i++)e.m[i][i]=1;Mat ans=pow(a,p);//输出for(int i=1; i<=n; i++) {for(int j=1; j<=n; j++)cout<<ans.m[i][j]%mod<<" ";cout<<endl;}return 0;
}

18.分解质因数

应用
1,求出数n的因子个数
(1+a1)∗(1+a2)∗(1+a3)∗(1+a4)......∗(1+an)(1+a1)*(1+a2)*(1+a3)*(1+a4)......*(1+an)(1+a1)∗(1+a2)∗(1+a3)∗(1+a4)......∗(1+an)
a1,a2,这些分别是素数因子的幂次数
因为当我的a1=3时那我n的因子肯定会有 p1^0 p1^1 p1^2 p1^3 这四个数
然后再和p2的个数搭配起来就是两个数的因子数相乘了 p1^x 可以与 p2^y 随意搭配,所以进行乘法

2.求所有的因子之和
这个其实也就是和上面这个一样的道理,不过我们求的是和,所以我们要把所有的因子和求出来
公式:(q10+q11+q12.....q1a1)∗(q20+q21+q22.....q2a2)∗........∗(qn0+qnn+qn2.....qnan)(q1^0+q1^1+q1^2.....q1^a1)*(q2^0+q2^1+q2^2.....q2^a2)*........*(qn^0+qn^n+qn^2.....qn^an)(q10+q11+q12.....q1a1)∗(q20+q21+q22.....q2a2)∗........∗(qn0+qnn+qn2.....qnan)
因为每一项都有个1就代表是原来的自己那一项,后面都是组合项

//将A分解成素因子的积,A = p[0]^n[0]+p[1]^n[1]+....+p[k-1 ]^n[k-1];
int Div(int A) {int k = 0, i;for (i = 2; i * i <= A;) {if (A % i == 0) {n[k] = 0;p[k] = i;while (!(A % i)) { //有问题吗n[k]++;A /= i;}k++;}if (i == 2)i++;elsei += 2;}if (A != 1) {p[k] = A;n[k++] = 1;}return k;
}

19.线性递推式BM(杜教)

#include <bits/stdc++.h>
using namespace std;
#define rep(i, a, n) for (int i = a; i < n; i++)
#define per(i, a, n) for (int i = n - 1; i >= a; i--)
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(), (x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef vector<int> VI;
typedef long long ll;
typedef pair<int, int> PII;
const ll mod = 1000000007;
ll powmod(ll a, ll b) {ll res = 1;a %= mod;assert(b >= 0);for (; b; b >>= 1) {if (b & 1) res = res * a % mod;a = a * a % mod;}return res;
}
// headint _, n;
namespace linear_seq {const int N = 10010;
ll res[N], base[N], _c[N], _md[N];vector<int> Md;
void mul(ll *a, ll *b, int k) {rep(i, 0, k + k) _c[i] = 0;rep(i, 0, k) if (a[i]) rep(j, 0, k) _c[i + j] =(_c[i + j] + a[i] * b[j]) % mod;for (int i = k + k - 1; i >= k; i--)if (_c[i])rep(j, 0, SZ(Md)) _c[i - k + Md[j]] =(_c[i - k + Md[j]] - _c[i] * _md[Md[j]]) % mod;rep(i, 0, k) a[i] = _c[i];
}
int solve(ll n, VI a, VI b) {  // a 系数 b 初值 b[n+1]=a[0]*b[n]+...//        printf("%d\n",SZ(b));ll ans = 0, pnt = 0;int k = SZ(a);assert(SZ(a) == SZ(b));rep(i, 0, k) _md[k - 1 - i] = -a[i];_md[k] = 1;Md.clear();rep(i, 0, k) if (_md[i] != 0) Md.push_back(i);rep(i, 0, k) res[i] = base[i] = 0;res[0] = 1;while ((1ll << pnt) <= n) pnt++;for (int p = pnt; p >= 0; p--) {mul(res, res, k);if ((n >> p) & 1) {for (int i = k - 1; i >= 0; i--) res[i + 1] = res[i];res[0] = 0;rep(j, 0, SZ(Md)) res[Md[j]] =(res[Md[j]] - res[k] * _md[Md[j]]) % mod;}}rep(i, 0, k) ans = (ans + res[i] * b[i]) % mod;if (ans < 0) ans += mod;return ans;
}
VI BM(VI s) {VI C(1, 1), B(1, 1);int L = 0, m = 1, b = 1;rep(n, 0, SZ(s)) {ll d = 0;rep(i, 0, L + 1) d = (d + (ll)C[i] * s[n - i]) % mod;if (d == 0)++m;else if (2 * L <= n) {VI T = C;ll c = mod - d * powmod(b, mod - 2) % mod;while (SZ(C) < SZ(B) + m) C.pb(0);rep(i, 0, SZ(B)) C[i + m] = (C[i + m] + c * B[i]) % mod;L = n + 1 - L;B = T;b = d;m = 1;} else {ll c = mod - d * powmod(b, mod - 2) % mod;while (SZ(C) < SZ(B) + m) C.pb(0);rep(i, 0, SZ(B)) C[i + m] = (C[i + m] + c * B[i]) % mod;++m;}}return C;
}
int gao(VI a, ll n) {VI c = BM(a);c.erase(c.begin());rep(i, 0, SZ(c)) c[i] = (mod - c[i]) % mod;return solve(n, c, VI(a.begin(), a.begin() + SZ(c)));
}
};  // namespace linear_seqint main() {while (~scanf("%d", &n)) {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(4);v.push_back(7);v.push_back(13);v.push_back(24);// VI{1,2,4,7,13,24}printf("%d\n", linear_seq::gao(v, n - 1));}
}

20.线性一次方程组解的情况

/*
Berlekamp-Massey Algorithm:求常系数线性递推式

数列:x[0], x[1], x[2]…, x[N].

增量法求系数:

假设 现有对于前i - 1项都成立的多项式G(x),但是对于第i项不成立,需要修正
定义:delta[k] 为 G(k) 与 x[k] 的差值
上面的假设 即 delta[k]==0(1<=k<i) 且 delta[i]!=0

求出F(x) ,使 F(i)=y(y!=0) 且 F(k) = 0, k ∈ (1,i-1)
设 G’(x) = G(x) - delta[i] * F(x)/y
G’(x) 即为 修正后的多项式
证明:
(就是加上只影响G(i)而不影响前面的东西来修正第i项。。)
1<=k<i : G’(k) = G(k) + F(k) = G(k) + 0 = G(k) = x[k]
k == i : G’(k) = G(i) + F(i) = (delta + x[i]) - (delta * y/y) = x[i]
满足 G(k) = x[k] (1<=k<=i)
即 delta[k] = G[k] - x[k] = 0 (1<=k<=i)
证毕

F(x)求法:
设 以前的在第j项就出现错误的未修正系数为R(x),R(x)即为 第一次错误出现在第j项的未修正的G(x)
设 mul = G(x)的delta[i]/R(x)的delta[j]
F(x)前面加入 i−j−1 个 0 , 后面加入 mul*{1,-R(x)},就是把 mul 和R(x)的每一项乘上-k后 接到i-j-1个0后面
就构造出F(x)了

证明:
先考虑 F(x) 仅加入 i−j−1个0 和 {0,R(x)}
加入 i−j−1个0 和 {0,-R(x)} 之后,因为加入了i-j-1+1个元素,F(x)中R(x)的系数位置往后推移了i-j,此时F(i)和R(j)对应的x[k]相同,F(x)等价于R(x)
F(i) = R(j);
F(i-k) = R(j-k) (1<=k<j)

然后考虑 F(x) 是加入了 i−j−1个0 和 {1,-R(x)}
F(i) = x[j] - R(j) = delta[j]; // R(x)的delta[x]
F(i-k) = x[i-k] - R(i-k) = delta[i-k] = 0 (1<=k<j) // R(x)的delta[x]
因为 j是R(x)第一个错误出现的地方,而 j-k < j ,所以 对于 R(x)的delta[j-k] = 0;
这样就 构造出了 仅第i项不为0 ,小于i的项都是 都是0的 的F(x)了

最后就是乘上倍数,抵消delta[i] // G(x)的delta[x]
G’(x) = G(x) - mulF(x)/F(i)
G’(x) 对小于i 的值不变,依旧正确
对于 第i项 G’(i) 的 delta[i] - mul
F(i) = delta - delta[i]/delta[j] * delta[j] = 0;
证毕
*/

//正常版,需要double,小一点的整数系数可以直接输出,优化版会输出逆元就不知道原来系数是什么了:
#include <bits/stdc++.h>
using namespace std;const int MAXN = 3000 + 10;
int n;
vector<double> ps[MAXN]; //系数
int pn = 0;              // now ps = ps[pn];
int fail[MAXN];          // fail[i]=j ps[i] 在 第j项 错误了
double x[MAXN];          // 原数列
double delta[MAXN];      // 差值
int main()
{freopen("0.in", "r", stdin);// 输入 数列scanf("%d", &n);for (int i = 1; i <= n; i++)scanf("%lf", x + i);// bmint best = 0; //用来修正当前系数的最佳的以前的系数,即用来求F(x)最好的系数for (int i = 1; i <= n; i++){// calculate deltadouble dt = -x[i];for (int j = 0; j < ps[pn].size(); j++)dt += x[i - j - 1] * ps[pn][j];delta[i] = dt;// delta == 0,系数正确,不需要修正if (fabs(dt) <= 1e-7)continue;// if delte != 0 ,系数不正确,需要修正fail[pn] = i; // 记录第pn个系数ps 不符合 数列第i项if (!pn)      //如果pn==0 ,即为第一个不为0的元素,所以可能的系数个数为i个,初始化为i个0{ps[++pn].resize(i);continue;}//pn!=0 ,使用第best个系数来修正vector<double> &ls = ps[best];      //复制最佳的用来修正的系数double k = -dt / delta[fail[best]]; //计算k倍vector<double> cur;                 //存放修正后的系数cur.resize(i - fail[best] - 1);     //加入 i - fail[best] - 1 个0//加入剩下元素的k倍cur.push_back(-k);for (int j = 0; j < ls.size(); j++)cur.push_back(ls[j] * k);//补0?if (cur.size() < ps[pn].size())cur.resize(ps[pn].size());//G'(x) = G(x) + F(x)for (int j = 0; j < ps[pn].size(); j++)cur[j] += ps[pn][j];// 维护当前最优的一个递推式,使递推式最短,就是选最近且最短的if (i - fail[best] + (int)ps[best].size() >= ps[pn].size())best = pn;//记录修正后的系数ps[++pn] = cur;}// 输出 系数cout << ps[pn].size() << endl;for (unsigned g = 0; g < ps[pn].size(); g++)cout << ps[pn][g] << " ";return 0;
}
// 取模 + 逆元 + 滚动 + 似乎是快速乘或者是其他东西看不懂的 优化版
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
const int MAXN = 3000;
ll qp(ll a, ll b)
{ll x = 1, a %= MOD;while (b){if (b & 1)x = x * a % MOD;a = a * a % MOD, b >>= 1;}return x;
}
namespace linear_seq
{inline vector<int> BM(vector<int> x)
{vector<int> ls, cur; // ls = ps[best]int lf, ld;          //lf = best , ld = delta[best]for (int i = 0; i < int(x.size()); ++i){ll t = -x[i] % MOD;for (int j = 0; j < int(cur.size()); ++j)t = (t + x[i - j - 1] * (ll)cur[j]) % MOD;if (!t)continue;if (!cur.size()){cur.resize(i + 1);lf = i;ld = t;continue;}ll k = -t * qp(ld, MOD - 2) % MOD;vector<int> c(i - lf - 1);c.push_back(-k);for (int j = 0; j < int(ls.size()); ++j)c.push_back(ls[j] * k % MOD);if (c.size() < cur.size())c.resize(cur.size());for (int j = 0; j < int(cur.size()); ++j)c[j] = (c[j] + cur[j]) % MOD;if (i - lf + (int)ls.size() >= (int)cur.size())ls = cur, lf = i, ld = t;cur = c;}vector<int> &o = cur;for (int i = 0; i < int(o.size()); ++i)o[i] = (o[i] % MOD + MOD) % MOD;// cout << o.size() << endl;// for (int i = 0; i < o.size(); ++i)//     cout << o[i] << ' ';// cout << endl;return o;
}
int N;
ll a[MAXN], h[MAXN], t_[MAXN], s[MAXN], t[MAXN];
inline void mull(ll *p, ll *q)
{for (int i = 0; i < N + N; ++i)t_[i] = 0;for (int i = 0; i < N; ++i)if (p[i])for (int j = 0; j < N; ++j)t_[i + j] = (t_[i + j] + p[i] * q[j]) % MOD;for (int i = N + N - 1; i >= N; --i)if (t_[i])for (int j = N - 1; ~j; --j)t_[i - j - 1] = (t_[i - j - 1] + t_[i] * h[j]) % MOD;for (int i = 0; i < N; ++i)p[i] = t_[i];
}
inline ll calc(ll K)
{for (int i = N; ~i; --i)s[i] = t[i] = 0;s[0] = 1;if (N != 1)t[1] = 1;elset[0] = h[0];for (; K; mull(t, t), K >>= 1)if (K & 1)mull(s, t);// cout << N << endl;// for (int i = 0; i < N; ++i)//     cout << s[i] << ' ';// cout << endl;ll su = 0;for (int i = 0; i < N; ++i)su = (su + s[i] * a[i]) % MOD;return (su % MOD + MOD) % MOD;
}
inline int gao(vector<int> x, ll n)
{if (n < int(x.size()))return x[n];vector<int> v = BM(x);N = v.size();if (!N)return 0;for (int i = 0; i < N; ++i)h[i] = v[i], a[i] = x[i];return calc(n);
}
} // namespace linear_seqint main()
{freopen("0.in", "r", stdin);vector<int> x;int t;while (cin >> t)x.push_back(t);cout << linear_seq::gao(x, 3000) << endl;return 0;
}

求解行列式的逆矩阵,伴随矩阵,矩阵不全随机数不全

//求解行列式的逆矩阵,伴随矩阵,矩阵不全随机数不全
#include<bits/stdc++.h>
#define LL long long int
#define FIO ios::sync_with_stdio(false)
#define FRD freopen("input.txt","r",stdin)
#define lowbit(x) (x&-(x))
#define INF 1e18
#define ms(x) memset(x,0,sizeof x)
#define pb(x) push_back(x)
#define lson l,mid,now<<1
#define rson mid+1,r,now<<1|1
#define UNIQUE(x) unique(x.begin(),x.end())
#define SORT(x) sort(x.begin(),x.end())
#define LISAN(x) x.erase(UNIQUE(x),x.end())
#define pii pair<int,int>
#define pll pair<LL,LL>
#define N 510
#define SAFE_DELETE(p) {if(p) {delete[]p;p=nullptr;}}
const LL MOD=1e9+7;
using namespace std;
template<class T> inline T Min(const T& a,const T& b){return a<b?a:b;}
template<class T> inline T Max(const T& a,const T& b){return a<b?b:a;}
inline void inc(int &x,int &v,int &mod){x+=v;if(x>=mod) x-=mod;}
inline void dec(int &x,int &v,int &mod){x-=v;if(x<0) x+=mod;}
inline int read(){char ch = getchar();int x = 0, f = 1;while(ch < '0' || ch > '9') {if(ch == '-') f = -1;ch = getchar();}while('0' <= ch && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}return x * f;}
inline LL readl(){char ch = getchar();LL x = 0, f = 1;while(ch < '0' || ch > '9') {if(ch == '-') f = -1;ch = getchar();}while('0' <= ch && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}return x * f;}
#define aaa for(int q=1; q<=n; ++q){for(int p=1; p<=n; ++p) cout << a[q][p] << " "; cout << endl;}
#define bbb for(int q=1; q<=n; ++q){for(int p=1; p<=n; ++p) cout << b[q][p] << " "; cout << endl;}
const LL Mod=1000000007;
LL a[N][N],b[N][N];
LL ksm(int a,int k){int res=1;while(k){if(k&1)res=1LL*res*a%Mod;a=1LL*a*a%Mod;k>>=1;}return res;
}
void solve(int n)
{for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)b[i][j]=(i==j);LL det=1;for(int i=1;i<=n;i++){int t=i;for(int k=i;k<=n;k++)if(a[k][i])t=k;if(t!=i)det*=-1;for(int j=1;j<=n;j++){swap(a[i][j],a[t][j]);swap(b[i][j],b[t][j]);}
//      aaa;
//      bbb;if(!a[i][i]){puts("No Solution"); return;}det=1LL*a[i][i]*det%Mod;LL inv=ksm(a[i][i],Mod-2);for(int j=1;j<=n;j++){a[i][j]=1LL*inv*a[i][j]%Mod;b[i][j]=1LL*inv*b[i][j]%Mod;}for(int k=1;k<=n;k++){if(k==i)continue;LL tmp=a[k][i];for(int j=1;j<=n;j++){a[k][j]=(a[k][j]-1LL*a[i][j]*tmp%Mod+Mod)%Mod;b[k][j]=(b[k][j]-1LL*b[i][j]*tmp%Mod+Mod)%Mod;}}
//      aaa;
//      bbb;}//bbb 即逆矩阵for(int q=1; q<=n; ++q){for(int p=1; p<=n; ++p) cout << b[q][p] << " "; cout << endl;}/*det=(det+Mod)%Mod;for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){if((i+j)%2==0)printf("%lld",b[j][i]*det%MOD);elseprintf("%lld",(MOD-b[j][i]*det%MOD)%MOD);if(j!=n)printf(" ");elseprintf("\n");}}*///伴随矩阵
}
int main(){//如果是n-1行n列,第一列随机n个数把矩阵补全成n×n的.那么就是要算伴随矩阵的第一行,也就是逆矩阵的第一列,高斯消元即可。srand(time(0));int n;scanf("%d",&n);for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%lld",&a[i][j]);//for(int i=1;i<=n;i++)    a[1][i]=1;solve(n);return 0;
}

ACM巨全模板(上)相关推荐

  1. ACM巨全模板(下)

    柯氏模板(下) 柯氏模板(上) 柯氏模板(中) pdf下载 本模板博主还在完善ing-谢谢大家观看 计算几何: 1.三角形 (求面积)) 2.多边形 3.三点求圆心和半径 4.扫描线 (矩形覆盖求面积 ...

  2. HTML和CSS仿唯品会首页,ecshop仿唯品会2014全模板带团购品牌特卖

    ecshop仿唯品会2014全模板带团购品牌特卖,一款简洁时尚的综合通用类模板,特别适用于品牌特卖类电商.整站右侧悬浮式导航特效,头部下拉切换式商品分类树功能.首页多个大幅轮播广告图切换,热销商品功能 ...

  3. ACM期刊LaTex模板使用

    重要:如果编译失败删除新生成的文件 .aux .bbl 文件重新开始! 1.下载LaTex模板 下载网址:https://www.acm.org/publications/taps/latex-bes ...

  4. LESSON 9.5 随机森林在巨量数据上的增量学习

    五 随机森林在巨量数据上的增量学习 集成学习是工业领域中应用最广泛的机器学习算法.实际工业环境下的数据量往往十分巨大,一个训练好的集成算法的复杂程度与训练数据量高度相关,因此企业在应用机器学习时通常会 ...

  5. 上云的先行军,QQ 率先完成了20万台服务器全量上云,是怎么做到的?

    作者 | 田晓旭 截止到目前,QQ 所有的业务都已经迁移到了腾讯云上. 2019 年 1 月 4 日,腾讯技术委员会正式成立,同时下设了两个项目组"开源协同"和"自研上云 ...

  6. “Word自动更改后的内容保存到通用文档模板上。是否加载该模板?“的解决办法...

    在win7系统下,Word2010出现了不能正常关闭.打开一个已有word文档,点击右上角关闭按钮后,先提示"word已停止工作,windows正在检查该问题的解决方案",随后提示 ...

  7. 腾讯 QQ 产品已经实现全量上云;中科院计算所发明新编程语言“木兰”;Electron 7.1.9 发布 | 极客头条...

    整理 | 屠敏 快来收听极客头条音频版吧,智能播报由标贝科技提供技术支持. 「极客头条」-- 技术人员的新闻圈! CSDN 的读者朋友们早上好哇,「极客头条」来啦,快来看今天都有哪些值得我们技术人关注 ...

  8. 阿里云cenos 6.5 模板上安装 docker

    本章将介绍在阿里云的 Centos6.5 模板上安装 Docker 以及在 Ubuntu 14.04 模板上安装 Docker 的过程 Centos 6.5 模板上使用Docker 首先,通过 ssh ...

  9. 分析全基因组上的蛋白信息

    背景 在ncbi上查一个细菌的全基因组,搜取全基因组上的这五种信息:gene,locus_tag,note,location,product,蛋白序列的前10个氨基酸(为什么爬这个最后讲),并分析这个 ...

最新文章

  1. python简单代码演示效果-Python Selenium的简单演示程序
  2. Beta版冲刺Day1
  3. PMCAFF《产品经理第一课》第三期开始报名!天团导师再次升级,631培训模式升级...
  4. 天兔(Lepus)监控邮件推送安装配置
  5. cpu时间片 linux,能讲一下在Linux系统中时间片是怎么分配的还有优先级的具体算法是...
  6. phpstudy2018选择php7,phpstudy(小皮面板)和phpstudy2018 配置php的区别
  7. vue 页面url参数_Vue下URL地址栏参数改变却不能刷新界面
  8. h5优秀控件_H5匠人手册:霸屏H5实战解密
  9. 设置loadrunner中每个mdrv.exe进程中包含的vuser个数
  10. 上海.NET俱乐部聚会筹备进展
  11. 【印刷字符识别】基于matlab OCR印刷字母+数字识别【含Matlab源码 287期】
  12. 喇叭花日记080501
  13. 编写一程序从键盘输入圆锥体的半径r 高度h 并计算其体积
  14. 转 脏字/ 敏感词汇搜索算法
  15. Android4.4 状态栏WiFi图标显示流程
  16. 小白兔写话_一年级小白兔看图写话
  17. 写一个函数,输入n,求斐波那契数列的第n项。
  18. t3系统总显示得不到服务器,用友T3不能连接服务器你好,用友T3总是说连接不到服务...
  19. Java之自动拆装箱及享元模式应用
  20. 花园植物花卉高清照片合集 Garden photo Pack

热门文章

  1. Keil4与STC-ISP操作
  2. wetool 接入图灵机器人_wetool自动接受新好友wetool使用教程-客服
  3. LOGO(小海龟)编程之父留给我们的思想遗产
  4. java mp3 信息_java读取MP3的信息
  5. PAT-ADVANCED1011——World Cup Betting
  6. PC-DMIS 2019 EROWA校验夹具外部坐标系
  7. 【转】推荐系统原理、工程、大厂(Youtube、BAT、TMB)架构干货分享
  8. c++,参数模板使用,实验报告
  9. ibm服务器维修站点,上海服务器维修,上海HP服务器维修,上海IBM服务器维修,上海SUN服务器维修,上海DELL服务器维修,上海小型机维修,上海工业设备维修,上海工作站维修,上海工控机维修...
  10. ExtremeTable 使用简介