2021-07-31

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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值