省选集训 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";
}
posted @ 2026-02-03 08:54  tkdqmx  阅读(6)  评论(0)    收藏  举报