CFR-826-Div-3解题报告

F. Multi-Colored Segments

题意:数轴上有 \(n\) 个线段,每个区间有一个颜色 \(c\),对于每个线段,求与它颜色不同的线段中与它的最短距离。距离定义为两个线段中的点集最近的两个点的距离,如果相交则为 \(0\)

做法1

可以想到按颜色排序,正着扫一遍再反着扫一遍,每次维护当前颜色之前的所有颜色的贡献。

具体地,可以用两个 \(set\) 分别维护出现过的所有 \(l\)\(r\) 的位置,每次查询在当前区间 \(r\) 左边的最大 \(r\) 和当前区间 \(l\) 右边的最小 \(l\)。可以发现,这种做法有一个缺陷,即对于完全被包含的情况无法考虑到。于是我们可以离散化后维护一个权值树状数组,对于每个区间在 \(l\)\(+1\),在 \(r+1\)\(-1\),此时如果存在一个区间包含当前区间 \([l,r]\),则对应树状数组里的 \(1\sim r\) 的和大于 \(0\)。这是充分不必要条件,但显然不会出错。

#include<bits/stdc++.h>
using namespace std;
struct seg{int l,r,c,num;} a[200010];
int b[400010],ans[200010],cnt=0;
int t[400010];
void add(int x,int y) {
	for(;x<=cnt;x+=x&-x) t[x]+=y;
}
int sum(int x,int res=0) {
	for(;x;x-=x&-x) res+=t[x];
	return res;
}
void chkmin(int &x,int y) {x=min(x,y);}
signed main() {
	int T;
	cin>>T;
	while(T--) {
		cnt=0;
		int n;
		cin>>n;
		for(int i=1;i<=n;i++) ans[i]=1e9;
		for(int i=1;i<=n;i++) {
			cin>>a[i].l>>a[i].r>>a[i].c;
			b[++cnt]=a[i].l,b[++cnt]=a[i].r;
			a[i].num=i;
		}
		sort(b+1,b+cnt+1);
		cnt=unique(b+1,b+cnt+1)-b-1;
		for(int i=1;i<=n;i++) {
			a[i].l=lower_bound(b+1,b+cnt+1,a[i].l)-b;
			a[i].r=lower_bound(b+1,b+cnt+1,a[i].r)-b;
		}
		auto solve=[&]() {
			set<int> l,r;
			for(int i=1;i<=n;i++) {
				if(sum(a[i].r)>0) ans[a[i].num]=0;
				auto tmp1=l.lower_bound(a[i].l);
				if(tmp1!=l.end())
					chkmin(ans[a[i].num],max(0,b[*tmp1]-b[a[i].r]));
				auto tmp2=r.upper_bound(a[i].r);
				if(tmp2!=r.begin()) {
					tmp2--;
					chkmin(ans[a[i].num],max(0,b[a[i].l]-b[*tmp2]));
				}
				if(a[i].c!=a[i+1].c) {
					for(int j=i;j>0&&a[j].c==a[i].c;j--) {
						add(a[j].l,1),add(a[j].r+1,-1);
						l.insert(a[j].l);
						r.insert(a[j].r);
					}
				}
			}
		};
		sort(a+1,a+n+1,[](seg x,seg y){return x.c<y.c;});
		for(int i=1;i<=cnt;i++) t[i]=0;
		solve();
		sort(a+1,a+n+1,[](seg x,seg y){return x.c>y.c;});
		for(int i=1;i<=cnt;i++) t[i]=0;
		solve();
		for(int i=1;i<=n;i++)
			cout<<ans[i]<<" ";
		cout<<endl;
	}
	return 0;
}

做法2

同样是按颜色分开考虑,先处理出上文中 \(set\) 维护的部分,然后再统一处理包含的情况。

对于包含的情况,\(l\)\(r\) 分开考虑,我们对于每个坐标(离散化后)预处理出以它为 \(l\) 的所有区间编号以及以它为 \(r\) 的所有区间编号。从左往右扫每个位置,用 \(set\) 维护包含这个位置的所有区间,当碰到 \(l\) 时就加入区间,碰到 \(r\) 时就删除区间。如果我们扫到一个 \(l\),发现 \(set\) 里还有别的区间并且颜色个数多于 \(1\) 个,此时 \(set\) 里所有的区间都是有相交的,答案全部赋成 \(0\),然后从 \(set\) 里删掉即可。维护颜色个数可以使用 \(map\)

By SSRS_

