ABC312 格点思想 贪心+二分 树上路径计数

E

格点思想

空间中多个互不重合的长方体,端点都在[0,100][0,100][0,100],两个长方体有一个面公共面积不为0即为接触,问每个长方体和多少个长方体接触?

n=2e5n=2e5n=2e5个长方体,直接做会爆炸。但是这种整点上,并且值域不大的计算几何,可以考虑直接枚举每个格点。(其实格点思想就类似于一般问题里的考虑转成值域上思考)

这里就是我们注意到在这个范围内,只有3∗10033*100^3310031∗11*111的格点,并且由于长方体互不重合,每个格点最多又两个长方体,即两侧各一个。故我们对于每个格点,记录上面有哪些长方体,然后枚举每个格点,把两侧的长方体都放进对方的接触集合内(对每个长方体用一个set记录和它接触的长方体编号),对于两个长方体在不止一个格点接触的情况,set可以去重,不用管。最后set的大小就是答案

这需要先把每个长方体的所有面上的格点中,都存上当前长方体的编号,这乍看复杂度很高,但是前面已经分析得到了,每个格点最多两个长方体,故这部分复杂度不超过格点个数*2,即6∗10036*100^361003,并不大

有三个维度,可以写个函数来复用,每次交换数据维度即可,即最开始考虑所有z=Cz=Cz=C的平面上的格点,然后swap(xi,zi)swap(x_i,z_i)swap(xi,zi),然后考虑所有x=Cx=Cx=C的平面上的格点,以此类推…

vi id[110][110][110];
void solve(void){
	int n;
	cin>>n;
	vi x1(n+1),y1(n+1),z1(n+1),x2(n+1),y2(n+1),z2(n+1);
	rep(i,1,n){
		cin>>x1[i]>>y1[i]>>z1[i]>>x2[i]>>y2[i]>>z2[i];
	}
	vi ans(n+1);
	int cnt=0;
	auto work=[&]()->void{
		cnt++;
		rep(i,0,100){
			rep(j,0,100){
				rep(k,0,100){
					id[i][j][k].clear();
				}
			}
		}
		vector<vector<array<int,5>>>p(110);
		rep(i,1,n){
			rep(j,x1[i],x2[i]-1)
				rep(k,y1[i],y2[i]-1)
					id[z1[i]][j][k].push_back(i),
					id[z2[i]][j][k].push_back(i);
			
		}
		rep(i,0,100){
			set<pii>vis;
			rep(j,0,100){
				rep(k,0,100){
					if(id[i][j][k].size()>1){
						int x=id[i][j][k][0],y=id[i][j][k][1];
						if(!vis.count({x,y})&&!vis.count({y,x})){
							ans[x]++;
							ans[y]++;
							vis.insert({x,y});
						}
					}
				}
			}
		}
	};
	work();
	rep(i,1,n){
		swap(x1[i],z1[i]);
		swap(x2[i],z2[i]);
	}
	work();
	rep(i,1,n){
		swap(x1[i],z1[i]);
		swap(x2[i],z2[i]);
	}
	rep(i,1,n){
		swap(y1[i],z1[i]);
		swap(y2[i],z2[i]);
	}
	work();
	
	rep(i,1,n){
		cout<<ans[i]<<'\n';
	}
}

F

贪心 二分

瓶子不需要开罐器就能获得价值,罐头需要用一次开罐器才能获得价值,一个开罐器最多开xix_ixi次罐头

给一堆瓶子,罐头,开罐器,最多选m个,问最大价值和?

贪心的想肯定先选价值大的,或者使用次数多的开罐器,故对这三种东西分别按权值降序排序。

接下来有多个维度的问题肯定考虑枚举。

然后瓶子是没有其他限制的,先不考虑他。罐头个数确定,我们可以二分出来至少用几个开罐器,也就是开罐器是确定的,剩下的可以都选瓶子。而枚举开罐器个数,我们只能确定最多多少个罐头,但是不一定真的要开这么多,因为可能后面的罐头价值不如瓶子了,有点麻烦

