ABC245:E - 树状数组 / mutiset + 双指针,F - dfs回溯

4 篇文章 0 订阅
3 篇文章 0 订阅

E - Wrapping Chocolate


题意:

给 n 块巧克力,每块巧克力有长度 a i a_i ai,宽度 b i b_i bi
一共 m 个箱子,每个箱子有长度 c i c_i ci,宽度 d i d_i di

巧克力和箱子的角度都是固定的,即不能旋转放置。且每个箱子中最多只能放一块巧克力。
每块巧克力只能放置在长度大于等于其长度,宽度大于等于其宽度的箱子里。
问,能否将这 n 块巧克力都放到箱子中?

1 ≤ N ≤ M ≤ 2 × 1 0 5 1≤N≤M≤2×10^5 1NM2×105
1 ≤ A i , B i , C i , D i ≤ 1 0 9 1≤A_i, B_i, C_i, D_i ≤10^9 1Ai,Bi,Ci,Di109


思路:

大体上看,肯定是大巧克力配大箱子,所以先按照长度为第一元素,宽度为第二元素,将所有巧克力和箱子从小到大排序。

巧克力和箱子,两个都是动态变化的,我们要抓住一个不变,只让一个随之改变。
从后往前枚举巧克力,再用一个指针,将所有长度大于等于当前枚举巧克力长度的所有箱子都放进来,放到一个集合中。
那么从这个集合中找到第一个宽度大于等于当前巧克力宽度的箱子,便是利用率最高的。把宽度较大的留给宽度更大的巧克力。
用这个箱子来装当前的巧克力,把该箱子从集合中删掉。

我们发现,从这个集合中查找只用的宽度,所以只需要把宽度放到集合中。
如何从集合中找到第一个大于等于x的值呢?
两种思路:

  1. 按照数值建立树状数组,然后二分第一个大于x的值mid,判断sum(mid)-sum(x-1)是否不为0,如果不为0就说明x到mid之间有值,r=mid。那么便可以找到第一个大于等于x的值了。
    每次插入x到集合中就是在树状数组第x个位置+1。因为x可能很大,所以还需要将所有值离散化。
  2. 将所有值放到multiset中,然后用自带的lower_bound找到第一个大于x的迭代器,然后删除这个迭代器,就相当于把这个值删掉了。
    每次插入x到multiset中,直接insert。

如果在集合中查不到,那么就说明当前值放不下,无解。


Code:

思路1:树状数组 + 手写二分 + 离散化

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

const int N = 1000010, mod = 1e9+7;
int T, n, m;
vector<int> v;
PII a[N], b[N];
int c[N];

int lbit(int x){
	return x & -x;
}

void add(int x, int y){
	for(int i=x;i<=800000;i+=lbit(i)) c[i]+=y;
}

int query(int x){
	int sum=0;
	for(int i=x;i;i-=lbit(i)) sum+=c[i];
	return sum;
}

bool check(int mid, int x)
{
	if(query(mid) - query(x-1)) return 1;
	return 0;
}

int get(int x)
{
	return lower_bound(v.begin(), v.end(), x) - v.begin() + 1;
}

signed main(){
	Ios;
	cin>>n>>m;
	
	for(int i=1;i<=n;i++) cin>>a[i].fi, v.pb(a[i].fi);
	for(int i=1;i<=n;i++) cin>>a[i].se, v.pb(a[i].se);
	
	for(int i=1;i<=m;i++) cin>>b[i].fi, v.pb(b[i].fi);
	for(int i=1;i<=m;i++) cin>>b[i].se, v.pb(b[i].se);
	
	sort(v.begin(), v.end());
	v.erase(unique(v.begin(), v.end()), v.end());
	
	for(int i=1;i<=n;i++) a[i].fi = get(a[i].fi), a[i].se = get(a[i].se);
	for(int i=1;i<=m;i++) b[i].fi = get(b[i].fi), b[i].se = get(b[i].se);
	
	sort(a+1, a+n+1);
	sort(b+1, b+m+1);
	
	int j=m+1, flag=0;
	for(int i=n;i>=1;i--)
	{
		while(j>1 && b[j-1].fi >= a[i].fi){
			j--;
			add(b[j].se, 1);
		}
		int l=a[i].se, r=800000;
		while(l<r)
		{
			int mid=l+r>>1;
			if(check(mid, a[i].se)) r=mid;
			else l=mid+1;
		}
		
//		cout << i << " : " <<j<<" "<< l<<endl;
		
		if(l==800000){
			flag=1;
			break;
		}
		add(l, -1);
	}
	
	if(flag) cout<<"No";
	else cout<<"Yes";
	
	return 0;
}

思路2:multiset + lower_bound

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

const int N = 200010, mod = 1e9+7;
int T, n, m;
PII a[N], b[N];
multiset<int> st;

signed main(){
	Ios;
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i].fi;
	for(int i=1;i<=n;i++) cin>>a[i].se;
	for(int i=1;i<=m;i++) cin>>b[i].fi;
	for(int i=1;i<=m;i++) cin>>b[i].se;
	
	sort(a+1, a+n+1);
	sort(b+1, b+m+1);
	
	int j=m+1, flag=0;
	for(int i=n;i>=1;i--)
	{
		while(j>1 && b[j-1].fi >= a[i].fi)
			st.insert(b[j-1].se), j--;
		
		auto it = st.lower_bound(a[i].se);
		
		if(it == st.end()){
			flag=1;break;
		}
		else st.erase(it);
	}
	
	if(flag) cout<<"No";
	else cout<<"Yes";
	
	return 0;
}

比较下,第二种码量少些,而且从运行结果看,时间复杂度更低。

很好的一道题!


F - Endless Walk

题意:

给定一个n个点,m条边的有向图。
问,求最多有多少点满足:从该点出发,最终可以进环?

思路:

从一点开始搜,如果搜到之前路径上的点,也就是出现环了,就把该点标记。
回溯的时候判断,如果回来的点上有标记,那么把该点也标记。

注意判断,搜过的点不要再搜。
如果发现之前走过,先判断是否标记过,再跳过。

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

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
vector<int> e[N];
bool vis[N], f[N], flag[N];

void dfs(int x)
{
	for(auto tx:e[x])
	{
		if(f[tx]){flag[x]=1;continue;}
		if(flag[tx]) flag[x]=1;
		if(vis[tx]) continue;
		
		vis[tx] = 1;
		f[tx] = 1;
		dfs(tx);
		f[tx] = 0;
		
		if(flag[tx]) flag[x]=1;
	}
}

signed main(){
	Ios;
	cin>>n>>m;
	
	while(m--)
	{
		int x,y;cin>>x>>y;
		e[x].pb(y);
	}
	
	for(int i=1;i<=n;i++)
	{
		if(!vis[i]) dfs(i);
	}
	
	int ans=0;
	for(int i=1;i<=n;i++) if(flag[i]) ans++;
	cout << ans;
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值