2020.7.15集训

plan

description

在小X的家乡,有机房一条街,街上有很多机房。每个机房里都有一万个人在切题。小 X 刚刷完
CodeChef \text{CodeChef} CodeChef,准备出来逛逛。
机房一条街有 n n n个机房,第 i i i 个机房的坐标为 x i x_i xi ,小 X 的家坐标为 0 0 0。小 X 在街上移动的速度为 1 1 1,即从 x 1 x_1 x1 x 2 x_2 x2 所耗费的时间为 ∣ x 1 − x 2 ∣ |x_1−x_2| x1x2
每个机房的学生数量不同,ACM 题目水平也良莠不齐。小 X 到达第 i i i 个机房后,可以花 t i t_i ti 的时间
想题,然后瞬间 AK;当然,也可以过机房而不入。
小 X 现在只有 m m m 个单位时间,之后他就该赶着去打 Codeforces \text{Codeforces} Codeforces 了。现在他想知道自己最多能在多少个机房 AK,希望你帮帮他。

solution
发现我们一定不能往回走,所以可以按照 x x x坐标排个序,枚举我们最右边走到哪了,那么问题就转化成了,对于每个 i i i,前 i i i个点的 t t t最大的 k k k个要 ≤ m − x i \leq m-x_i mxi,求最大的 k k k

那么这个东西我们可以从左往右扫描,利用权值线段树维护,每次在这个权值线段树上二分就可以了

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

gjm写了个 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)的二分+堆也过了

离散化去重那里我老写不对干脆就没去重导致代码特别难看

#include <bits/stdc++.h>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)

typedef long long ll;

const int N=1e5+5;

template<typename T> void read(T &x){
   x=0;int f=1;
   char c=getchar();
   for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
   for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

# define int long long

int n,m;
int match[N];

struct node{
	int x,t;
	bool operator < (const node &cmp)const{
		return x<cmp.x;
	}
}a[N];

struct lsh{
	int val,id;
	bool operator < (const lsh &cmp)const{
		return val<cmp.val;
	}
}b[N];

int ans;

struct sakura{
	int l,r;
	int val,sum;	
}seg[N<<2];

# define lc (u<<1)
# define rc (u<<1|1)

void pushup(int u){
	seg[u].val=seg[lc].val+seg[rc].val;
	seg[u].sum=seg[lc].sum+seg[rc].sum;	
}

void build(int u,int l,int r){
	seg[u].l=l,seg[u].r=r;
	if(l==r)return;
	int mid=l+r>>1;
	build(lc,l,mid);
	build(rc,mid+1,r);	
}

void update(int u,int x,int k){
	if(seg[u].l==seg[u].r){
		seg[u].val++;
		seg[u].sum+=k;
		return;
	}
	int mid=seg[u].l+seg[u].r>>1;
	if(x<=mid)update(lc,x,k);
	else update(rc,x,k);
	pushup(u);
}

int query(int u,int k){
	if(seg[u].l==seg[u].r)return seg[u].val;	
	if(k<=seg[lc].sum)return query(lc,k);
	return seg[lc].val+query(rc,k-seg[lc].sum);
}

signed main()
{
	freopen("plan.in","r",stdin);
	freopen("plan.out","w",stdout);
	read(n),read(m);
	Rep(i,1,n)read(a[i].x),read(a[i].t),b[i]=(lsh){a[i].t,i};
	sort(a+1,a+n+1);
	sort(b+1,b+n+1);
	Rep(i,1,n)a[b[i].id].t=i,match[i]=b[i].val;
	build(1,1,n);
	Rep(i,1,n){
		update(1,a[i].t,match[a[i].t]);
		if(m>a[i].x)ans=max(ans,query(1,m-a[i].x));	
	}
	printf("%lld\n",ans);
	return 0;
}
heap

description
给定 n n n,问有多少个由 n n n的排列构成的二叉堆
注意包括小根堆和大根堆

solution
n = 1 n=1 n=1特判
n > 1 n>1 n>1只计算小根堆个数然后 × 2 \times 2 ×2就可以了

f ( i ) f(i) f(i)表示 i i i个节点的情况数,我们可以分别算出左右子树的节点个数 l i , r i l_i,r_i li,ri
那么有

f ( i ) = ( i − 1 l i ) f ( l i ) f ( r i ) f(i)=\binom{i-1}{l_i}f(l_i)f(r_i) f(i)=(lii1)f(li)f(ri)

然后简单递推就可以

注意不要递归会爆炸

#include <bits/stdc++.h>
using namespace std;

# pragma GCC optimize(3)

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)

typedef long long ll;

