CSP-S 2025 赛前记录
[ABC304Ex] Constrained Topological Sort
给一个 \(n\) 个点 \(m\) 条边的无向图,和 \(n\) 个区间 \(L_i,R_i\),问是否存在一个 \(n\) 的合法排列,如果存在输出任意一组。
一个排列合法,要求 \(\forall (s,t),p_s < p_t\) 且 \(\forall i,L_i \le p_i \le R_i\)。
\(1 \le n \le 2 \cdot 10^5\) 且 \(0 \le m \le 4 \cdot 10^5\),3s。
若 \(m=0\),弱智贪心(从左往右扫即可)。
考虑加上边的限制,我们考虑给每个点的 \(l,r\) 进行修改,使得贪心得到的答案满足拓扑序。
对于有向边 \((x,y)\),为了优先 \(x\),则 \(l_y = \max(l_y, l_x + 1)\),同时如果 \(l_x\) 被其他选了,则还需要优先 \(x\),则 \(r_x = \min(r_x, r_y - 1)\),这样贪心的结果必定是满足拓扑序的,那正确性呢?
正确性:若\([l_x,r_x]\) 与 \([l_y,r_y]\) 的交初始不满,则显然正确,否则,交集大小减一,任然正确。
注意:可能在修改 \(l_y\) 之前 \(l_x\) 需要进行修改,所以需要在拓扑排序上进行修改( \(r\) 就在反图上拓扑排序)
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using PII = pair<int, int>;
const int MAXN = 4e5 + 3;
struct Node{
  int l, r, id;
}a[MAXN];
int n, m, ans[MAXN];
vector<PII> q[MAXN];
int d[MAXN], _d[MAXN];
vector<int> _eg[MAXN], eg[MAXN];
void No(){
  cout << "No";
  exit(0);
}
int main(){
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n >> m;
  for(int i = 1, U, V; i <= m; i++){
    cin >> U >> V;
    eg[U].push_back(V), d[V]++;
    _eg[V].push_back(U), _d[U]++;
  }
  for(int i = 1; i <= n; i++){
    cin >> a[i].l >> a[i].r, a[i].id = i;
  }
  vector<int> stk;
  for(int i = 1; i <= n; i++){
    if(!d[i]) stk.push_back(i);
  }
  while(!stk.empty()){
    int i = stk.back();
    stk.pop_back();
    for(int nxt : eg[i]){
      d[nxt]--, a[nxt].l = max(a[nxt].l, a[i].l + 1);
      if(!d[nxt]) stk.push_back(nxt); 
    }
  } 
  for(int i = 1; i <= n; i++){
    if(d[i]) No(); 
  }
  stk.clear();
  for(int i = 1; i <= n; i++){
    if(!_d[i]) stk.push_back(i);
  }
  while(!stk.empty()){
    int i = stk.back();
    stk.pop_back();
    for(int nxt : _eg[i]){
      _d[nxt]--, a[nxt].r = min(a[nxt].r, a[i].r - 1);
      if(!_d[nxt]) stk.push_back(nxt); 
    }
  } 
  for(int i = 1; i <= n; i++){
    if(a[i].l > n || a[i].l < 1 || a[i].r < a[i].l) No();
    q[a[i].l].push_back({a[i].r, i});
  }
  set<int> st;
  for(int i = n; i >= 1; i--){
    sort(q[i].begin(), q[i].end());
    st.insert(i);
    for(PII x : q[i]){
      auto it = st.upper_bound(x.first);
      if(it == st.begin()){
        No();
      }else{
        ans[x.second] = *prev(it);
        st.erase(prev(it));
      }
    }
  }
  cout << "Yes\n";
  for(int i = 1; i <= n; i++) cout << ans[i] << " ";
  return 0;
}
luogu - P9755 [CSP-S 2023] 种树
森林的地图有 \(n\) 片地块,其中 \(1\) 号地块连接森林的入口。
共有 \(n-1\) 条道路连接这些地块,使得每片地块都能通过道路互相到达。
你每天可以选择一个未种树且与某个已种树的地块直接邻接(即通过单条道路相连)的地块,种一棵高度为 \(0\) 米的树。
在第 \(x\) 天,\(i\) 号地块上的树会长高 \(\max(b_i + x \times c_i, 1)\) 米。注意这里的 \(x\) 是从整个任务的第一天,而非种下这棵树的第一天开始计算。
你想知道:最少需要多少天能够完成你的任务?
\(1 \le n \le 10^5\)
二分,算出每个点最晚种树的时间,然后和上面 [ABC304Ex] Constrained Topological Sort 类似。
附:这题得开 int128。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using i128 = __int128_t;
const LL zero = 0;
const int MAXN = 1e5 + 3;
inline LL read(){
  LL x = 0, f = 1; char ch = getchar();
  while(ch < '0' || ch > '9') f = (ch == '-' ? -1 : f), ch = getchar();
  while(ch >= '0' && ch <= '9') x = (x<<1) + (x<<3) + (ch^48), ch = getchar();
return x * f; }
void write(LL x){ if(x>9) write(x/10); putchar(x%10+'0'); }
struct Node{
	LL a, b, c;
}v[MAXN];
int n, dayl[MAXN];
vector<int> eg[MAXN];
i128 ssuumm(LL L, LL R){ 
	if(L > R) return 0ll;
	return (R + L) * (R - L + 1) / 2;
}
void dfs(int x, int dad){
  for(int nxt : eg[x]){
    if(nxt == dad) continue;
    dfs(nxt, x), dayl[x] = min(dayl[nxt] - 1, dayl[x]);
  }
}
bool as(int mxday){
	for(int i = 1; i <= n; i++){
		if(v[i].c >= 0){
			int l = 1, r = mxday;
			while(l < r){
				int mid = (l + r + 1) >> 1;
				if(i128(v[i].b) * (mxday - mid + 1) + i128(v[i].c) * ssuumm(mid, mxday) >= v[i].a){
					l = mid;
				}else r = mid - 1;
			}
			if(i128(v[i].b) * (mxday - l + 1) + i128(v[i].c) * ssuumm(l, mxday) < v[i].a){
				return 0;
			}
			dayl[i] = l;
		}else{
			int l = 1, r = mxday;
			while(l < r){
				int mid = (l + r + 1) >> 1;
				int _l = (v[i].b - 1) / (-v[i].c);
				_l = (_l > mxday ? mxday : _l);
				if(i128(v[i].b) * max(0, _l - mid + 1) + i128(v[i].c) * ssuumm(mid, _l) + max(0, mxday - max(_l, mid - 1)) >= v[i].a){
					l = mid;
				}else r = mid - 1;
			}
			int _l = (v[i].b - 1) / (-v[i].c); 
			_l = (_l > mxday ? mxday : _l);
			if(i128(v[i].b) * max(0, _l - l + 1) + i128(v[i].c) * ssuumm(l, _l) + max(0, mxday - max(_l, l - 1)) < v[i].a){
				return 0;
			}
			dayl[i] = l;
		}
	}
	dfs(1, 0);
  for(int i = 1; i <= n; i++){
    if(dayl[i] <= 0) return 0;
  }
  vector<int> vt;
  for(int i = 1; i <= n; i++) vt.push_back(dayl[i]);
  sort(vt.begin(), vt.end());
  for(int i = 1; i <= n; i++){
    if(vt[i-1] < i) return 0;
  }
  return 1;
}
int main(){
	//freopen("tree3.in", "r", stdin);
	n = read();
	for(int i = 1; i <= n; i++){
	  v[i].a = read(), v[i].b = read(), v[i].c = read();
	}
	for(int i = 1, U, V; i < n; i++){
	  U = read(), V = read();
		eg[U].push_back(V), eg[V].push_back(U);
	}
	int l = 1, r = 1e9;
	while(l < r){
		LL mid = (l + r) >> 1;
		if(as(mid)){
			r = mid;
		}else{
			l = mid + 1;
		}
	}
	write(l);
	return 0;
}
luogu - P8819 [CSP-S 2022] 星战
有一个 \(n\) 个点 \(m\) 条有向边的图,每条边有可用和不可用两个状态。有 \(q\) 个操作:
- 操作 \(\tt1\):让一条可用的边变得不可用
 - 操作 \(\tt2\):让一个点的所有入边中,可用的变得不可用
 - 操作 \(\tt3\):让一条不可用边变得重新可用
 - 操作 \(\tt4\):让一个点的所有入边中,不可用的变得可用
 每次操作之后,如果同时满足
