Week3学习总结
最长上升子序列
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<i;j++)
if(a[i]>a[j]) f[i]=max(f[j]+1,f[i]);
}
最长下降子序列
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<i;j++)
if(a[i]<a[j]) f[i]=max(f[j]+1,f[i]);
}
从某点向两端
//开口向上是从两端都做最长下降子序
for(int i=n;i>=n;i--)
{
f[i]=1;
for(int j=n;j>i;j--)
if(a[i]<a[j]) f[i]=max(f[j]+1,f[i]);
}
//开口向下是从两端都做最长上升子序
for(int i=n;i>=n;i--)
{
f[i]=1;
for(int j=n;j>i;j--)
if(a[i]>a[j]) f[i]=max(f[j]+1,f[i]);
}
Diworth定理
一个序列中下降子序列的最少划分数个数等于最长上升子序列的长度。
一个序列中上升子序列的最少划分数个数等于最长下降子序列的长度。
动态规划
在推导 dp 方程时,我们时常会感到毫无头绪,而实际上 dp 方程也是有迹可循的,总的来说,需要关注两个要点:状态,决策和转移。其中 “状态” 又最为关键,决策最为复杂。
【状态】
关于 “状态” 的优化可以从很多角度出发,思维难度及其高,有时候状态选择的好坏会直接导致出现暴零和满分的分化。【决策】
与 “状态” 不同,“决策” 优化则有着大量模板化的东西,在各大书籍,文章上你都可以看到这样的话:只要是形如 XXXXXX 的状态转移方程,都可以用 XXXXXX 进行优化。【转移】
“转移” 则指由最优决策点得到答案的转移过程,其复杂度一般较低,通常可以忽略,但有时也需要特别注意并作优化。
目前对dp的理解总结
dp数组的维度取决于状态变量有几个,但有时可以进行维度压缩。
循环取决于状态变量有几个,状态转移方程的数量取决于决策有几个。
状态转移方程里面绝大多数的转移都是和循环对应的方向相反的。
首先确定dp空间的一个角(如dp[1] [1]),然后通过循环使其走到相距最远的另一个角上(如dp[n] [n]),要控制每一步的行走方向(决策方向)才能达到最优解,而状态转移方程就对应了每一种决策。
回文字符串
#include<bits/stdc++.h>
using namespace std;
char a[1002];
char b[1002];
int dp[1002][1002];
int main()
{
cin>>a+1;
int len=strlen(a+1);
for(int i=1,j=len; i<=len+1; i++,j--)
b[j]=a[i];
for(int i=1; i<=len; i++)
{
for(int j=1; j<=len; j++)
{
if(a[i]==b[j])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
}
}
int ans=len-dp[len][len];
cout<<ans<<endl;
return 0;
}
数塔
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int a[N][N];
int main()
{
int n;
cin>>n;
while(n--)
{
int h;
cin>>h;
for(int i=1; i<=h; i++)
{
for(int j=1; j<=i; j++)
{
cin>>a[i][j];
}
}
for(int i=h; i>=1; i--)
{
for(int j=1; j<=i;j++)
{
a[i][j]+=max(a[i+1][j],a[i+1][j+1]);
}
}
cout<<a[1][1]<<endl;
}
return 0;
}
乌龟棋
#include<iostream>
#include<memory.h>
using namespace std;
int n,m;
int a[400];
int b[5];
int f[42][42][42][42];
int main(){
cin >> n>>m;
for(int i=1;i<=n;i++){
cin >> a[i];
}
memset(b,0,sizeof(b));
for(int i=0;i<m;i++){
int x;
cin >> x;
b[x]++;
}
//f[i][j][k][h]表示使用i张1,j张2,,,所获得的最大分数
f[0][0][0][0]=a[1];
for(int i=0;i<=b[1];i++){
for(int j=0;j<=b[2];j++){
for(int k=0;k<=b[3];k++){
for(int h=0;h<=b[4];h++){
int x=i+2*j+k*3+h*4;//增加的步数
//因为从1开始,那么下一步position为x+1,所以取x+1位置的数字
if(i) f[i][j][k][h]=max(f[i][j][k][h],f[i-1][j][k][h]+a[x+1]);
if(j) f[i][j][k][h]=max(f[i][j][k][h],f[i][j-1][k][h]+a[x+1]);
if(k) f[i][j][k][h]=max(f[i][j][k][h],f[i][j][k-1][h]+a[x+1]);
if(h) f[i][j][k][h]=max(f[i][j][k][h],f[i][j][k][h-1]+a[x+1]);
}
}
}
}
cout<<f[b[1]][b[2]][b[3]][b[4]]<<endl;
return 0;
}
欧拉函数
筛法求欧拉函数
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]];//利用了欧拉函数是个积性函数的性质
}
}
}
组合数
方法1:递推
C
(
m
,
n
)
=
C
(
m
−
1
,
n
)
+
C
(
m
−
1
,
n
−
1
)
,
运用
D
P
思想直接地递推出结果
C(m,n) = C(m-1,n) + C(m-1,n-1) , 运用DP思想直接地递推出结果
C(m,n)=C(m−1,n)+C(m−1,n−1),运用DP思想直接地递推出结果
const int N=2010,mod=1e9+7;
int c[N][N];
void init()
{
for(int i=0;i<N;i++)
for(int j=0;j<=i;j++)
if(!j) c[i][j]=1;
else c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
方法2:预处理阶乘
C
(
m
,
n
)
=
m
!
/
n
!
/
(
m
−
n
)
!
,先预处理出
1
N
的阶乘和阶乘的乘法逆元,后运用费马小定理处理
C(m,n) = m!/n!/(m-n)! ,先预处理出1~N的阶乘和阶乘的乘法逆元,后运用费马小定理处理
C(m,n)=m!/n!/(m−n)!,先预处理出1 N的阶乘和阶乘的乘法逆元,后运用费马小定理处理
typedef long long LL;
const int N=1e5+10,mod=1e9+7;
int fact[N],infact[N];
int qmi(int a,int k,int p)
{
int res=1;
while(k)
{
if(k&1) res=(LL) res*a%p;
a=(LL)a*a%p;
k>>=1;
}
return res;
}
int main()
{
fact[0]=infact[0]=1;
for(int i=1;i<N;i++)
{
fact[i]=(LL)fact[i-1]*i%mod;
infact[i]=(LL)infact[i-1]*qmi(i,mod-2,mod)%mod;
}
...
printf("%d\n",(LL)fact[a]*infact[b]%mod*infact[a-b]%mod);
...
}
方法3:卢卡斯定理
带入卢卡斯公式,递归调用,得到结果。
带入卢卡斯公式,递归调用,得到结果。
带入卢卡斯公式,递归调用,得到结果。
typedef long long LL;
int qmi(int a,int k,int p)
{
int res=1;
while(k)
{
if(k&1) res=(LL)res*a%p;
a=(LL)a*a%p;
k>>=1;
}
return res;
}
typedef long long LL;
int qmi(int a,int k,int p)
{
int res=1;
while(k)
{
if(k&1) res=(LL)res*a%p;
a=(LL)a*a%p;
k>>=1;
}
return res;
}
int lucas(LL a,LL b,int p)
{
if(a<p && b<p) return C(a,b,p);
return (LL)C(a%p,b%p,p)*lucas(a/p,b/p,p)%p;
}
int main()
{
int n;
cin>>n;
while(n--)
{
LL a,b;
int p;
cin>>a>>b>>p;
cout<<lucas(a,b,p)<<endl;
}
return 0;
}
方法4:分解质因子法
将分子分母的质因数都分解后消去,存在数组中
(
s
u
m
[
i
]
)
,运用高精度乘法算出结果。
将分子分母的质因数都分解后消去,存在数组中(sum[i]),运用高精度乘法算出结果。
将分子分母的质因数都分解后消去,存在数组中(sum[i]),运用高精度乘法算出结果。
const int N = 5010;
int primes[N], cnt;
int sum[N];
bool st[N];
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int get(int n, int p)
{
int res = 0;
while (n)
{
res += n / p;
n /= p;
}
return res;
}
vector<int> mul(vector<int> a, int b)
{
vector<int> c;
int t = 0;
for (int i = 0; i < a.size(); i ++ )
{
t += a[i] * b;
c.push_back(t % 10);
t /= 10;
}
while (t)
{
c.push_back(t % 10);
t /= 10;
}
return c;
}
int main()
{
int a, b;
cin >> a >> b;
get_primes(a);
for (int i = 0; i < cnt; i ++ )
{
int p = primes[i];
sum[i] = get(a, p) - get(a - b, p) - get(b, p);
}
vector<int> res;
res.push_back(1);
for (int i = 0; i < cnt; i ++ )
for (int j = 0; j < sum[i]; j ++ )
res = mul(res, primes[i]);
for (int i = res.size() - 1; i >= 0; i -- ) printf("%d", res[i]);
puts("");
return 0;
}
如果是大数的组合数(1e18)一般选择卢卡斯定理。
而2、4算法时间复杂度相似,区别在于是否取余。
exgcd同余方程
#include<iostream>
using namespace std;
long long exgcd(long long a, long long b, long long &x, long long &y)
{
if (!b)
{
x = 1; y = 0;
return a;
}
long long d = exgcd(b, a % b, y, x);
y -= (a/b) * x;
return d;
}
int main()
{
long long a,b,x=0,y=0;
cin>>a>>b;
exgcd(a,b,x,y);
cout<<(x%b+b)%b<<endl;
return 0;
}
矩阵乘法
<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 502;
const ll mod = 1e9+7;
ll n, p, m;
ll a[N][N];
ll b[N][N];
ll c[N][N];
void xf()
{
for (ll i = 0; i < n; i++)
{
for (ll j = 0; j < m; j++)
{
for (ll k = 0; k < p; k++)
{
c[i][j] += a[i][k] * b[k][j] % mod;
c[i][j] %= mod;
}
}
}
}
int main()
{
cin >> n >> p >> m;
for (ll i = 0; i < n; i++)
for (ll j = 0; j < p; j++)
cin >> a[i][j];
for (ll i = 0; i < p; i++)
for (ll j = 0; j < m; j++)
cin >> b[i][j];
xf();
for (ll i = 0; i < n; i++)
{
for (ll j = 0; j < m; j++)
{
while (c[i][j] < 0)
c[i][j] += mod;
}
}
for (ll i = 0; i < n; i++)
{
for (ll j = 0; j < m; j++)
cout << c[i][j]<<' ';
cout << endl;
}
return 0;
}
Fibonacci第n项
#include <iostream>
using namespace std;
long long m;
void mul(int a[][2], int b[][2], int c[][2])
{
int temp[][2] = {{0, 0}, {0, 0}};
for (int i = 0; i < 2; i ++ )
for (int j = 0; j < 2; j ++ )
for (int k = 0; k < 2; k ++ )
{
long long x = temp[i][j] + (long long)a[i][k] * b[k][j];
temp[i][j] = x % m;
}
for (int i = 0; i < 2; i ++ )
for (int j = 0; j < 2; j ++ )
c[i][j] = temp[i][j];
}
int f_final(long long n)
{
int x[2] = {1, 1};
int res[][2] = {{1, 0}, {0, 1}};
int t[][2] = {{1, 1}, {1, 0}};
long long k = n - 1;
while (k)
{
if (k&1) mul(res, t, res);
mul(t, t, t);
k >>= 1;
}
int c[2] = {0, 0};
for (int i = 0; i < 2; i ++ )
for (int j = 0; j < 2; j ++ )
{
long long r = c[i] + (long long)x[j] * res[j][i];
c[i] = r % m;
}
return c[0];
}
int main()
{
long long n ;
cin >> n >> m;
cout << f_final(n-1) << endl;
return 0;
}
exgcd中国剩余定理-曹冲养猪
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
long long n,x,y;
long long ans,N;
long long a[15],b[15];
inline long long Exgcd(long long a,long long b,long long &x,long long &y){
if(b==0){
x=1;
y=0;
return a;
}
long long gcd=Exgcd(b,a%b,x,y);
long long t=x;
x=y;
y=t-a/b*y;
return gcd;
}
inline long long IntChina(long long k){
long long N=1;
for(register int i=1;i<=k;i++)
N*=a[i];
for(register int i=1;i<=k;i++){
long long m=N/a[i];
Exgcd(m,a[i],x,y);
ans=((ans+b[i]*m*x)%N+N)%N;
}
return ans;
}
int main(){
scanf("%lld",&n);
for(register int i=1;i<=n;i++)
scanf("%lld%lld",&a[i],&b[i]);
printf("%lld\n",IntChina(n));
return 0;
}
线段树区间维护带lazy——just a hook
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define maxn 100000
struct node
{
int w,f;
} tree[4*maxn+10];
void build(int k,int l,int r)//建树
{
tree[k].w=1;
tree[k].f=0;
if(l==r)
return ;
int m=(l+r)/2;
build(2*k,l,m);
build(2*k+1,m+1,r);
tree[k].w=tree[2*k].w+tree[2*k+1].w;
}
void down(int k,int l,int r)//这里的l,r还可以写在结构里面,那样可能更好理解
{
tree[2*k].f=tree[2*k+1].f=tree[k].f;
int m=(l+r)/2;
tree[2*k].w=tree[k].f*(m-l+1);
tree[2*k+1].w=tree[k].f*(r-m);
tree[k].f=0;
}
void add(int k,int l,int r,int x,int y,int w)//区间更改
{
if(l>=x&&r<=y)
{
tree[k].w=w*(r-l+1);
tree[k].f=w;
return ;
}
if(tree[k].f)
down(k,l,r);
int m=(l+r)/2;
if(x<=m)
add(2*k,l,m,x,y,w);
if(y>m)
add(2*k+1,m+1,r,x,y,w);
tree[k].w=tree[2*k].w+tree[2*k+1].w;
}
int main()
{
int t,n,q,ca=1;
scanf("%d",&t);
while(t--)
{
memset(tree,0,sizeof(tree));
scanf("%d%d",&n,&q);
int x,y,w;
build(1,1,n);
while(q--)
{
scanf("%d%d%d",&x,&y,&w);
add(1,1,n,x,y,w);
}
printf("Case %d: The total value of the hook is %d.\n",ca++,tree[1].w);
}
return 0;
}
学习心得
本周学习平稳,并没有什么特别的地方,不过打了一次团队赛,配合还不错,不过感觉因为缺少板子导致有些题没有做出来,比如那道曹冲养猪,是一道中国剩余定理的模板题,但是因为我们都没有学过中国剩余定理就没有做出来。但是如果有板子的话应该是可以做出来的,因为题上已经有明显暗示是使用这个方面的知识了,还有A题的一道线段树,如果有板子的话应该能找到错误然后过,期间罚时和耽误的时间比较多,主要还是比赛经验不足导致的。通过这次团队赛让我们队得到了很多经验。初步划分了我们队每个成员的主要负责内容。