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
1≤N≤M≤2×105
1
≤
A
i
,
B
i
,
C
i
,
D
i
≤
1
0
9
1≤A_i, B_i, C_i, D_i ≤10^9
1≤Ai,Bi,Ci,Di≤109
思路:
大体上看,肯定是大巧克力配大箱子,所以先按照长度为第一元素,宽度为第二元素,将所有巧克力和箱子从小到大排序。
巧克力和箱子,两个都是动态变化的,我们要抓住一个不变,只让一个随之改变。
从后往前枚举巧克力,再用一个指针,将所有长度大于等于当前枚举巧克力长度的所有箱子都放进来,放到一个集合中。
那么从这个集合中找到第一个宽度大于等于当前巧克力宽度的箱子,便是利用率最高的。把宽度较大的留给宽度更大的巧克力。
用这个箱子来装当前的巧克力,把该箱子从集合中删掉。
我们发现,从这个集合中查找只用的宽度,所以只需要把宽度放到集合中。
如何从集合中找到第一个大于等于x的值呢?
两种思路:
- 按照数值建立树状数组,然后二分第一个大于x的值mid,判断sum(mid)-sum(x-1)是否不为0,如果不为0就说明x到mid之间有值,r=mid。那么便可以找到第一个大于等于x的值了。
每次插入x到集合中就是在树状数组第x个位置+1。因为x可能很大,所以还需要将所有值离散化。 - 将所有值放到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;
}