#include <bits/stdc++.h>
using namespace std;
const int INF = 1000000000;
int main(){
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  int t;
  cin >> t;
  for (int i = 0; i < t; i++){
    int n;
    cin >> n;
    vector<int> l(n), r(n), c(n);
    for (int j = 0; j < n; j++){
      cin >> l[j] >> r[j] >> c[j];
      c[j]--;
    }
    vector<vector<int>> id(n);
    for (int j = 0; j < n; j++){
      id[c[j]].push_back(j);
    }
    vector<int> ans(n, INF);
    for (int j = 0; j < 2; j++){
      set<int> st;
      for (int k = 0; k < n; k++){
        for (int x : id[k]){
          auto itr1 = st.lower_bound(l[x]);
          if (itr1 != st.end()){
            ans[x] = min(ans[x], *itr1 - l[x]);
          }
          if (itr1 != st.begin()){
            ans[x] = min(ans[x], l[x] - *prev(itr1));
          }
          auto itr2 = st.lower_bound(r[x]);
          if (itr2 != st.end()){
            ans[x] = min(ans[x], *itr2 - r[x]);
          }
          if (itr2 != st.begin()){
            ans[x] = min(ans[x], r[x] - *prev(itr2));
          }
        }
        for (int x : id[k]){
          st.insert(l[x]);
          st.insert(r[x]);
        }
      }
      reverse(id.begin(), id.end());
    }
    vector<int> x;
    for (int j = 0; j < n; j++){
      x.push_back(l[j]);
      x.push_back(r[j]);
    }
    sort(x.begin(), x.end());
    x.erase(unique(x.begin(), x.end()), x.end());
    for (int j = 0; j < n; j++){
      l[j] = lower_bound(x.begin(), x.end(), l[j]) - x.begin();
      r[j] = lower_bound(x.begin(), x.end(), r[j]) - x.begin();
    }
    int cnt = x.size();
    vector<vector<int>> L(cnt), R(cnt);
    for (int j = 0; j < n; j++){
      L[l[j]].push_back(j);
      R[r[j]].push_back(j);
    }
    set<int> st;
    map<int, int> mp;
    for (int j = 0; j < cnt; j++){
      for (int k : L[j]){
        st.insert(k);
        mp[c[k]]++;
        if (mp.size() > 1){
          for (int a : st){
            ans[a] = 0;
          }
          st.clear();
        }
      }
      for (int k : R[j]){
        mp[c[k]]--;
        if (mp[c[k]] == 0){
          mp.erase(c[k]);
        }
        st.erase(k);
      }
    }
    for (int j = 0; j < n; j++){
      cout << ans[j];
      if (j < n - 1){
        cout << ' ';
      }
    }
    cout << endl;
  }
}

做法3

这次我们不用按颜色分开考虑。我们直接将每段区间分成两个端点,按坐标整体排序,再正反各扫一遍。以从左到右扫为例,扫的过程中维护之前区间中最近和次近的区间 \(r\) 及其颜色(要求这两个值的颜色不同),每次询问如果与最近颜色相同,则取次近颜色,否则取最近颜色。

By jiangly

#include <bits/stdc++.h>

using i64 = long long;

constexpr int inf = 1E9;

void solve() {
    int n;
    std::cin >> n;

    std::vector<std::array<int, 5>> pts(2 * n);
    for (int i = 0; i < n; i++) {
        int l, r, c;
        std::cin >> l >> r >> c;
        pts[2 * i] = {l, r, c, i, 0};
        pts[2 * i + 1] = {r, l, c, i, 1};
    }

    std::sort(pts.begin(), pts.end());

    std::vector<int> ans(n, inf);

    for (int t = 0; t < 2; t++) {
        std::array<int, 2> f[2];
        f[0] = f[1] = {-inf, -1};
        for (auto [x, y, c, i, o] : pts) {
            if (!o) {
                std::array g{y, c};
                if (g > f[0]) {
                    std::swap(g, f[0]);
                }
                if ((g > f[1] && g[1] != f[0][1]) || f[0][1] == f[1][1]) {
                    f[1] = g;
                }
            } else {
                for (auto [z, d] : f) {
                    if (d != c) {
                        ans[i] = std::min(ans[i], std::max(0, y - z));
                    }
                }
            }
        }
        std::reverse(pts.begin(), pts.end());
        for (auto &[x, y, c, i, o] : pts) {
            x = inf - x;
            y = inf - y;
            o ^= 1;
        }
    }

    for (int i = 0; i < n; i++) {
        std::cout << ans[i] << " \n"[i == n - 1];
    }
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int t;
    std::cin >> t;

    while (t--) {
        solve();
    }

    return 0;
}

做法4

