1.欧拉函数:φ(n)为小于n的正整数中与n互质的个数①若q为质数,φ(q)= q-1,②若gcd(a,b)=1,则φ(ab)=φ(a)φ(b),积性函数③
2.康托展开,逆康托展开,针对前一个排列的算法
3.map使用,使用方法,用平衡树构建了一个字典,支持删除:mp.earse(mp.begin(),mp.end());
改变元素,增加元素等功能,某些题目可以偷懒用
4.换教室:综合性比较强的题目,考到了dp,期望,Floyd,状态设计的难度还可以,与关路灯有点像。找了半天的玄学错误,
#include<bits/stdc++.h>
using namespace std;
const int N=2e3+5;
const double inf=1e17+5;
int n,m,v,e,c[N],d[N],mp[N][N],x,y,z,dis[N][N];
double f[N][N][3],k[N];//状态设计与关路灯有点相似
int main()
{
cin>>n>>m>>v>>e;
for(int i=1;i<=n;i++)cin>>c[i];
for(int i=1;i<=n;i++)cin>>d[i];
for(int i=1;i<=n;i++)cin>>k[i];
memset(mp,0x3f,sizeof mp);
for(int i=1;i<=e;i++)
{
cin>>x>>y>>z;
mp[x][y]=mp[y][x]=min(mp[x][y],z);
}
for(int kk=1;kk<=v;kk++)//中转点
for(int i=1;i<=v;i++)
for(int j=1;j<=v;j++)
mp[i][j]=min(mp[i][j],mp[i][kk]+mp[kk][j]);
for(int i=1;i<=v;i++)mp[i][i]=mp[i][0]=mp[0][i]=0;
//dp
/* for (register int i = 0; i <= n; i++)
for (register int j = 0; j <= m; j++)
f[i][j][0] = f[i][j][1] = inf;*/
memset(f,0x7f,sizeof f);
//这里写0x3f会有问题,输出0,若写0xff,会输出过大。。
f[1][0][0]=0;f[1][1][1]=0;
for(int i=2;i<=n;i++)//第i节课
{
f[i][0][0]=f[i-1][0][0]+mp[c[i-1]][c[i]];
int c1=c[i-1],d1=d[i-1],c2=c[i],d2=d[i];
for(int j=1;j<=min(m,i);j++)//申请了j节课
{
f[i][j][0]=min(f[i][j][0],min(f[i-1][j][1]+(1-k[i-1])*mp[c1][c2]+k[i-1]*mp[d1][c2],f[i-1][j][0]+mp[c1][c2]));
f[i][j][1]=min(f[i][j][1],min(f[i-1][j-1][1] + (1-k[i-1])*(1-k[i])*mp[c1][c2] + (1-k[i-1])*k[i]*mp[c1][d2] + k[i-1]*(1-k[i])*mp[d1][c2] +k[i]*k[i-1]*mp[d1][d2],f[i-1][j-1][0]+ (1-k[i])*mp[c1][c2] + k[i]*mp[c1][d2]));
}
}
double ans=inf;
for(int i=0;i<=m;i++)ans=min(ans,min(f[n][i][1],f[n][i][0]));
printf("%.2lf",ans);
return 0;
}
5.HH的项链:板子题,树状数组求区间种类,核心在于记录最近的某一个数值的位置,可以看作是贪心的思想。还有一些细节,树状数组2倍空间,快读优化,在线转离线
板子
//板子题 ,树状数组求区间种类
//O(N*logN)
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e6+10;
struct qj{
int l,r,pos;
}q[N];
int read(){
char ch;int f=1,x=0;
ch=getchar();
while(ch<'0'||ch>'9'&&ch!='-')ch=getchar();
if(ch=='-')f=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=10*x+ch-'0',ch=getchar();
return f*x;
}
int tree[N<<1],ans[N],a[N],vis[N],n,m;
bool cmp(qj x,qj y)
{
return x.r<y.r;//按照右端点递增
}
int lowbit(int x)
{
return x&(-x);
}
void add(int pos,int x)
{
while(pos<=n)
{
tree[pos]+=x;
pos+=lowbit(pos);
}
}
int sum(int pos)
{
int tmp=0;
while(pos>=1)
{
tmp+=tree[pos];
pos-=lowbit(pos);
}
return tmp;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)a[i]=read();
cin>>m;
for(int i=1;i<=m;i++)
{
q[i].l=read();q[i].r=read();
q[i].pos=i;
}
sort(q+1,q+m+1,cmp);
int nxt=1;//起始位置
for(int i=1;i<=m;i++)//m次询问
{
for(int j=nxt;j<=q[i].r;j++)//需要遍历的区间
{
if(vis[a[j]])
{
add(vis[a[j]],-1);//更新最近的节点
}
add(j,1);
vis[a[j]]=j;//记录最近的节点位置
}
nxt=q[i].r+1;
ans[q[i].pos]=sum(q[i].r)-sum(q[i].l-1);
}
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}
6.小白逛公园:还是板子题,求区间内最长连续字段和,对每一区间,需要记录maxl,maxr,mx,sum才能够计算。
//线段树用结构体写
//板子题,求区间最长字段和
#include<iostream>
#include<cstdio>
using namespace std;
const int N=5e5+10;
struct node{
int mx,maxl,maxr,sum;
}tree[N<<2];
int n,m,a[N];
void build(int p,int l,int r)
{
if(l==r)
{
tree[p].mx=tree[p].maxl=tree[p].maxr=tree[p].sum=a[l];
return;
}
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);//或运算和加法不要混合使用,写p<<1+1就wa了,找了好久的bug
tree[p].sum=tree[p<<1].sum+tree[p<<1|1].sum;
tree[p].maxl=max(tree[p<<1].maxl,tree[p<<1].sum+tree[p<<1|1].maxl);
tree[p].maxr=max(tree[p<<1|1].maxr,tree[p<<1].maxr+tree[p<<1|1].sum);
tree[p].mx=max(tree[p<<1|1].maxl+tree[p<<1].maxr,max(tree[p<<1|1].mx,tree[p<<1].mx));
}
void add(int p,int l,int r,int pos,int x)
{
if(pos<l||pos>r)return;
if(l==r&&pos==l)
{
tree[p].sum=x;
tree[p].maxl=x;
tree[p].maxr=x;
tree[p].mx=x;
return;
}
int mid=l+r>>1;
add(p<<1,l,mid,pos,x);
add(p<<1|1,mid+1,r,pos,x);
//更新操作和建树差不多
tree[p].sum=tree[p<<1].sum+tree[p<<1|1].sum;
tree[p].maxl=max(tree[p<<1].maxl,tree[p<<1].sum+tree[p<<1|1].maxl);
tree[p].maxr=max(tree[p<<1|1].maxr,tree[p<<1].maxr+tree[p<<1|1].sum);
tree[p].mx=max(tree[p<<1|1].maxl+tree[p<<1].maxr,max(tree[p<<1|1].mx,tree[p<<1].mx));
}
node query(int p,int l,int r,int x,int y)//新建一个区间计算
{
if(x<=l&&r<=y)//x l mid r y
{
return tree[p];
}
int mid=l+r>>1;
if(mid+1<=x)return query(p<<1|1,mid+1,r,x,y);
else if(y<=mid)return query(p<<1,l,mid,x,y);
else{//l x mid y r
node res,x1,x2;//x1,x2为两个子区间
x1=query(p<<1,l,mid,x,y);//左区间
x2=query(p<<1|1,mid+1,r,x,y);
res.sum=x1.sum+x2.sum;
res.maxl=max(x1.maxl,x1.sum+x2.maxl);
res.mx=max(max(x1.mx,x2.mx),x1.maxr+x2.maxl);
res.maxr=max(x1.maxr+x2.sum,x2.maxr);
return res;
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
build(1,1,n);
for(int i=1;i<=m;i++)
{
int x,y,z;
cin>>x>>y>>z;
if(x==1)
{
if(y>z)swap(y,z);
printf("%d\n",query(1,1,n,y,z).mx);
}
else
{
add(1,1,n,y,z);
}
}
return 0;
}
7.数的计算:线段树做,以时间为轴,将计算从n变为logn(只改变了线段树中的一条链)。坑点:tree数组要开long long,两个int相乘会爆int
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int N=1e5+10;
ll tree[N<<2],a[N];
void build(int p,int l,int r)
{
tree[p]=1;
if(l==r)return;
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
}
void change(int p,int l,int r,int pos,int x,int mod)
{
tree[p]=tree[p]*x%mod;
if(l==r)
{
// cout<<p<<'!';
// cout<<tree[p]<<'!';
return;
}
int mid=l+r>>1;
if(pos<=mid)change(p<<1,l,mid,pos,x,mod);
else change(p<<1|1,mid+1,r,pos,x,mod);
}
void change2(int p,int l,int r,int pos,int mod)
{
if(l==r){
// cout<<tree[p]<<'?';
tree[p]=1;
// cout<<p<<'!';
return;
}
int mid=l+r>>1;
if(pos<=mid)change2(p<<1,l,mid,pos,mod);
else change2(p<<1|1,mid+1,r,pos,mod);
tree[p]=tree[p<<1]*tree[p<<1|1]%mod;
}
int main()
{
int t,q,M,op,m;
cin>>t;
while(t--)
{
cin>>q>>M;
build(1,1,q);
// for(int i=1;i<=q*2;i++)cout<<tree[i]<<' ';
// cout<<endl;
int now=0;
for(int i=1;i<=q;i++)
{
cin>>op>>m;
if(op==1)
{
++now;
change(1,1,q,now,m,M);
cout<<tree[1]<<endl;
a[i]=now;
}
else{
change2(1,1,q,a[m],M);
cout<<tree[1]<<endl;
}
}
memset(tree,0,sizeof tree);
memset(a,0,sizeof a);
}
return 0;
}
8.严格次小生成树:其实拆开看,没这么难,都是做过的东西,最小生成树和倍增优化,lca。
次小问题其实大都可以这样做,记录第一个max和第二个max,然后就可以推了。这道题枚举每条边,对每一条边,找出最小增量,即找到右逼近的边(大中取最小)
#include<cstdio>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int N=4e5+10,M=9e5+10;
const ll INF=(1<<61);
struct node2{
int u,v,w;
}e2[M<<1];
struct node{
int to,w,nxt;
}e[N*2];
ll tot=0,cnt=0,head[N],vis[2*M];
ll fa[N],dep[N],mx[N][20],mn[N][20],bz[N][20],n,m;
void add(int u,int v,int w)
{
e[++tot].nxt =head[u];
e[tot].to=v;
e[tot].w=w;
head[u]=tot;
}
bool cmp(node2 x,node2 y)
{
return x.w<y.w;
}
ll find(int x)
{
if(fa[x]!=x)return fa[x]=find(fa[x]);
return fa[x];
}
void dfs(int u,int fa)
{
bz[u][0]=fa;
for(int i=head[u];i;i=e[i].nxt)
{
int to=e[i].to;
if(to==fa)continue;
dep[to]=dep[u]+1;
mx[to][0]=e[i].w;
mn[to][0]=-INF;
dfs(to,u);
}
}
void cal()
{
for(int i=1;i<=18;i++)
for(int j=1;j<=n;j++)
{
bz[j][i]=bz[bz[j][i-1]][i-1];
mx[j][i]=max(mx[j][i-1],mx[bz[j][i-1]][i-1]);
mn[j][i]=max(mn[j][i-1],mn[bz[j][i-1]][i-1]);
if(mx[j][i]>mx[bz[j][i-1]][i-1])mn[j][i]=max(mn[j][i],mx[bz[j][i-1]][i-1]);
else if(mx[j][i]<mx[bz[j][i-1]][i-1])mn[j][i]=max(mn[j][i],mx[j][i-1]);
}
}
ll LCA(int x,int y)
{
if(dep[x]<dep[y])swap(x,y);
for(int i=18;i>=0;i--)
if(dep[bz[x][i]]>=dep[y])
{
x=bz[x][i];
}
if(x==y)return x;
for(int i=18;i>=0;i--)
{
if(bz[x][i]!=bz[y][i])
{
x=bz[x][i];
y=bz[y][i];
}
}
return bz[x][0];
}
ll qmax(int u,int v,int d)//从u到v的路径上右逼近的边,不能等于
{
ll ans=-INF;
for(int i=18;i>=0;i--)//。。这里的i不能超过计算cal(),(cal里i是18)的最大范围
{
if(dep[bz[u][i]]>=dep[v])
{
if(d!=mx[u][i])ans=max(ans,mx[u][i]);//边一定不小于d
else ans=max(ans,mn[u][i]);
u=bz[u][i];
}
}
return ans;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>e2[i].u>>e2[i].v>>e2[i].w;
}
sort(e2+1,e2+m+1,cmp);
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++)//选n-1条边
{
int r1=find(e2[i].u),r2=find(e2[i].v);
if(r1!=r2)
{
fa[r1]=r2;
add(e2[i].u,e2[i].v,e2[i].w);
add(e2[i].v,e2[i].u,e2[i].w);
cnt+=e2[i].w;
vis[i]=1;
}
}
//已经找到最小生成树,要求次小生成树,即需枚举未被选的边,并删掉最比它小的最大的边(且不能等于)
//朴素即两个for枚举,其实可以倍增+lca优化 ,为nlogn
mn[1][0]=-INF;
dep[1]=1;
dfs(1,0);//倍增预处理
cal();
ll ans=INF;
for(int i=1;i<=m;i++)
{
if(!vis[i])
{
ll u=e2[i].u;
ll v=e2[i].v;
ll d=e2[i].w;
ll lca=LCA(u,v);
ll x1=qmax(u,lca,d);
ll x2=qmax(v,lca,d);
ans=min(ans,cnt+d-max(x1,x2));
}
}
cout<<ans;
return 0;
}