Save your tears for the day so far away
难难结论题,拼尽全力最终猜出。
先考虑一点显然的结论:\(k_i=1\),显然是随便填写的,因此考虑所有的 \(k_i > 1\) 的情况。
先考察样例,不难发现形成的是一个环的状物,因此我们不难得出这样的结论:如果只是一条链,则链的情况为:奇数长度的环,是 \(2len - 1\),否则为 \(2len\)。
然后我们再考察如果不是链的情况,不难证明任意的情况最后都可以转化为若干条链,或者是若干个环,然后上面再挂一些东西。假设我们是在环上的,我们不难发现,如果环上加上一个新的队伍,则这个环就一定每个点都可以产生两个贡献了。再考虑链,我们又能发现如果成链,则左右点一定是只参加一个比赛的队伍加上参加两个比赛的对于,然后我们对于这种情况进行考虑,发现依然是对的。
汇总一下,我们可以猜到结论:如果一个连通块是任意点都可以贡献 \(2\) 的,当且仅当其覆盖的比赛个数为偶数,或者参加的队伍数量多于覆盖的比赛个数,否则就要 \(-2\) 了。然后写个并查集维护下就行了。
思考链非常长的题目!
Code
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define File(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout)
#define LL long long
#define fi first
#define se second
const int N = 1e6 + 10;
int a[N][5];
int n,m;
int deg[N];
vector<int> vec[N];
int res[N];
void del(int num,int i){
int oth = 0;
if(a[num][0] == 1){
a[num][0] -- ;
a[num][1] = 0;
}
else{
if(a[num][1] == i) oth = a[num][2];
else oth = a[num][1];
a[num][2] = 0;
a[num][1] = oth;
a[num][0] -- ;
}
return ;
}
int sz[N],cnt[N],fa[N];
int find(int u){
if(u == fa[u]) return u;
return fa[u] = find(fa[u]);
}
void merge(int u,int v){
u = find(u); v = find(v);
if(u == v) return ;
fa[v] = u;
sz[u] += sz[v];
cnt[u] += cnt[v];
}
int main(){
IOS;
cin >> n >> m;
for(int i=1;i<=m;i++){
cin >> deg[i];
for(int j=1;j<=deg[i];j++){
int x;
cin >> x;
vec[i].push_back(x);
a[x][++a[x][0]] = i;
}
}
for(int i=1;i<=m;i++){
if(deg[i] == 0) res[i] = 0;
if(deg[i] == 1){
res[i] = 1;
int num = vec[i][0];
del(num,i);
}
}
for(int i=1;i<=m;i++)
sz[i] = 1,cnt[i] = 0,fa[i] = i;
for(int i=1;i<=n;i++){
if(a[i][0] == 0) continue;
if(a[i][0] == 1){
cnt[find(a[i][1])] ++ ;
}
if(a[i][0] == 2){
int u = find(a[i][1]);
int v = find(a[i][2]);
cnt[u] ++ ;
merge(u,v);
}
}
int ans = 0;
for(int i=1;i<=m;i++){
ans += res[i];
if(res[i] == 0 && i == find(i) && cnt[i] > 0){
if(cnt[i] > sz[i])
ans += sz[i] * 2;
else if(cnt[i] == sz[i] && sz[i] % 2 == 0)
ans += sz[i] * 2;
else
ans += sz[i] * 2 - 1;
}
}
cout << ans;
return 0;
}

浙公网安备 33010602011771号