暴力美学。将所有区间扔到 \(set\) 里,如果没有颜色限制,询问一个区间的答案是很简单的。考虑枚举每个颜色,暴力将该颜色的所有区间全部从 \(set\) 里删掉,然后询问该颜色每个区间的答案,最后将这些区间重新扔回 \(set\) 里。

By Bench0310

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int t;
    cin >> t;
    while(t--)
    {
        int n;
        cin >> n;
        multiset<int> l,r;
        auto add=[&](int a,int b)
        {
            l.insert(a);
            r.insert(b);
        };
        auto rm=[&](int a,int b)
        {
            l.erase(l.find(a));
            r.erase(r.find(b));
        };
        auto nxt=[&](multiset<int> &s,int x)->int
        {
            auto it=s.lower_bound(x);
            if(it!=s.end()) return (*it);
            else return 2000000000;
        };
        auto prv=[&](multiset<int> &s,int x)->int
        {
            auto it=s.upper_bound(x);
            if(it!=s.begin()) return (*prev(it));
            else return -1000000000;
        };
        auto go=[&](int a,int b)->int
        {
            if(nxt(r,a)<=b||prv(l,b)>=a) return 0;
            return min(a-prv(r,a),nxt(l,b)-b);
        };
        vector<array<int,3>> v[n+1];
        vector<array<int,4>> e(n);
        for(int i=1;i<=n;i++)
        {
            int a,b,c;
            cin >> a >> b >> c;
            v[c].push_back({a,b,i});
            e[i-1]={a,b,c,i};
            add(a,b);
        }
        vector<int> res(n+1,(1<<30));
        for(int i=1;i<=n;i++)
        {
            for(auto [a,b,j]:v[i]) rm(a,b);
            for(auto [a,b,j]:v[i]) res[j]=min(res[j],go(a,b));
            for(auto [a,b,j]:v[i]) add(a,b);
        }
        multiset<array<int,2>> s;
        vector<int> opt(n+1,0);
        for(int i=1;i<=n;i++) s.insert({opt[i],i});
        ranges::sort(e);
        for(auto [a,b,c,j]:e)
        {
            s.erase(s.find({opt[c],c}));
            opt[c]=max(opt[c],b);
            s.insert({opt[c],c});
            auto it=s.rbegin();
            if((*it)[1]==c) it++;
            if((*it)[0]>=b) res[j]=0;
        }
        for(int i=1;i<=n;i++) cout << res[i] << " \n"[i==n];
    }
    return 0;
}

G. Kirill and Company

题意:有一个无向图,边权为 \(1\),给你 \(f\) 个一型点的编号(可以重复)和 \(k\) 个二型点的编号(可以重复)。你需要对于每个一型点选择任意一条从 \(1\) 到该节点的最短路并选中该路径上所有的二型点,求最少有多少二型点没被选中(即选中尽可能多的二型点)。\(k\le 6\)

做法1

很自然的考虑状压 DP。首先预处理,令 \(f[i][s]\) 表示 \(i\) 号点能否有一条路径经过 \(s\) 的二型点。这个可以使用一次 BFS 处理出从 \(1\) 到每个节点的距离 \(dis[i]\),然后只考虑 \(i\to j,dis[i]+1=dis[j]\) 的转移(为了保证是最短路)。统计答案时,可以使用 \(g[i][s]\) 表示前 \(i\) 个一型点能否选中 \(s\) 的二型点。这两次 DP 的状态均为 \(O (n\times 2^k)\),转移分别为 \(O(1)\)\(O(2^k)\),可以通过此题。