const int N=5e6+5;
const int mod=1e9+7;

template<typename T> void read(T &x){
   x=0;int f=1;
   char c=getchar();
   for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
   for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

int n;
int a[N];
int fac[N],inv[N];
int f[N],siz[N];
int ans;

int Qpow(int base,int ind){
	int res=1;
	while(ind){
		if(ind&1)res=1ll*res*base%mod;
		base=1ll*base*base%mod;
		ind>>=1;
	}
	return res;
}

# define lc (u<<1)
# define rc (u<<1|1)

int C(int n,int m){
	return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;	
}

int main()
{
	freopen("heap.in","r",stdin);
	freopen("heap.out","w",stdout);
	read(n);
	if(n==1)return puts("1"),0;
	fac[0]=inv[0]=1;
	Rep(i,1,n)fac[i]=1ll*fac[i-1]*i%mod;
	Rep(i,1,n)inv[i]=Qpow(fac[i],mod-2);
	_Rep(u,n,1){
		siz[u]=1;
		if(lc<=n)siz[u]+=siz[lc];
		if(rc<=n)siz[u]+=siz[rc];
	}
	f[0]=f[1]=1;
	_Rep(u,n,1)
		if(siz[u]>1)
			f[siz[u]]=1ll*C(siz[u]-1,siz[lc])*f[siz[lc]]%mod*f[siz[rc]]%mod;
	printf("%lld\n",1ll*f[n]*2%mod);
	return 0;
}
road

description

因为一场不小的地震,Y 省 n n n 个城市之间的道路都损坏掉了,省长希望小 X 将城市之间的道路重
修一遍。
很多城市之间的地基都被地震破坏导致不能修路了,因此可供修建的道路只有 m m m 条。因为施工队伍
有限,省长要求用尽量少的道路将所有的城市连通起来,这样施工量就可以尽量少。不过,省长为了表
示自己的公正无私,要求在满足上述条件的情况下,选择一种方案,使得该方案中最贵道路的价格和最
便宜道路的价格的差值尽量小,即使这样的方案会使总价提升很多也没关系。
小 X 现在手忙脚乱,希望你帮帮他。

solution

首先根据长度排序,然后我们想到双指针,可以用lct维护连通性,复杂度 O ( m log ⁡ m + m log ⁡ n ) O(m\log m+m\log n) O(mlogm+mlogn)

但是同样我们也可以用dfs来解决,每次找这条路径上最短的那条删掉

但是注意删掉的边要从邻接表里面跳过去,否则复杂度不对

O ( m log ⁡ m + n m ) O(m\log m+nm) O(mlogm+nm)

#include <bits/stdc++.h>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)

typedef long long ll;

const int N=2005;
const int M=2e4+5;

template<typename T> void read(T &x){
   x=0;int f=1;
   char c=getchar();
   for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
   for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

int n,m;
int head[N],cnt=1;
int ans=INT_MAX;
bool vis[N],able[M<<1];

struct sakura{
	int x,y,c;
	bool operator < (const sakura &cmp)const{
		return c<cmp.c;
	}
}e[M<<1];

struct Edge{
	int to,next,w;
}E[M<<1];

void add(int x,int y,int c){
	E[++cnt]=(Edge){y,head[x],c},head[x]=cnt;
	able[cnt]=true;
}

int dfs(int u,int tar,int pre){
	for(int i=head[u],last=0;~i;i=E[i].next){
		int v=E[i].to;
		if(!able[i]){
			if(i==head[u])head[u]=E[i].next;
			else E[last].next=E[i].next;
		}
		else{
			if(v!=pre){
				if(v==tar)return i;
				int x=dfs(v,tar,u);
				if(~x){
					if(E[i].w<E[x].w)return i;
					else return x;
				}
			}
			last=i;
		}
	}
	return -1;
}

int main()
{
	freopen("road.in","r",stdin);
	freopen("road.out","w",stdout);
	memset(head,-1,sizeof(head));
	read(n),read(m);
	Rep(i,1,m)read(e[i].x),read(e[i].y),read(e[i].c);
	sort(e+1,e+m+1);
	for(int i=1,tot=0,l=1;i<=m;i++){
		int nephren=dfs(e[i].x,e[i].y,-1);
		if(~nephren){
			tot--;
			able[nephren]=able[nephren^1]=false;
			while(l<=m&&!able[l*2])l++;
		}
		add(e[i].x,e[i].y,e[i].c);
		add(e[i].y,e[i].x,e[i].c);
		if(++tot==n-1)ans=min(ans,e[i].c-e[l].c);
	}
	printf("%d\n",ans>2e9?-1:ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值