2020牛客暑期多校训练营(第八场)
| 题号 | A | B | C | D | E | F | G | H | I | J | K |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 赛中 | 💭 | 🎈 | 🎈 | ||||||||
| 赛后 | ✅ | ✅ | ✅ | ✅ | ✅ |
A - All-Star Game
题意
\(n\) 个球员,\(m\) 个粉丝,初始第 \(i\) 个球员有 \(k_i\) 个粉丝,分别为 \(a_{i1},k_{i2},...\),若粉丝 \(x\) 喜欢 \({a,b}\),粉丝 \(y\) 喜欢 \(a\),则粉丝 \(y\) 也会喜欢 \(b\),接下来有 q 次询问,每次询问 \(x,y\) ,改变粉丝 x 和球员 y 的关系,即若 \(x\) 不是 \(y\) 的粉丝,那么此刻起,\(x\) 是 \(y\) 的粉丝,反之此刻起 \(x\) 不是 \(y\) 的粉丝,对每次询问输出至少要选多少个球员使得所有粉丝都喜欢他们。
思路
其实是线段树上分治的经典题了。
对于某个时刻,用并查集维护一下连通块的数量,该时刻的答案等于有连粉丝的球员连通块的数量,若连通块的粉丝数量之和不等于 m, 则无解。
对于每一对关系,有一个存在时间段,以时间为下标建线段树,将关系维护到线段树中,最后遍历线段树的每个节点,将对应时间段的关系维护到并查集,达到叶子节点时就输出答案,回溯时要按栈序撤销并这些关系的影响,线段树套可撤销并查集即可。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 4e5 + 10;
#define pii pair<int,int>
#define fir first
#define sec second
int n, m, q;
int p[maxn], num[maxn], dep[maxn];
map<pii,int> st;
int find(int x) {
return p[x] == x ? x : find(p[x]);
}
struct seg_tree {
#define lson rt << 1, l, mid
#define rson rt << 1 | 1, mid + 1, r
vector<pii> opt[maxn << 2];
int ans[maxn << 2], cnt[maxn << 2]; //ans 维护的是连通块的个数, cnt 维护的是粉丝的个数
void build(int rt,int l,int r) {
opt[rt].clear(); ans[rt] = cnt[rt] = 0;
if (l == r) return ;
int mid = l + r >> 1;
build(lson); build(rson);
}
void add(int L,int R,pii edg,int rt,int l,int r) {
if (L > R) return ;
if (L <= l && r <= R) {
opt[rt].push_back(edg);
return ;
}
int mid = l + r >> 1;
if (L <= mid) add(L,R,edg,lson);
if (mid + 1 <= R) add(L,R,edg,rson);
}
void dfs(int rt,int l,int r) {
vector<pair<int,pii> > sta;
for (int i = 0; i < opt[rt].size(); i++) {
int x = opt[rt][i].fir, y = opt[rt][i].sec;
int fx = find(x), fy = find(y);
if (fx != fy) {
if (dep[fx] >= dep[fy]) swap(fx,fy), swap(x,y);
if (fx > n) { //fx 是粉丝
cnt[rt] += 1; //总粉丝数量
if (num[fy] == 0) ans[rt] += 1; //fy 是球员,有球员有新粉丝,这个球员要选
} else {
if (num[fx] > 0 && num[fy] > 0) //两个球员只要选一个
ans[rt] -= 1;
}
sta.push_back(make_pair(fx,pii(fy,dep[fy])));
num[fy] += num[fx];
p[fx] = fy;
if (dep[fx] == dep[fy]) dep[fy] = dep[fx] + 1;
}
}
cnt[rt << 1] = cnt[rt << 1 | 1] = cnt[rt];
ans[rt << 1] = ans[rt << 1 | 1] = ans[rt];
if (l != r) {
int mid = l + r >> 1;
dfs(lson); dfs(rson);
} else {
if (cnt[rt] != m) puts("-1");
else printf("%d\n",ans[rt]);
}
for (int i = sta.size() - 1; i >= 0; i--) {
int fx = sta[i].fir, fy = sta[i].sec.fir;
p[fx] = fx; dep[fy] = sta[i].sec.sec;
num[fy] -= num[fx];
}
sta.clear();
}
} tree;
int main() {
scanf("%d%d%d",&n,&m,&q);
st.clear(); tree.build(1,1,q);
for (int i = 1; i <= n + m; i++)
dep[i] = 1, p[i] = i, num[i] = (i > n ? 1 : 0);
for (int i = 1, k, x; i <= n; i++) {
scanf("%d",&k);
for (int j = 1; j <= k; j++) {
scanf("%d",&x);
st[pii(i,x + n)] = 1;
}
}
for (int i = 1; i <= q; i++) {
int x, y; scanf("%d%d",&x,&y);
x += n;
if (st[pii(y,x)] == 0)
st[pii(y,x)] = i;
else {
//printf("***%d %d %d %d\n",y,x,st[pii(y,x)],i - 1);
tree.add(st[pii(y,x)],i - 1,pii(y,x),1,1,q);
st[pii(y,x)] = 0;
}
}
for (auto it : st)
if (it.second != 0) {
//printf("***%d %d %d %d\n",it.first.first,it.first.second,it.second,q);
tree.add(it.second,q,it.first,1,1,q);
}
tree.dfs(1,1,q);
return 0;
}
B - Bon Voyage
题意
思路
代码
C - Cinema
题意
思路
代码
D - Disgusting Relationship
题意
思路
代码
E - Enigmatic Partition
题意
定义\(n\)的一个合法拆分为\(n = a_1 + a_2 + \cdots + a_m\),且满足下列条件:
- \(\forall 1 \le i \le m\),\(a_i\)为整数,\(1 \le a_i \le n\),且
- \(\forall 1 \le i \lt m\),\(a_i \le a_{i+1} \le a_i + 1\),且
- \(a_m = a_1 + 2\)
令\(f(n)\)为\(n\)的合法拆分的数量,要求计算\(\sum_{i=l}^r f(i)\)。(\(1\le T \le 10^4,1\le l \le r \le 10^5\))
思路
考虑预处理出所有\(f(n)\),计算前缀和后\(O(1)\)查询结果。
由题意可知\(n\)的拆分一定为如下形式:
变换一下得到:
设\(q=x+y+z\),则有:
且有\(y\ge 1\),\(z\ge1\),\(y+z\le q-1\)
考虑先枚举\(p\)和\(q\),进一步枚举\(z\)(\(z\in[1, q-2]\)):
则在枚举\(p,q\)和\(z\)(\(z\in[1, q-2]\))过程中,只需要令以下区间内的\(f(n)\)每次全部\(+1\)即可:
由于区间是连续的,可以用差分\(O(1)\)处理,但这样预处理时间复杂度为\(O(n^2\log n)\),仍然会TLE。
发现进行枚举\(z\)进行一次差分时,
-
需要\(+1\)的位置(区间左边界):\(pq+3,\;pq+5,\;pq+7,\;\cdots,\;pq+2q-3\)
-
需要\(-1\)的位置(区间右边界\(+1\)):\(pq+q+1,\;pq+q+2,\;,pq+q+3,\;\cdots,\;pq+2q-2\)
于是可以对需要\(+1\)位置进行第二次的隔项差分(注意隔项差分区间\(+x\)时,需要\(-x\)的位置应为区间右边界\(+2\)),需要\(-1\)的位置进行第二次的邻项差分,最后还原完相加即可,预处理时间复杂度\(O(n\log n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f;
const int maxn = 5e5 + 10;
LL a[maxn], b[maxn], sum[maxn];
void preprocess()
{
for(int p = 1; p <= 100000; p++)
{
for(int q = 3; p * q <= 100000; q++)
{
a[p * q + 3] += 1;
a[p * q + 2 * q - 1] -= 1;
b[p * q + q + 1] += -1;
b[p * q + 2 * q - 1] -= -1;
}
}
for(int i = 3; i <= 100000; i++)
a[i] += a[i-2];
for(int i = 2; i <= 100000; i++)
b[i] += b[i-1];
for(int i = 2; i <= 100000; i++)
{
a[i] += a[i-1];
b[i] += b[i-1];
}
for(int i = 2; i <= 100000; i++)
sum[i] = sum[i-1] + a[i] + b[i];
}
int main()
{
preprocess();
int T, kase = 0;;
scanf("%d", &T);
while(T--)
{
int l, r;
scanf("%d %d", &l, &r);
printf("Case #%d: %lld\n", ++kase, sum[r] - sum[l-1]);
}
return 0;
}
F - Factorio
题意
思路
代码
G - Game SET
题意
每张牌上有4种属性,每个属性有三个值:
- number of shapes (one, two, or three)
- shape (diamond, squiggle, or oval)
- shading (solid, striped, or open)
- and color (red, green, or purple)
若三张牌中每个属性要么都相同,要么都不相同,则称这三张牌构成一个set。*表示可以对应属性中的任意一个值。
现给出\(n(≤256)\)张牌,问能否在这\(n\)张牌中找到三张牌构成一个set,能的话输出这三张牌的编号,不能的话输出-1。
思路
官方题解中说只需要考虑21张牌就够了,如果这21张牌中不能找到一个set,那么答案就是-1,否则就肯定能找到,输出三张牌的编号,这样就可以\(n^3\)枚举了。
我们的思路:
从\(n\)张牌中任选两张牌,考虑可匹配的第三张牌属性组合,对于每种属性组合查询这n张牌中是否存在(且不是前面选定的两张牌)即可。
对牌的属性组合hash一下,用map标记。
时间复杂度\(O(4^4*n^2)\)
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 300;
struct node{
int a, b, c, d;
node(int a = 0, int b = 0, int c = 0, int d = 0):a(a),b(b),c(c),d(d){}
};
node no, nos[maxn];
map<string, int> mp;
map<int, int> p;
string s[5], ss;
set<int> a, b, c, d;
int t, n, x, y, z;
void pre()
{
mp["one"] = 1; mp["two"] = 2; mp["three"] = 3; mp["*"] = 4;
mp["diamond"] = 1; mp["squiggle"] = 2; mp["oval"] = 3;
mp["solid"] = 1; mp["striped"] = 2; mp["open"] = 3;
mp["red"] = 1; mp["green"] = 2; mp["purple"] = 3;
}
int get(int a, int b, int c, int d)
{
return a+b*10+c*100+d*1000;
}
void change(int tp, int i, int j, set<int> &v)
{
int x, y;
if(tp==1) x = nos[i].a, y = nos[j].a;
if(tp==2) x = nos[i].b, y = nos[j].b;
if(tp==3) x = nos[i].c, y = nos[j].c;
if(tp==4) x = nos[i].d, y = nos[j].d;
v.insert(4);
if(x==y)
{
if(x!=4) v.insert(x);//A A
else for(int k = 1; k <= 3; k++) v.insert(k);//* *
}
else
{
if(x==4 || y==4) for(int k = 1; k <= 3; k++) v.insert(k);// (* A) or (A *)
if(x!=4 && y!=4) for(int k = 1; k <= 3; k++) if(x!=k && y!=k) v.insert(k);// A B
}
}
bool solve(int &x, int &y, int &z)
{
for(int i = 1; i <= n; i++)
{
for(int j = i+1; j <= n; j++)
{
//printf("%d %d\n", i, j);
a.clear(); b.clear(); c.clear(); d.clear();
x = i, y = j;
change(1, i, j, a);
change(2, i, j, b);
change(3, i, j, c);
change(4, i, j, d);
for(auto A : a)
for(auto B : b)
for(auto C : c)
for(auto D : d)
{
//printf("%d %d %d %d\n", A, B, C, D);
int v = get(A, B, C, D);
if(p.count(v) && p[v]!= i && p[v]!=j) { z = p[v]; return true; }
}
}
}
return false;
}
int main()
{
//freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
ios::sync_with_stdio(false); cin.tie(0);
pre();
cin >> t;
for(int T = 1; T <= t; T++)
{
cin >> n;
x = y = z = 0;
p.clear();
for(int i = 1; i <= n; i++)
{
cin >> ss;
int l = -1, r = -1, num = 0;
for(int k = 0; k < ss.length(); k++)
{
if(ss[k]=='[') l = k;
if(ss[k]==']') r = k;
if(l!=-1 && r!=-1)
{
s[num++] = ss.substr(l+1, r-l-1);
l = r = -1;
}
}
//cout << s[0] << " " << s[1] << " " << s[2] << " " << s[3] << endl;
no = node{mp[s[0]], mp[s[1]], mp[s[2]], mp[s[3]]};
//cout << mp[s[0]] << mp[s[1]] << mp[s[2]] << mp[s[3]] << " " << i << endl;
p[get(mp[s[0]], mp[s[1]], mp[s[2]], mp[s[3]])] = i;
nos[i] = no;
}
if(solve(x, y, z)) printf("Case #%d: %d %d %d\n", T, x, y, z);
else printf("Case #%d: -1\n", T);
}
return 0;
}
H - Hard String Problem
题意
思路
代码
I - Interesting Computer Game
题意
n个二元组,从每个二元组中选一个元素,使得最后不重复集合的 \(size\) 最大,问最大的 \(size\) 的多少
思路
如果以每个二元组的元素和对应所在二元组连边,则一条边对应一个选择方案,要使得不相交的边尽可能多,这是裸的二分图最大匹配。
二分图匹配很难通过,如果每个二元组的两个元素连边,则一条边对应一个集合,顶点对应元素,一条边至少能选一个元素,如果连通块是一棵树,找个点做根,每条边都选儿子,则只有根不能选,这个连通块的贡献为树的 \(size - 1\)。进一步思考根什么时候能选,发现在这个无向图上,只要有环,就可以把根选上。
做法为无向图连通块判断是否有环,可以用并查集,但实际只需要判断每个连通块的点数和边数的大小关系。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 7;
int a[maxn], b[maxn];
int vis[maxn], t[maxn], tot, p;
int f[maxn], num[maxn];
int find(int x) {
return x == f[x] ? x : f[x] = find(f[x]);
}
int Hash(int x) {
return lower_bound(t + 1,t + p + 1,x) - t;
}
int main() {
int tt, n, sz = 0;
scanf("%d",&tt);
while (tt--) {
scanf("%d",&n);
tot = p = 0;
for (int i = 1; i <= n; i++) {
int x; scanf("%d%d",&a[i],&b[i]);
t[++tot] = a[i];
t[++tot] = b[i];
}
sort(t + 1,t + tot + 1);
p = unique(t + 1,t + tot + 1) - t - 1;
for (int i = 1; i <= p; i++)
num[i] = 1, f[i] = i, vis[i] = 0;
for (int i = 1; i <= n; i++) {
a[i] = Hash(a[i]); b[i] = Hash(b[i]);
int fa = find(a[i]), fb = find(b[i]);
if (fa != fb) {
vis[fb] += vis[fa];
num[fb] += num[fa];
f[fa] = fb;
} else {
vis[fa]++;
}
}
int ans = 0;
for (int i = 1; i <= p; i++) {
if (find(i) == i) {
if (vis[i]) ans += num[i];
else ans += num[i] - 1;
}
}
printf("Case #%d: %d\n",++sz,ans);
}
return 0;
}
J - Jumping Points
题意
思路
代码
k - Kabaleo Lite
题意
\(n(≤{10}^5)\)种菜,每种菜有成本\(a_i\)元,数量\(b_i\)元。现在要给客人上菜,为每个客人所上的菜必须是从第一种菜开始的连续的几个菜,且每种菜只给该客人上一次,至少给该客人上一道菜。
问最多能给多少个客人上菜,在此基础上求能获得的最大收益。
思路
很明显,最多能给\(b_1\)个客人上菜。
对于第\(i\)种菜,如果\(\sum_{k=1}^{i} a_k>a_1\),那么上前\(i\)种菜能获得比第一种菜更大的收益,统计满足条件的这些“\(i\)”及其对应的收益。以收益为第一关键字降序排列,“\(i\)”为第二关键字降序排列,即先考虑能获得收益最大的位置,同收益下优先考虑靠后的菜,每次从当前选择的菜的位置往前找,寻找下一个可以上的菜,统计答案。
注意:答案会爆longlong,要用__int128
时间复杂度O(nlogn)
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
struct node{
ll sum;
int idx, cnt;
friend bool operator < (node a, node b)
{
return a.sum==b.sum?a.idx>b.idx:a.sum>b.sum;
}
}no;
vector<node> c;
int a[maxn], b[maxn];
int t, n, mi;
ll sum;
void print(__int128 x)
{
if (!x) return ;
if (x < 0) putchar('-'),x = -x;
print(x / 10);
putchar(x % 10 + '0');
}
int main()
{
//freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
scanf("%d", &t);
for(int T = 1; T <= t; T++)
{
scanf("%d", &n);
c.clear();
sum = 0; mi = INT_MAX;
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
for(int i = 1; i <= n; i++) scanf("%d", &b[i]);
for(int i = 1; i <= n; i++)
{
sum += 1ll*a[i];
mi = min(mi, b[i]);
no.sum = sum, no.cnt = mi, no.idx = i;
if(i==1) c.push_back(no);
else
{
if(sum>a[1]) c.push_back(no);
}
}
sort(c.begin(), c.end());
//for(auto x: c) printf("%lld %d %d\n", x.sum, x.cnt, x.idx);
int preidx = INT_MAX, num = b[1], sumcnt = 0;
__int128 ans = 0;
for(auto x: c)
{
if(x.idx<preidx && sumcnt<x.cnt && num>0)
{
x.cnt -= sumcnt;
ans += min(num, x.cnt)*(__int128)x.sum;
preidx = x.idx;
sumcnt += min(num, x.cnt);
num -= min(num, x.cnt);
}
}
printf("Case #%d: %d ", T, b[1]);
print(ans); puts("");
}
return 0;
}

浙公网安备 33010602011771号