3.22
第一题 区间和(离散化+去重操作+二分查找)
int unique()
{
int j=0;
for(int i=0;i<k;i++)
{
if(!i||alls[i]!=alls[i-1])
alls[j++]=alls[i];
}
return j;
}
//去重 返回下标
第二题 差分(理解实现差分功能的函数的意义)
void insert(int l,int r,int c)//实现当对b[n]求和的时候,求和的结果a[n]在区间段[l,r]
//上加c
{
b[l]+=c;
b[r+1]-=c;
}
3.23
第一题 bfs走迷宫
int bfs(int sx,int sy,int ex,int ey)
{
queue<pair<int,int>>q;
q.push({ sx,sy });
memset(d, -1, sizeof d);
d[sx][sy] = 0;
while(!q.empty())
{
auto p = q.front(); q.pop();
for (int i = 0; i < 4; i++)
{
int x = p.first + dx[i];
int y = p.second + dy[i];
if (x >= 1 && x <= n && y >= 1 && y <= m && d[x][y] == -1&&g[x][y]=='1')
{
q.push({ x,y });
d[x][y] = d[p.first][p.second] + 1;
if (x == ex && y == ey)return d[x][y];
}
}
}
return d[ex][ey];
}
第二题 并查集
#include<iostream>
using namespace std;
const int N=1e5+10;
int f[N];
int find(int x)//寻找x的祖先并且路径压缩
{
if(x!=f[x])f[x]=find(f[x]);
return f[x];
}
int main()
{
int n,m;cin>>n>>m;
for(int i=1;i<=n;i++)
f[i]=i;
while(m--)
{
char op;cin>>op;
int a,b;
if(op=='M')
{
cin>>a>>b;
f[find(a)]=find(b);
}
else
{
cin>>a>>b;
if(find(a)==find(b))
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
}
}
return 0;
}
3.27(2h)
首先复习了bfs走迷宫,并查集网络分析
然后复习dp,01背包问题,数字三角形,最长上升子序列
然后做了一道数学模拟题 两个闹钟
3.28
单调栈
表达式求值
八数码(注意到unordered_map比map要快)
int bfs(string start)
{
queue<string>q;
unordered_map<string,int>d;
q.push(start);
d[start]=0;
string end="12345678x";
while(q.size())
{
auto t=q.front();
q.pop();
int distance=d[t];
if(t==end)return distance;
int k=t.find('x');
int x=k/3,y=k%3;
for(int i=0;i<4;i++)
{
int a=x+dx[i],b=y+dy[i];
if(a>=0&&a<3&&b>=0&&b<3)
{
swap(t[3*a+b],t[k]);
if(!d.count(t))
{
d[t]=distance+1;
q.push(t);
}
swap(t[a*3+b],t[k]);
}
}
}
return -1;
}
皇后问题(注意到对于对角线和反对角线的映射)
void dfs(int u)
{
if(u==n)
{
for(int i=0;i<n;i++)
cout<<g[i]<<endl;
cout<<endl;
return;
}
else
{
for(int i=0;i<n;i++)
{
if(!col[i]&&!dg[u+i]&&!udg[n+u-i])
{
g[u][i]='Q';
col[i]=dg[u+i]=udg[n-u+i]=1;
dfs(u+1);
col[i]=dg[u+i]=udg[n-u+i]=0;//恢复现场
g[u][i]='.';
}
}
}
}
最长公共子序列
学会对复杂问题进行状态转移的思考
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(a[i]==b[j])f[i][j]=f[i-1][j-1]+1;
else
{
f[i][j]=max(f[i-1][j],f[i][j-1]);
}
}
筛质数
int main()
{
cin>>n;
int cnt=0;
for(int i=2;i<=n;i++)
{
if(!st[i])primes[cnt++]=i;
for(int j=0;primes[j]*i<=n&&primes[j];j++)
{
st[primes[j]*i]=1;
if(i%primes[j]==0)break;
//如果此时不停止,那么后面被筛的数字的最小质因数就是primes[j]了,这意味着它会被重复晒
}
}
cout<<cnt<<endl;
}
质因数分解定理
oid divide(int n)
{
for(int i=2;i*i<=n;i++)
//只需要枚举到小于sqrt(n)的质因子
//因为大于sqrt(n)的质因子必然只有一个
if(n%i==0)
{
int s=0;
while(n%i==0)
{
n/=i;
s++;
}
cout<<i<<' '<<s<<endl;
}
if(n>1)cout<<n<<' '<<1<<endl;
//如果大于sqrt(n)的质因子有一个的话,输出它
}
欧几里得算法
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
约数数量
#include<iostream>
#include<unordered_map>
const int mod=1e9+7;
using namespace std;
unordered_map<int,int>primes;
int main()
{
int n;cin>>n;
while(n--)
{
int x;cin>>x;
for(int i=2;i*i<=x;i++)
{
if(x%i==0)
{
while(x%i==0)
{
primes[i]++;
x/=i;
}
}
}
if(x>1)primes[x]++;
}
long long ans=1;
for(auto it=primes.begin();it!=primes.end();it++)
{
ans=ans*(1+it->second)%mod;
}
cout<<ans%mod<<endl;
}
约数之和
对于等比数列求和
假设指数为t,底数为x
则sum的求法为
while(t--)
{
tmp=tmp*x+1;
}
for(auto it=primes.begin();it !=primes.end();it++)
{
long long tmp=1;
int t=it->second;
while(t--)
{
tmp=(tmp*it->first+1)%mod;
}
sum=sum*tmp%mod;
//虽然能保证tmp不大于mod,但如果sum*tmp很有可能大于mod
}
欧拉函数
记住公式
#include<iostream>
using namespace std;
int n;
int main()
{
cin>>n;
while(n--)
{
int x;
cin>>x;
int res=x;
for(int i=2;i<=x/i;i++)
{
if(x%i==0)
{
res=res/i*(i-1);
while(x%i==0)x/=i;
}
}
if(x>1)res=res/x*(x-1);
cout<<res<<endl;
}
}
线性筛求欧拉函数
ll get_phi()
{
int cnt=0;
phi[1]=1;
for(int i=2;i<=n;i++)
{
if(!st[i])
{
primes[cnt++]=i;
phi[i]=i-1;
}
for(int j=0;primes[j]*i<=n&&primes[j];j++)
{
st[primes[j]*i]=1;
if(i%primes[j]==0)
{
phi[primes[j]*i]=phi[i]*primes[j];
break;
}
phi[primes[j]*i]=phi[i]*(primes[j]-1);
}
}
ll sum=0;
for(int i=1;i<=n;i++)
sum+=phi[i];
return sum;
}
容斥原理
求所有质数选法的并集
即
n个圈相并=1个圈-2个圈+3个圈-4个圈+5个圈……
奇数圈加,偶数圈减
for(int i=1;i<1<<m;i++)
//这里表示一共有1-2^m-1种选法,对应其二进制数
{
ll cnt=0,t=1;
for(int j=0;j<m;j++)
{//对于每一个质数的选取方案通过j来访问遍历
if(i>>j&1)
{
cnt++;
if(t*p[j]>n)
{
t=-1;
break;
}
//如果当前方案的最小公倍数已经超过范围了,则跳过此方案
t*=p[j];
}
}
if(t!=-1)
{
if(cnt&1)
res+=n/t;
//寻找在1-n中有多少t的公倍数
else
res-=n/t;
}
}
二维前缀和
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
//求二维前缀和的公式
s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1];
//求区间(x1,y1)到(x2,y2)的区间和公式
二维差分
void insert(int x1,int y1,int x2,int y2,int c)
{
b[x1][y1]+=c;
b[x2+1][y1]-=c;
b[x1][y2+1]-=c;
b[x2+1][y2+1]+=c;
}
区间维护
int merge()
{
vector<pii>res;
int st=-1e9,ed=-1e9;
for(auto &seg:segs)
{
if(st==-1e9)
{
st=seg.first,ed=seg.second;
}//放入第一个区间
else
{
if(seg.first>ed)//维护新区间
{
res.push_back({st,ed});
st=seg.first,ed=seg.second;
}
else
ed=max(ed,seg.second);
}
}
res.push_back({st,ed});
return res.size();
}
双指针
int main()
{
int n;cin>>n;int j=1,res=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
s[a[i]]++;
while(s[a[i]]!=1)
{
s[a[j]]--;
j++;
}
res=max(i-j+1,res);
}
cout<<res<<endl;
}
数组目标和
int main()
{
int n,m,x;cin>>n>>m>>x;
for(int i=0;i<n;i++)
cin>>a[i];
for(int j=0;j<m;j++)
cin>>b[j];
for(int i=0,j=m-1;i<n;i++)
{
while(a[i]+b[j]>x&&j>=0)
{
j--;
}
if(a[i]+b[j]==x)
cout<<i<<' '<<j<<endl;
}
return 0;
}