10.20梦熊十三连测第十三场

T1:加减乘除

因为 s、ts、tst 均为正整数,所以不难发现一个 >=x>=x>=x 的数,经过变化后只会变的更大,<=x<=x<=x 的同理,只会变的更小。这说明原序列的单调性是不变的,就允许我们进行二分。
所以先把原序列升序排序,然后两次二分分别求出第一个大于等于 LLL 的位置和第一个小于等于 RRR 的位置,这道题就做完了。时间复杂度 O(q logV)O(q\space logV)O(q logV)

#include<bits/stdc++.h>
using namespace std;
#define rd read()
#define int long long
const int N=2e5+10;
int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
	return x*f;
}
int n,m,L,R,a[N];
struct node{int op,x,s,t;}q[N];
int cal(int x){
	int res=x;
	for(int i=1;i<=m;i++){
		if(q[i].op==1&&res>=q[i].x) res=q[i].t*(res+q[i].s);
		if(q[i].op==2&&res<=q[i].x) res=(res-q[i].s)/q[i].t;
	}
	return res;
}
signed main(){
	n=rd,m=rd,L=rd,R=rd;
	for(int i=1;i<=n;i++) a[i]=rd;
	for(int i=1;i<=m;i++) q[i].op=rd,q[i].x=rd,q[i].s=rd,q[i].t=rd;
	sort(a+1,a+1+n);
	int mx=0,mn=n+1,l=1,r=n;
	while(l<=r){
		int mid=(l+r)>>1;
		if(cal(a[mid])>=L) mn=mid,r=mid-1;
		else l=mid+1;
	}
	l=1,r=n;
	while(l<=r){
		int mid=(l+r)>>1;
		if(cal(a[mid])<=R) mx=mid,l=mid+1;
		else r=mid-1; 
	}
	if(mn>mx) cout<<0<<endl;
	else cout<<mx-mn+1<<endl;
	return 0;
}

T2:图书管理

暴力枚举区间去找中位数最好最好也只能 O(n2 logn)O(n^2\space logn)O(n2 logn),所以考虑换一种思路。

对于这样的题,一般考虑枚举中位数,计算它所产生的贡献。对于此题,设此时中位数为 pip_ipi,则对于 pj<pip_j<p_ipj<pi 的令 aj=−1a_j=-1aj=1,对 pj>pip_j>p_ipj>pi 的令 aj=1a_j=1aj=1。则我们的问题转化为多少区间 [l,r][l,r][l,r] 满足:l≤i≤rl\le i\le rlir,且 ∑j=lraj=0\sum_{j=l}^{r}a_j=0j=lraj=0。对此可以用桶来统计,先从 iii 开始往左扫,把此时扫到的和加在桶里,然后从右扫,查找当前和的负数的出现次数,然后计算贡献。

时间复杂度 O(n2)O(n^2)O(n2),常数确实很小。

#include<bits/stdc++.h>
using namespace std;
#define rd read()
#define ll long long
int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
	return x*f;
}
const int N=10010;
int n,a[N],p[N],cnt[N*2];ll ans;
int main(){
	n=rd;
	for(int i=1;i<=n;i++) p[i]=rd;
	for(int i=1;i<=n;i++){
		memset(cnt,0,sizeof(cnt));
		int s=n;
		for(int j=i;j>=1;j--){
			if(p[j]>p[i]) s--;
			else if(p[j]<p[i]) s++;
			cnt[s]+=j;
		}
		s=n;
		for(int j=i;j<=n;j++){
			if(p[j]>p[i]) s++;
			else if(p[j]<p[i]) s--;
			ans+=1ll*cnt[s]*p[i]*j;
		}
	}
	cout<<ans<<endl;
	return 0;
}

T3:两棵树

首先要知道一个常用的转化:连通块数 === 剩余点数 −- 剩余边数。

接下来原式可拆为 点×\times×−-×\times×−-×\times×+++×\times×边。

考虑它们如何计算:

①:点×\times×点,假设 TTT 中一个点 xxx 被保留,概率为 12\frac{1}{2}21,另一棵树中一个点 yyy,若 x=yx=yx=y 则概率为 000,否则概率为 14\frac{1}{4}41,所以它的期望就为 n×(n−1)4\frac{n\times (n-1)}{4}4n×(n1)

