https://codeforces.com/gym/104417
B. Building Company
题意
有一家建筑公司,一开始你有 g g g 种员工。第 i i i 种员工编号为 t i t_i t i ,有 u i u_i u i 个。一共有 n n n 项任务,第 i i i 项任务有 m i m_i m i 个指标,第 j j j 个指标需要编号为 a i , j a_{i,j} a i , j 的员工至少有 b i , j b_{i,j} b i , j 个。完成第 i i i 项任务有 k i k_i k i 个奖励,第 j j j 个奖励为编号 c i , j c_{i,j} c i , j 的员工 d i , j d_{i,j} d i , j 个。每项任务只能完成一次,完成任务不消耗员工,只是需要员工数量达到要求。现在可以任意安排完成任务的顺序,问最多可以完成多少个任务。
题解
首先,我们有最朴素的做法:先看看一开始有哪些任务可以做,然后加上这些任务的奖励,看看又有哪些任务可以做了,依此类推,直到任务全都做完了或者没有可以做的任务了。但这样显然会超时。
我们考虑在此基础上优化:每当一个任务完成后,它给出奖励名额只针对了一些编号。我们可以只检查包含这些编号且未被完成的任务。但是,每次重新检查一个任务的开销同样很大,所以,我们可以对每个任务维护它还有多少个指标没有完成。同时,对于每个编号,我们只保存那些没有还没有满足的任务,按需要的人数插入一个小根堆。每当一个任务完成后,我们对每个奖励,检查对应编号的堆顶是否能被满足,若可以,则对应任务指标数减一,若为 0 0 0 ,则加入队列。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <bits/stdc++.h> using namespace std;typedef pair<int ,int > pii;typedef long long ll;const int N=1e5 +5 ;int m[N]; pii a[N]; vector<pii> b[N]; unordered_map<int ,priority_queue<pii,vector<pii>,greater<pii>>> mp; unordered_map<int ,ll> have; queue<int > q;void add (int t,int u) { ll &val=have[t]; val+=u; priority_queue<pii,vector<pii>,greater<pii>> &pq=mp[t]; while (!pq.empty ()) { pii p=pq.top (); if (p.first>val) break ; pq.pop (); if (!(--m[p.second])) q.push (p.second); } }int main () { ios::sync_with_stdio (false ); cin.tie (nullptr ); int g;cin>>g; for (int i=1 ;i<=g;i++) cin>>a[i].first>>a[i].second; int n;cin>>n; for (int i=1 ;i<=n;i++) { cin>>m[i]; for (int j=1 ;j<=m[i];j++) { int x,y;cin>>x>>y; mp[x].push ({y,i}); } int k;cin>>k; for (int j=1 ;j<=k;j++) { int x,y;cin>>x>>y; b[i].push_back ({x,y}); } } for (int i=1 ;i<=n;i++) if (!m[i]) q.push (i); for (int i=1 ;i<=g;i++) add (a[i].first,a[i].second); int ans=0 ; while (!q.empty ()) { int u=q.front ();q.pop (); ans++; for (auto i:b[u]) add (i.first,i.second); } cout<<ans<<'\n' ; return 0 ; }
J. Not Another Path Query Problem
题意
给出一张 n n n 个点 m m m 条边的无向图。对于一条 u u u 到 v v v 的路径,定义路径的权值为该条路径上所有边的边权按位与的结果。现给出一个阈值 V V V 。给出 q q q 次询问,每次询问 u u u 到 v v v 是否存在一条路径的权值大于等于阈值。
题解
考虑大于等于阈值的数,一定有一段相同的公共前缀,然后下一位阈值为 0 0 0 ,该数为 1 1 1 (也可能不存在这样的位),后面的位置没有限制。这样的限制至多有 log V \log V log V 种。对于每一种限制,我们保留符合该限制的所有的边,然后判断一下每个点属于哪个连通块。如果点 u u u 和点 v v v 在某个限制下处于同一个连通块内,那么它们间就存在符合条件的路径。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include <bits/stdc++.h> using namespace std;typedef long long ll;const int N=1e5 +5 ;struct edge { int v; ll w; }; vector<edge> g[N]; pair<int ,int > qry[5 *N];bool ans[5 *N];int n,m,q,f[N]; ll V;void bfs (int s,ll x) { queue<int > q; q.push (s);f[s]=s; while (!q.empty ()) { int u=q.front ();q.pop (); for (auto i:g[u]) { int v=i.v;ll w=i.w; if (!f[v]&&(w&x)==x) { q.push (v); f[v]=s; } } } }void solve (ll x) { memset (f,0 ,sizeof (f)); for (int i=1 ;i<=n;i++) if (!f[i]) bfs (i,x); for (int i=1 ;i<=q;i++) ans[i]|=(f[qry[i].first]==f[qry[i].second]); }int main () { ios::sync_with_stdio (false ); cin.tie (nullptr ); cin>>n>>m>>q>>V; for (int i=1 ;i<=m;i++) { int u,v;ll w;cin>>u>>v>>w; g[u].push_back ({v,w}),g[v].push_back ({u,w}); } for (int i=1 ;i<=q;i++) cin>>qry[i].first>>qry[i].second; if (!V) solve (0 ); else { for (ll i=V;i<(1ll <<60 );i+=i&(-i)) solve (i); } for (int i=1 ;i<=q;i++) cout<<(ans[i]?"Yes" :"No" )<<'\n' ; return 0 ; }