2020牛客暑期多校训练营(第八场)

5 篇文章 0 订阅
4 篇文章 0 订阅

题目链接
A - All-Star Game
题意: 有n个运动员与m个球迷,现给你n个运动员各自的球迷编号,现在问你若要m名球迷都看比赛,至少需要多少名运动员上场?
满足球迷看比赛的条件:
(1) 该球迷喜欢的运动员有上场
(2) 球迷i与球迷j都有相同的喜欢球员,则球迷j喜欢运动员k,则球迷i也喜欢运动员k
然后有q次操作,每次操作撤销或建立x与y的关系,并输出每次操作后需要的运动员的数量
思路: 其实该问题简化到
给你一张图,有两个操作:
(1)删去图中原有的一条边
(2)加上图中没的一条边
询问图中联通块的个数。
其实这是一个经典问题,线段树分治问题,建立一个时间轴,每次询问操作按顺序就是一个叶节点,然后为每条边建立生存时间的起点,终点即可。最后用线段树查询,用可撤销并查集联通块维护即可

另一种做法:LCT(目前还不会)

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define lc (rt << 1)
#define rc ((rt << 1) | 1)
typedef long long LL;
typedef pair<int, int> pii;
const int maxn = 5e5 + 50;
int n, m, q;

vector<int> vec[maxn << 2];
void Update(int le, int ri, int L, int R, int id, int rt){
	if(L <= le && ri <= R){
		vec[rt].push_back(id);
		return ;
	}
	int mid = (le + ri) >> 1;
	if(L <= mid) Update(le, mid, L, R, id, lc);
	if(R > mid) Update(mid + 1, ri, L, R, id, rc);
}
map<pii, int> mp;
struct Edge
{
	int u, v;
} edge[maxn * 10];
int tot = 0;
int le[maxn * 10], ri[maxn * 10];

int ans = 0, sum = 0;
struct qnode
{
	int fau, fav, rku, rkv, sum, ans;
} stk[maxn * 10];
int top;
int fa[maxn], Rank[maxn];
int Find(int x){
	if(x == fa[x]) return x;
	return Find(fa[x]);
}
int unit(int x, int y){
	top++;
	x = Find(x), y = Find(y);
	stk[top].fau = x, stk[top].fav = y;
	stk[top].rku = Rank[x], stk[top].rkv = Rank[y];
	stk[top].sum = sum, stk[top].ans = ans;
	if(x == y) return 0;
	if(x <= n && y > n) {
		sum++;
		if(Rank[x] == 1) ans++;
	}
	else {
		if(Rank[x] != 1 && Rank[y] != 1){
			ans--; 
		}
	}
	if(Rank[x] < Rank[y]) swap(x, y);
	if(Rank[x] == Rank[y]) Rank[x]++;
	fa[y] = x;
	return 1;
}

void stkpop(){ 
	fa[stk[top].fau] = stk[top].fau;
	fa[stk[top].fav] = stk[top].fav;
	Rank[stk[top].fau] = stk[top].rku;
	Rank[stk[top].fav] = stk[top].rkv;
	sum = stk[top].sum, ans = stk[top].ans;
	top--;	
}
void Query(int le, int ri, int rt){
	int len = vec[rt].size();
	for(int i = 0; i < len; i++){
		int id = vec[rt][i];
		int u = edge[id].u, v = edge[id].v;
		unit(u, v);
	}
	if(le == ri){
		if(sum != m) printf("-1\n");
		else printf("%d\n", ans);
	} else {
		int mid = (le + ri) >> 1;
		Query(le, mid, lc);
		Query(mid + 1, ri ,rc);
	}
	while(len--){
		stkpop();
	}
}
int main(int argc, char const *argv[])
{
	scanf("%d%d%d", &n, &m, &q);
	for(int i = 1; i <= n + m; i++) {
		Rank[i] = 1;
		fa[i] = i;
	}
	for(int i = 1; i <= n; i++){
		int k;
		scanf("%d", &k);
		while(k--){
			int u = i;
			int v;
			scanf("%d", &v);
			v += n;
			if(!mp.count({u, v})) {
				mp[{u, v}] = ++tot;
				edge[tot] = {u, v};
			}
			le[tot] = 1; // 记录起点
		}
	}
	for(int i = 1; i <= q; i++){
		int u, v;
		scanf("%d%d", &v, &u);
		v += n;
		if(!mp.count({u, v})) {
			mp[{u, v}] = ++tot;
			edge[tot] = {u, v};
		}
		int id = mp[{u, v}];
		if(le[id] == 0) le[id] = i; // 如果这条边未记录过,记录左端点
		else { // 否则在线段树上记录该区间
			ri[id] = i - 1; 
			if(i != 1){
				Update(1, q, le[id], ri[id], id, 1);
			}
			le[id] = ri[id] = 0;
		}
	}
	for(int i = 1; i <= tot; i++){
		if(le[i]){
			ri[i] = q;
			Update(1, q, le[i], ri[i], i, 1);
		}
	}
	Query(1, q, 1);
    return 0;
}

E - Enigmatic Partition
题意: f(n)为满足以下条件的方案数
(1) a 1 + … … + a m = n a_1+……+a_m=n a1++am=n
(2) a i < = a i + 1 < = a i + 1 a_i<=a_{i+1}<=a_i+1 ai<=ai+1<=ai+1
(3) a m = a 1 + 2 a_m=a_1+2 am=a1+2
思路: 他人详细题解

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+100;
ll f[MAXN<<2],pre[MAXN<<1];
int main()
{
    for(int m=3;m<MAXN;m++)
        for(int a=m;a<MAXN;a+=m)
            f[a+3]++,f[a+m+1]--,f[a+m+2]--,f[a+2*m]++;//在上述位置直接标记
    for(int i=3;i<MAXN;i++)
        f[i]+=f[i-2];//把隔位的差分搞回来
    for(int i=2;i<MAXN;i++)
        f[i]+=f[i-1];//再拆一层套娃
    pre[1]=f[1];
    for(int i=2;i<MAXN;i++)
        pre[i]=pre[i-1]+f[i];//前缀和快速求和
    int t,l,r;
    scanf("%d",&t);
    for(int _=1;_<=t;_++){
        scanf("%d%d",&l,&r);
        printf("Case #%d: %lld\n",_,pre[r]-pre[l-1]);
    }//直接输出答案
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值