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\)的拆分一定为如下形式:

\[n=xp+y(p+1)+z(p+2)\;\;\;\;\;\;\;\;x,y,z\ge1 \]

变换一下得到:

\[n=p(x+y+z)+y+2z \]

\(q=x+y+z\),则有:

\[n=pq+y+2z \]

且有\(y\ge 1\)\(z\ge1\)\(y+z\le q-1\)

考虑先枚举\(p\)\(q\),进一步枚举\(z\)\(z\in[1, q-2]\)):

\[n=pq+2+y\;\;\;\;\;\;\;\;z=1,y\in[1,q-2]\\ n=pq+4+y\;\;\;\;\;\;\;\;z=2,y\in[1,q-3]\\ n=pq+6+y\;\;\;\;\;\;\;\;z=3,y\in[1,q-4]\\ \vdots\\ n=pq+2(q-2)+y\;\;\;\;\;\;\;\;z=q-2,y\in[1,1] \]

则在枚举\(p,q\)\(z\)\(z\in[1, q-2]\))过程中,只需要令以下区间内的\(f(n)\)每次全部\(+1\)即可:

\[n\in [pq+3,pq+q]\;\;\;\;\;\;\;\;z=1\\ n\in[pq+5,pq+q+1]\;\;\;\;\;\;\;\;z=2\\ n\in[pq+7,pq+q+2]\;\;\;\;\;\;\;\;z=3\\ \vdots\\ n\in[pq+2q-3, pq+2q-3]\;\;\;\;\;\;\;\;z=q-2 \]

由于区间是连续的,可以用差分\(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;
}
posted @ 2020-08-05 11:09  ncu_supernova  阅读(92)  评论(0)    收藏  举报