省选集训 27 - 图论 & 贪心
P14404 [JOISC 2016] 最差的记者 2 / Worst Reporter 2
令 \((A_i,B_i)\) 为左部点,\((C_j,D_j)\) 为右部点,两部点有连边当前仅当 \(A_i=C_j\) 且 \(B_i\leq D_j\),题目即为要求修改最少的 \(C_j\) 使得原图存在完美匹配。根据 Hall 定理,把 \(B\) 和 \(D\) 混在一起排序后,令属于 \(B\) 数组的值为 \(1\),属于 \(D\) 数组的值为 \(-1\),存在完美匹配当且仅当此时 \(s_{min}\geq 0\),\(s\) 为前缀和数组,所以考虑用数据结构维护 \(s\),显然线段树是最方便的。
贪心地想,对于每个 \((C_j,D_j)\) 一定是匹配 \(B_i\) 最大的 \((A_i,B_i)\),所以考虑用 set 维护下每个 \(A_i\) 对应的有哪些 \((A_i,B_i)\),从大到小枚举 \(D_j\) 尝试进行匹配即可。为什么要从大到小枚举 \(D_j\)?因为对于同一个 \((A_i,B_i)\) 无论贡献哪一个 \((C_j,D_j)\) 答案都是一样的,但贡献靠后的可以让前面的 \((A_{i'},B_{i'})\) 跟前面的 \((C_{j'},D_{j'})\) 去匹配。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 200005
pair<int,int> s[N<<1];
set<pair<int,int>> st[N];
int n,ans,a[N],b[N],c[N],d[N],dy1[N],dy2[N];
struct Segment_tree{
int sum[N<<2],mn[N<<2];
void pushup(int p,int ls,int rs){
sum[p]=sum[ls]+sum[rs];
mn[p]=min(mn[ls],sum[ls]+mn[rs]);
}
void build(int l=1,int r=n*2,int p=1){
if(l==r) return sum[p]=mn[p]=(s[l].second<=n?1:-1),void();
int mid=(l+r)>>1,ls=mid<<1,rs=mid<<1|1;
build(l,mid,ls),build(mid+1,r,rs),pushup(p,ls,rs);
}
void update(int pos,bool op,int l=1,int r=n*2,int p=1){
if(l==r){
int tmp=(s[l].second<=n?1:-1);
return sum[p]=mn[p]=op*tmp,void();
}
int mid=(l+r)>>1,ls=mid<<1,rs=mid<<1|1;
pos<=mid?update(pos,op,l,mid,ls):update(pos,op,mid+1,r,rs);
pushup(p,ls,rs);
}
}SGT;
int main(){
scanf("%d",&n),ans=n;
for(int i=1;i<=n;i++){
scanf("%d%d",a+i,b+i);
st[a[i]].emplace(b[i],i),s[i]={b[i],i};
}
for(int i=1;i<=n;i++)
scanf("%d%d",c+i,d+i),s[i+n]={d[i],i+n};
sort(s+1,s+n*2+1),SGT.build();
for(int i=1;i<=n*2;i++){
int num=s[i].second;
num<=n?dy1[num]=i:dy2[num-n]=i;
}
for(int i=n;i>=1;i--){
auto it=st[c[i]].lower_bound({d[i],n+1});
if(it==st[c[i]].begin()) continue;
it--,SGT.update(dy1[it->second],0),SGT.update(dy2[i],0);
if(SGT.mn[1]>=0) ans--,st[c[i]].erase(it);
else SGT.update(dy1[it->second],1),SGT.update(dy2[i],1);
}
printf("%d\n",ans);
}
[COCI 2022/2023 #3] Baltazar
一条道路合法当且仅当所有长为 \(D\) 的路径都包含它且存在一条长为 \(D+1\) 的路径不包含它。
怎么求长为 \(D\) 路径的必经边?建出最短路图,一条边为必经边当且仅当删去它后 \(1\) 和 \(n\) 不连通,边删去后两点的连通性问题自然想到使用线段树分治解决。那怎么求长为 \(D+1\) 路径的必经边呢?考虑 \(D\) 和 \(D+1\) 具有什么性质:奇偶性不同。这启发我们根据奇偶性拆点建图,维护到每个点路径长为奇数和偶数的最短路。
具体地:如果 \(w\) 为奇数拆成 \((u_1,v_2,w)\) 和 \((u_2,v_1,w)\),否则拆成 \((u_1,v_1,w)\) 和 \((u_2,v_2,w)\)。
然后如果发现 \(|d_{1,n_1}-d_{1,n_2}|\neq 1\),说明没有长为 \(D+1\) 的路径,直接输出 \(0\)。
否则可以通过 \(d_{1,n_1}\) 和 \(d_{1,n_2}\) 的大小判断哪个是真正的终点。
接着我们在奇偶两个最短路图上分别找出长度 \(D\) 和 \(D+1\) 的必经边。
如果一条边 \(D\) 必经而 \(D+1\) 不必经就合法,注意找必经边的时候要使用原图的点标号并对边集去重。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 200005
pair<int,int> s[N<<1];
set<pair<int,int>> st[N];
int n,ans,a[N],b[N],c[N],d[N],dy1[N],dy2[N];
struct Segment_tree{
int sum[N<<2],mn[N<<2];
void pushup(int p,int ls,int rs){
sum[p]=sum[ls]+sum[rs];
mn[p]=min(mn[ls],sum[ls]+mn[rs]);
}
void build(int l=1,int r=n*2,int p=1){
if(l==r) return sum[p]=mn[p]=(s[l].second<=n?1:-1),void();
int mid=(l+r)>>1,ls=mid<<1,rs=mid<<1|1;
build(l,mid,ls),build(mid+1,r,rs),pushup(p,ls,rs);
}
void update(int pos,bool op,int l=1,int r=n*2,int p=1){
if(l==r){
int tmp=(s[l].second<=n?1:-1);
return sum[p]=mn[p]=op*tmp,void();
}
int mid=(l+r)>>1,ls=mid<<1,rs=mid<<1|1;
pos<=mid?update(pos,op,l,mid,ls):update(pos,op,mid+1,r,rs);
pushup(p,ls,rs);
}
}SGT;
int main(){
scanf("%d",&n),ans=n;
for(int i=1;i<=n;i++){
scanf("%d%d",a+i,b+i);
st[a[i]].emplace(b[i],i),s[i]={b[i],i};
}
for(int i=1;i<=n;i++)
scanf("%d%d",c+i,d+i),s[i+n]={d[i],i+n};
sort(s+1,s+n*2+1),SGT.build();
for(int i=1;i<=n*2;i++){
int num=s[i].second;
num<=n?dy1[num]=i:dy2[num-n]=i;
}
for(int i=n;i>=1;i--){
auto it=st[c[i]].lower_bound({d[i],n+1});
if(it==st[c[i]].begin()) continue;
it--,SGT.update(dy1[it->second],0),SGT.update(dy2[i],0);
if(SGT.mn[1]>=0) ans--,st[c[i]].erase(it);
else SGT.update(dy1[it->second],1),SGT.update(dy2[i],1);
}
printf("%d\n",ans);
}
[PA 2015] 精确打击 / Kontrmanifestacja
首先如果图中没有环直接输出无解,这一步是容易做到的。
否则先考虑找出图中的一个环,如果删去这个环后图上仍有环显然直接输出 \(0\)。
否则删去环后的图会构成一个有向无环图,考虑将环上节点重标号为 \(1,2,\cdots,c\)。
如果一个节点 \(i\) 在新图上能到达 \(j\):
当 \(j>i\) 时 \((i,j)\) 这段区间显然不合法,考虑找到一个最大能被 \(i\) 到达的 \(j\),记为 \(mx_i\)。
当 \(j\leq i\) 时 \([1,i)\) 和 \((j,c]\) 这两段区间不合法,考虑找到一个 \(\leq i\) 且能被 \(i\) 到达的 \(j\),记为 \(t_i\)。
\(mx_i\) 在新图上拓扑排序是容易求出来的,但是 \(t_i\) 不是很好求,考虑分开处理 \([1,i)\) 和 \((j,c]\)。
当最小能被 \(i\) 到达的 \(j\),记为 \(mn_i\),\(mn_i\leq i\) 时就可以去掉 \([1,i)\)。
\((j,c]\) 的处理考虑在反图上再求一遍 \(mx'_i\),如果 \(mx'_i\geq i\) 就可以去掉 \((j,c]\)。
从环上去掉一段区间使用前缀和是容易解决的,至此我们在 \(O(n+m)\) 的复杂度内解决了本题。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 500005
queue<int> q;
stack<int> st;
vector<int> c,v[N],vf[N];
int n,m,len,res,cd[N],mn[N],mx[N],vis[N],ins[N],inc[N],dif[N],ans[N];
void dfs1(int x){
vis[x]=1,st.push(x),ins[x]=1;
for(int i=0,y;i<v[x].size()&&c.empty();i++){
if(!vis[y=v[x][i]]) dfs1(y);
else if(ins[y]){
for(;st.top()!=y;st.pop()) c.push_back(st.top());
c.push_back(y),reverse(c.begin(),c.end());
}
}
if(ins[x]=0,c.empty()) st.pop();
}
void dfs2(int x){
vis[x]=1,ins[x]=1;
for(auto y:v[x]){
if(!inc[y]&&!vis[y]) dfs2(y);
else if(ins[y]) cout<<"0\n\n",exit(0);
}
ins[x]=0;
}
void work(){
for(int i=1;i<=n;i++) cd[i]=v[i].size();
for(int i=1;i<=n;i++){
for(auto x:v[i]){
if(!inc[x]||inc[i]&&inc[x]==inc[i]%len+1) continue;
cd[i]--,mn[i]=min(mn[i],inc[x]),mx[i]=max(mx[i],inc[x]);
}
}
for(int i=1;i<=n;i++) if(!cd[i]&&!inc[i]) q.push(i);
while(!q.empty()){
int x=q.front();q.pop();if(inc[x]) continue;
for(auto y:vf[x]){
if(!--cd[y]) q.push(y);
mn[y]=min(mn[y],mn[x]),mx[y]=max(mx[y],mx[x]);
}
}
}
int main(){
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
cin>>n>>m;
for(int i=1,x,y;i<=m;i++){
cin>>x>>y;
v[x].push_back(y),vf[y].push_back(x);
}
for(int i=1;i<=n&&c.empty();i++) if(!vis[i]) dfs1(i);
if(!(len=c.size())) return cout<<"NIE\n",0;
fill(vis,vis+n+1,0);
for(int i=0;i<len;i++) inc[c[i]]=i+1;
for(int i=1;i<=n;i++) if(!inc[i]&&!vis[i]) dfs2(i);
fill(mn,mn+n+1,n+1),fill(mx,mx+n+1,0),work();
for(int i=0;i<len;i++){
if(mn[c[i]]<=i+1) dif[i+1]++;
if(mx[c[i]]>i+1) dif[i+1]++,dif[mx[c[i]]-1]--;
}
for(int i=1;i<=n;i++) swap(v[i],vf[i]);
fill(mn,mn+n+1,n+1),fill(mx,mx+n+1,0),work();
for(int i=0;i<len;i++) if(mx[c[i]]>=i+1) dif[0]++,dif[i]--;
for(int i=1;i<len;i++) dif[i]+=dif[i-1];
for(int i=0;i<len;i++) if(!dif[i]) res+=ans[c[i]]=1;
cout<<res<<"\n";
for(int i=1;i<=n;i++) if(ans[i]) cout<<i<<" ";cout<<"\n";
}
[CCPC 2023 北京市赛] 哈密顿
参考题解:https://www.luogu.com.cn/article/c7kfdzzx
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=100005,INF=0x3f3f3f3f;
pair<int,int> pa[N],pb[N];
int n,pos,ans=-INF,a[N],b[N],sa[N],sb[N],vis[N];
signed main(){
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i]>>b[i],pa[i]={a[i],i},pb[i]={b[i],i};
sort(pa+1,pa+n+1),sort(pb+1,pb+n+1);
for(int i=1;i<=n;i++) a[i]=pa[i].first,b[i]=pb[i].first;
for(int i=1;i<=n;i++) sa[i]=sa[i-1]+a[i],sb[i]=sb[i-1]+b[i];
for(int i=0;i<=n;i++){
int sum=sa[n]-sa[i]-sa[i]+sb[n]-sb[n-i]-sb[n-i];
if(sum>ans) ans=sum,pos=i;
}
if(pos==0||pos==n) cout<<ans<<"\n",exit(0);
for(int i=1;i<=pos;i++) vis[pa[i].second]=1;
for(int i=0;i<pos;i++) if(!vis[pb[n-i].second]) cout<<ans<<"\n",exit(0);
int a1=pos,a2=pos+1,b1=n-pos,b2=n-pos+1;
if(a[a1]<=b[b1]){
if(b[b2]<=a[a2]) ans-=((b[b2]-b[b1])<<1);
else{
if(a2==n||pa[a2].second!=pb[b1].second) ans-=((a[a2]-b[b1])<<1);
else{
int a3=a2+1,b3=n-pos-1;
ans-=(min(a[a2]-max(b[b3],a[a1]),min(a[a3],b[b2])-b[b1])<<1);
}
}
}
else{
if(a[a2]<=b[b2]) ans-=((a[a2]-a[a1])<<1);
else{
if(b2==n||pb[b2].second!=pa[a1].second) ans-=((b[b2]-a[a1])<<1);
else{
int a3=a1-1,b3=b2+1;
ans-=(min(b[b2]-max(a[a3],b[b1]),min(b[b3],a[a2])-a[a1])<<1);
}
}
}
cout<<ans<<"\n";
}
[AGC056D] Subset Sum Game
参考题解:https://www.luogu.com.cn/article/cc1dt891
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 5005
#define int long long
int n,l,r,flag,a[N],su1[N],su2[N];
bool check(int l,int r,vector<int> &v){
su1[0]=0,su2[0]=0;
for(int i=1;i<n;i++){
su1[i]=su1[i-1]+(i&1)*v[i];
su2[i]=su2[i-1]+(!(i&1))*v[i];
}
for(int i=1;i<n;i++){
int sum1=su1[i-1]+su2[n-1]-su2[i];
int sum2=su2[i-1]+su1[n-1]-su1[i];
if(l<=sum1&&sum2<=r) return 1;
if(i==n-1) return 0;
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
cin>>n>>l>>r;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+n+1);
for(int i=1;i<=n&&!flag;i++){
vector<int> v(1,0);
for(int j=1;j<i;j++) v.push_back(a[j]);
for(int j=i+1;j<=n;j++) v.push_back(a[j]);
flag|=check(l-a[i],r-a[i],v);
}
cout<<(flag?"Alice":"Bob")<<"\n";
}

浙公网安备 33010602011771号