- 条件 \(\tt1\):从每个点开始都可以无限的沿着可用边(有向)走下去。
 - 条件 \(\tt2\):每个点只有一条出边可用
 就输出
YES,否则输出NO。
先看最后的要求,只要每个点的入度都为一即可。
每个点随机权值,维护所有出边的 \(v\) 的权值和即可。
点击查看代码
/*
就是要判断这个图是一个环,然后向外扩散的图,共 n 条边。
然后可以转化成:每个点的出度为 1。
可以判断每个点的入度,若入度和为 n,则合法。
但这显然是不行的,所以可以随机化每个点的点权,再来判断,减小错误率。
*/
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int MAXN = 5e5 + 3;
mt19937_64 rnd(time(0));
int n, m, Q;
LL w[MAXN];  // 点权
LL in[MAXN]; // 现在的入度点权和
LL prein[MAXN]; // 初始的入度点权和
int main(){
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n >> m;
  LL presum = 0, sum = 0;
  for(int i = 1; i <= n; i++) w[i] = rnd() % int(1e11) + 1ll, presum += w[i];
  for(int i = 1, U, V; i <= m; i++){
    cin >> U >> V;
    in[V] += w[U], sum += w[U];
    prein[V] = in[V];
  }
  cin >> Q;
  for(int _Q = 1, op, U, V; _Q <= Q; _Q++){
    cin >> op >> U;
    if(op == 1){
      cin >> V;
      in[V] -= w[U], sum -= w[U];
    }else if(op == 2){
      sum -= in[U], in[U] = 0;
    }else if(op == 3){
      cin >> V;
      in[V] += w[U], sum += w[U];
    }else if(op == 4){
      sum += prein[U] - in[U], in[U] = prein[U];
    }else cout << "1145141919810\n";
    cout << (sum == presum ? "YES" : "NO") << "\n";
  }
  return 0;
}
luogu - P7913 [CSP-S 2021] 廊桥分配
见原题。
注意这题确定了左右两边分配的廊桥数量后,答案就是固定的了,因为要求了“先到先得”,模拟即可。
哎,为什么这么唐诗的题,我一直以为是神一样的存在呢。
点击查看代码
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <set>
using namespace std;
const int MAXM = 1e5 + 10;
struct qwe{
    int l, r;
}a[MAXM], b[MAXM];
struct asd{
    int r, id; // 结束时间,廊桥编号. lm<?
    bool operator <(asd i) const {
        return r < i.r;
    }
};
int n, m1, m2, ans = 0;
int sumc[MAXM], sumd[MAXM];
multiset<int> c1, d1; // 可以用的              (存编号)
multiset<asd> c2, d2; // 目前不可以用的        (存结束时间和廊桥编号)
bool cmp(qwe i, qwe j){
    if(i.l == j.l){
        return i.r < j.r;
    }
    return i.l < j.l;
}
int as(){
    for(int i = 1; i <= n; i++){ c1.insert(i); }
    for(int i = 1; i <= n; i++){ d1.insert(i); }
    for(int i = 1; i <= m1; i++){
        while(!c2.empty() && asd(*c2.begin()).r <= a[i].l){
            c1.insert(asd(*c2.begin()).id), c2.erase(c2.begin());
        }
        if(!c1.empty()){
            sumc[*c1.begin()]++;
            c2.insert({a[i].r, *c1.begin()}), c1.erase(c1.begin());
        }
    }
    for(int i = 1; i <= m2; i++){
        while(!d2.empty() && asd(*d2.begin()).r <= b[i].l){
            d1.insert(asd(*d2.begin()).id), d2.erase(d2.begin());
        }
        if(!d1.empty()){
            sumd[*d1.begin()]++;
            d2.insert({b[i].r, *d1.begin()}), d1.erase(d1.begin());
        }
    }
    for(int i = 1; i <= n; i++){ sumc[i] += sumc[i - 1]; }
    for(int i = 1; i <= n; i++){ sumd[i] += sumd[i - 1]; }
    for(int i = 0; i <= n; i++){ ans = max(ans, sumc[i] + sumd[n - i]); }
    return ans;
}
int main(){
    cin >> n >> m1 >> m2;
    for(int i = 1; i <= m1; i++){
        cin >> a[i].l >> a[i].r;
    }
    for(int i = 1; i <= m2; i++){
        cin >> b[i].l >> b[i].r;
    }
    sort(a + 1, a + 1 + m1, cmp), sort(b + 1, b + 1 + m2, cmp);
    cout << as();
    return 0;
}
                    
                
                
            
        
浙公网安备 33010602011771号