所以还是枚举罐头个数,为了二分快速check,以及快速计算剩余名额全给瓶子最大价值,对瓶子和开罐器都做前缀和。

剩下是一些细节,需要考虑:没有开罐器,开罐器不够开当前罐头,剩余名额给瓶子用不完,罐头+开罐器个数超过m

void solve(void){
	int n,m;
	cin>>n>>m;
	
	vi a,b,c;
	rep(i,1,n){
		int t,x;
		cin>>t>>x;
		if(t==0)a.push_back(x);
		else if(t==1)b.push_back(x);
		else c.push_back(x);
	}
	sort(a.begin(),a.end(),greater<int>());
	sort(b.begin(),b.end(),greater<int>());
	sort(c.begin(),c.end(),greater<int>());
	
	int sza=a.size();
	rep(i,1,sza-1){
		a[i]+=a[i-1];
	}
	int ans=0;
	if(sza)ans=a[min(sza,m)-1];
	
	int szc=c.size();
	if(!szc){
		if(!sza)cout<<0;
		else cout<<a[min(m,sza)-1];
		return;
	}
	
	rep(i,1,szc-1){
		c[i]+=c[i-1];
	}
	
	int szb=b.size();
	rep(i,1,szb-1){
		b[i]+=b[i-1];
	}
	
	rep(i,0,szb-1){
		int l=1,r=szc;
		while(l<=r){
			int m=l+r>>1;
			if(c[m-1]>=i+1)r=m-1;
			else l=m+1;
		}
		if(l>szc)break;
		int cost=l+i+1;
		if(cost>m)continue;
		if(!sza||m==cost){
			ans=max(ans,b[i]);
		}
		else{
			ans=max(ans,b[i]+a[min(sza,m-cost)-1]);
		}
	}
	cout<<ans;
}

G

树上计数

问有多少个三个点的组合,满足不在一条简单路径上?

两种思路,

直接统计:三个点的关系一定是,在一个点为根的树上的三个不同子树内,想从一个点到另一个点必须经过这个根,也就是对于三个点来说这个根是唯一的

所以我们枚举根,然后统计每个点为根下,不同子树的三个点的组合即可,这样是不重不漏的

正难则反:

正着不好想可以反着,三个点组合总数是Cn3C_n^3Cn3,在一条简单路径上的点,一定是三个点连成一串,可以看成是中间那个点为根,另外两个点在不同子树下,这就是经典的选两个点在不同子树中的方案数,∑szv∗(tot−szv)\sum sz_v*(tot-sz_v)szv(totszv)即可,注意这里往父亲方向走也是一个子树,(因为我们枚举的是每个点作为根的情况),所以tottottot不是szu−1sz_u-1szu1,而是n−1n-1n1

并且这样统计每个点对会被统计两遍,需要除二

void solve(void){
	int n;
	cin>>n;
	
	vvi g(n+1);
	rep(i,1,n-1){
		int x,y;
		cin>>x>>y;
		g[x].push_back(y);
		g[y].push_back(x);
		
	}
	vi sz(n+1,1);
	auto &&dfs1=[&](auto &&dfs1,int u,int f)->void{
		for(int v:g[u]){
			if(v==f)continue;
			dfs1(dfs1,v,u);
			sz[u]+=sz[v];
		}
	};
	int ans=n*(n-1)*(n-2)/6;
	int sum=0;
	auto &&dfs2=[&](auto &&dfs2,int u,int f)->void{
		for(int v:g[u]){
			if(v==f){
				sum+=(n-sz[u])*(n-1-(n-sz[u]));
			}
			else{
				sum+=sz[v]*(n-1-sz[v]);
				dfs2(dfs2,v,u);
			}
		}
	};
	dfs1(dfs1,1,0);
	dfs2(dfs2,1,0);
	
	cout<<ans-sum/2;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值