题目链接
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]);
}//直接输出答案
}