#include<bits/stdc++.h>
using namespace std;
vector<int> a[10010];
int h[10010],x[6],dis[10010];
bool f[10010][70],done[10010],g[10010][70];
signed main() {
	int T;
	cin>>T;
	while(T--) {
		int n,m,t,k;
		cin>>n>>m;
		for(int i=1;i<=n;i++)
			for(int s=0;s<64;s++)
				f[i][s]=0;
		for(int i=1;i<=n;i++)
			a[i].clear(),dis[i]=1e9,done[i]=0;
		for(int i=1;i<=m;i++) {
			int u,v;
			cin>>u>>v;
			a[u].push_back(v);
			a[v].push_back(u);
		}
		cin>>t;
		for(int i=1;i<=t;i++)
			for(int s=0;s<64;s++)
				g[i][s]=0;
		for(int i=1;i<=t;i++)
			cin>>h[i];
		cin>>k;
		for(int i=0;i<k;i++) {
			int tt;
			cin>>tt;
			x[i]=h[tt];
		}
		queue<int> q;
		q.push(1),dis[1]=0;
		while(!q.empty()) {
			int u=q.front();q.pop();
			if(done[u]) continue;
			done[u]=1;
			for(int v:a[u])
				if(!done[v]&&dis[v]>dis[u]+1)
					dis[v]=dis[u]+1,q.push(v);
		}
		queue<pair<int,int> > q2;
		q2.push({1,0});
		while(!q2.empty()) {
			auto [u,s]=q2.front();q2.pop();
			if(f[u][s]) continue;
			for(int i=0;i<(1<<k);i++)
				if((i|s)==s) f[u][s]=1;
			for(int v:a[u])
				if(dis[v]==dis[u]+1) {
					int tmp=s;
					for(int i=0;i<k;i++)
						if(x[i]==v) tmp|=(1<<i);
					q2.push({v,tmp});
				}
		}
		for(int i=0;i<k;i++) {
			int tmp;
			for(int j=1;j<=t;j++)
				if(h[j]==x[i]) {
					tmp=j;
					break;
				}
			for(int j=tmp;j<=t;j++)
				h[j]=h[j+1];
		}
		g[0][0]=1;
		for(int i=1;i<=t-k;i++) {
			for(int s=0;s<(1<<k);s++) {
				for(int s2=0;s2<(1<<k);s2++) {
					if((s2|s)!=s) continue;
					if(!f[h[i]][s2]) continue;
					int x=s^s2;
					if(g[i-1][x]) g[i][s]=1;
				}
				for(int s2=0;s2<(1<<k);s2++)
					if((s2|s)==s) g[i][s2]|=g[i][s];
			}
		}
		int ans=k;
		for(int s=0;s<(1<<k);s++)
			if(g[t-k][s])
				ans=min(ans,k-__builtin_popcount(s));
		cout<<ans<<endl;
	}
	return 0;
}

做法2

由于 \(n\) 只有 \(1000\),可以考虑对每个二型点跑一遍 BFS,求出多源最短路,于是可以简化 DP 过程:枚举 \(i\)\(s\),直接判断从 \(1\) 经过 \(s\) 中的二型点到达 \(i\) 是否为最短路。

By jiangly

#include <bits/stdc++.h>

using i64 = long long;

constexpr int inf = 1E9;

void solve() {
    int n, m;
    std::cin >> n >> m;

    std::vector<std::vector<int>> adj(n);
    for (int i = 0; i < m; i++) {
        int u, v;
        std::cin >> u >> v;
        u--, v--;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    int f;
    std::cin >> f;

    std::vector<int> h(f);
    for (int i = 0; i < f; i++) {
        std::cin >> h[i];
        h[i]--;
    }

    int k;
    std::cin >> k;
    for (int i = 0; i < k; i++) {
        int p;
        std::cin >> p;
        p--;
        std::swap(h[i], h[p]);
    }

    auto bfs = [&](int s) {
        std::vector<int> d(n, -1);
        std::queue<std::array<int, 2>> q;
        q.push({s, 0});
        while (!q.empty()) {
            auto [x, v] = q.front();
            q.pop();
            if (d[x] != -1) {
                continue;
            }
            d[x] = v;
            for (auto y : adj[x]) {
                q.push({y, v + 1});
            }
        }
        return d;
    };

    auto d0 = bfs(0);
    std::sort(h.begin(), h.begin() + k, [&](int i, int j) {
        return d0[i] < d0[j];
    });
    std::vector<std::vector<int>> d(k);
    for (int i = 0; i < k; i++) {
        d[i] = bfs(h[i]);
    }

    std::vector<int> dp(1 << k);
    dp[0] = 1;

    for (int i = k; i < f; i++) {
        std::vector<int> good(1 << k);
        for (int s = 1; s < (1 << k); s++) {
            int lst = -1;
            int dist = 0;
            for (int i = 0; i < k; i++) {
                if (s >> i & 1) {
                    if (lst == -1) {
                        dist += d0[h[i]];
                    } else {
                        dist += d[lst][h[i]];
                    }
                    lst = i;
                }
            }
            dist += d[lst][h[i]];
            if (dist == d0[h[i]]) {
                good[s] = 1;
            }
        }

        for (int s = (1 << k) - 1; s; s--) {
            for (int t = s; t; t = (t - 1) & s) {
                dp[s] |= dp[s ^ t] & good[t];
            }
        }
    }

    int ans = k;
    for (int i = 0; i < (1 << k); i++) {
        if (dp[i]) {
            ans = std::min(ans, k - __builtin_popcount(i));
        }
    }
    std::cout << ans << "\n";
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int t;
    std::cin >> t;

    while (t--) {
        solve();
    }

    return 0;
}
posted @ 2023-03-01 15:52  曹轩鸣  阅读(14)  评论(0编辑  收藏  举报