②:边×\times×点 或 点×\times×边,这两个可以一起算。设一棵树中保留边 (x,y)(x,y)(x,y),概率应为 14\frac{1}{4}41,那么另一棵树中一点 uuu,若其与 (x,y)(x,y)(x,y) 均不相同,则概率为 18\frac{1}{8}81,否则为 000。所以它的期望为 (n−1)(n−2)8\frac{(n-1)(n-2)}{8}8(n1)(n2)

③:边 ×\times× 边,考虑一棵树中一边 (x,y)(x,y)(x,y),另一棵树中边 (u,v)(u,v)(u,v),若这四点均不相同,则概率为 116\frac{1}{16}161,否则为 000。对此如何计算,我们可以枚举一棵树中的边 (x,y)(x,y)(x,y),另一棵树中没有端点 xxxyyy 的边就是 n−1−degx−degyn-1-deg_x-deg_yn1degxdegy,注意特判若两棵树都有 (x,y)(x,y)(x,y) 则要加回 111,最后就算完了。

#include<bits/stdc++.h>
using namespace std;
#define rd read()
#define ll long long
#define PII pair<int,int>
const int N=2e5+10,mod=998244353;
int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
	return x*f;
}
int n,deg[N];map<PII,int> mp;ll ans;
struct node{int from,to;}e1[N*2],e2[N*2];
int qpow(int a,int b){
	int res=1;
	while(b){if(b&1)res=1ll*res*a%mod;a=1ll*a*a%mod,b>>=1;}
	return res%mod;
}
int main(){
	n=rd;
	for(int i=1;i<n;i++){
		int x=rd,y=rd;
		mp[make_pair(x,y)]=mp[make_pair(y,x)]=1;
		e1[i]={x,y};
	}
	(ans+=1ll*n*(n-1)%mod*qpow(4,mod-2)%mod)%=mod;
	ans=(ans-2ll*(n-1)*(n-2)%mod*1ll*qpow(8,mod-2)%mod+mod)%mod;
	for(int i=1;i<n;i++){
		int x=rd,y=rd;deg[x]++,deg[y]++;
		mp[make_pair(x,y)]++,mp[make_pair(y,x)]++;
		e2[i]={x,y};
	}
	for(int i=1;i<n;i++){
		int x=e1[i].from,y=e1[i].to;
		int tot=n-1-deg[x]-deg[y];
		if(mp[make_pair(x,y)]==2) tot++;
		ans=(ans+1ll*tot*qpow(16,mod-2)%mod)%mod;
	}
	printf("%lld\n",ans);
	return 0;
}

T4:电报

这个题还是很难想的,许多贪心做法是错的。

但是这道题可以借鉴哈夫曼树,我们给边赋上权值 1、21、212,然后从根到某个叶子节点的路径代表一个字符,路径权值和代表其代价,显然出现频次高的字符深度一定浅。

考虑由浅入深,用 dp 构造整颗二叉树。设 fi,a,b,lf_{i,a,b,l}fi,a,b,l 表示当前深度为 iii,深度为 i−1i-1i1 的节点留了 aaa 个,深度为 iii 的节点有 bbb 个,深度不超过 i−2i-2i2 的有 lll 个,枚举有 kkk 个节点作为叶子留在 i−1i-1i1 层,这样做的时间复杂度是 O(n5)O(n^5)O(n5) 的,需要优化。

发现转移时可以不用枚举深度,更深的节点所产生的贡献会加到深度浅的,贡献可以拆解到每一层,这样优化为 O(n4)O(n^4)O(n4),可以有 70 pts70\space pts70 pts,还是不够。

又发现可以不用枚举 kkk,一个 i−1i-1i1 层的节点,要么留下来做叶子,要么向下继续扩展,所以可以对其拆分进行转移。

#include<bits/stdc++.h>
using namespace std;
#define rd read()
int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return x*f;
}
const int N=755;
int n,a[N],f[N][N];
bool cmp(int a,int b){return a>b;}
int main(){
	n=rd;
	for(int i=0;i<n;i++) a[i]=rd;
	sort(a,a+n,cmp);//频次高的排前面
	for(int i=n-1;i>=0;i--) a[i]+=a[i+1];
	memset(f,0x3f,sizeof(f));
	f[1][0]=0;
	for(int i=0;i<n;i++){
		for(int j=0;j<=n-i;j++)
			for(int k=0;j+k<=n-i;k++) f[j+k][j]=min(f[j+k][j],f[j][k]+a[i]);//枚举留下来做叶子节点的
		for(int j=0;j<n-i;j++)
			for(int k=0;k<n-i;k++) f[j][k]=f[j+1][k];//枚举向下扩展的
	}
	printf("%d\n",f[0][0]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值