总结
今天这把一开始三个人一起做博弈论签到题,想了一个小时都没想出来,队友1看1004过的人也挺多就写了一发,我大概看了看之后就让他交,结果wa了,然后我就开始出样例hack,hack到了一个点后他把代码改了改又交了,结果又wa,遂继续hack,过了一会又hack到了一个点,这次改完之后ac了,遂一起看1003,三人推了半天,讨论了好久,都没有想到什么很好的办法, 最后队友2开启自闭模式写了半个多小时一发ac了(说是没有用到之前讨论的方法),队友1也想到了博弈论签到题的做法ac了,三题过后,还剩两个小时,我就和队友2一起做1002,思考了好一会后,队友2给出了一种解法,于是我就开写,写好后交上去tle了,改了改又交了几发,均是tle,上了趟厕所后,我和队友2换了个脑子重新想这道题,我发现一开始的做法虽然总的来说复杂度正确,但是建了很多多余的边,于是我简化了一下我们的建图方法,最后结束前五分钟终于写好ac了,四题结束,排名300+。
题解:
1002 - Independent Feedback Vertex Set
题意,给你一个初始图 { 1 , 2 1,2 1,2}{ 2 , 3 2,3 2,3}{ 1 , 3 1,3 1,3} ,告诉你总共有 n n n 个点, 1 − 3 1-3 1−3 为初始点,之后每次添加的点都在原来边的基础上添加,例如有一个点 x x x 在原来边 { u , v u,v u,v} 的基础上添加,那么就将 x x x 向 u , v u,v u,v 各连一条边,以此类推构成一个 n n n 个点, 3 n − 3 3n-3 3n−3 条边的有向图,让你将这个图划分为两个点集,其中一个点集构成森林,另外一个点集构成独立集,森林和独立集的含义如下。
森林:无环的无向图。
独立集:图中的一组顶点,使得对于每两个顶点,没有连接两者的边。
问:独立集中的顶点权重总和最大为多少。
以下是几个样例的图
样例1
样例2
样例3
做法:
我们可以看到,这个图由若干个三元环组成,为了能够将图划分为两种点集,我们必须在每个三元环中恰好选定一个点作为独立集中的点,因为一个点都不选则会破坏森林约束,选至少两个则会破坏独立集的约束,同时对于一对有至少两个公共点的三元环,确定了答案包含其中一个的某个点之后另一个也随之确定了。因此我们可以知道,答案只可能有三种情况,分别为包含1/2/3的独立集,通过题目中所给图的点—边—点性质我们可以建立一个图,分别从1/2/3开始跑 b f s bfs bfs 即可得到这三个答案,取 m a x max max 后输出即可。
代码:
/*
author:wuzx
*/
#include<bits/stdc++.h>
#define ll long long
#define endl "\n"
#define P pair<int,int>
#define f first
#define s second
using namespace std;
const int inf = 0x3f3f3f3f;
int t;
int n,m,k;
struct hashfunc//pair哈希
{
template<typename T, typename U>
size_t operator() (const pair<T, U> &i) const
{
return hash<T>()(i.first) ^ hash<U>()(i.second);
}
};
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>t;
while(t--)
{
cin>>n;
vector<ll> w(n+1);
for(int i=1;i<=n;i++)
cin>>w[i];
vector<vector<int>> g(n+1);
unordered_map<P,int,hashfunc> mp;
auto add = [&](int u,int v)
{
g[u].emplace_back(v);
g[v].emplace_back(u);
};
auto tag = [&](int u,int v,int idx)
{
mp[{u,v}]=idx;
};
tag(1,2,3);
tag(2,3,1);
tag(1,3,2);
for(int i=4;i<=n;i++)
{
int u,v;
cin>>u>>v;
if(u>v)swap(u,v);
add(mp[{u,v}],i);
tag(u,v,i);
tag(u,i,v);
tag(v,i,u);
}
ll ans=0;
vector<int> vis(n+1,0);
for(int i=1;i<=n;i++)
{
if(!vis[i])
{
ll num=0;
queue<int> q;
q.emplace(i);
vis[i]=1;
while(!q.empty())
{
int now=q.front();
q.pop();
num+=w[now];
for(int x:g[now])
{
if(!vis[x])
{
vis[x]=1;
q.emplace(x);
}
}
}
ans=max(ans,num);
}
}
cout<<ans<<endl;
}
return 0;
}
1003 - Counting Stickmen
题意:
给你一棵树,问你在这个树中能够找到多少个火柴人,答案对 998244353 998244353 998244353 取模,它有一条长度为 1 1 1 的链作为头部,两条长度为 2 2 2 的链作为手臂,一条长度为 1 1 1 的链作为身体,两条长度为 1 1 1 的链作为腿。
做法:
因为这是一棵树,所以我们可以选择枚举火柴人身体的两个端点(距离为1的两个点),身体上部为 x x x ,下部(与脚连接)为 y y y ,这里先规定一个点 i i i 的入度为 i n [ i ] in[i] in[i] ,我们可以知道
以 $x-y $ 为身体的火柴人:
脚的情况为 l e g = ( i n [ y ] 2 ) leg= \binom{in[y]}{2} leg=(2in[y])
头的情况为 h e a d = i n [ y ] − 3 head=in[y]-3 head=in[y]−3
而手的情况较复杂,首先我们可以知道手的长度需要为 2 2 2,因此我们可以知道与 x x x 距离为 2 2 2 的点的个数为 $ {\textstyle \sum_{u\in neighbor(x)}} in[u]-in[x]$
我们还要考虑作为手的点不能与 y y y 点相连(不然就是脚了,因此我们需要减去 i n [ y ] − 1 in[y]-1 in[y]−1 ,在这些点中任取两个作为手,则方案数为
( ( ∑ u ∈ n e i g h b o r ( x ) i n [ u ] − i n [ x ] ) − ( i n [ y ] − 1 ) 2 ) \binom{{\textstyle (\sum_{u\in neighbor(x)}} in[u]-in[x])-(in[y]-1)}{2} (2(∑u∈neighbor(x)in[u]−in[x])−(in[y]−1))
我们还要去掉两个手臂相同的情况(即手的父节点为同一个点),方案数为 ∑ u ∈ n e i g h b o r ( x ) , u ≠ y ( i n [ u ] − 2 2 ) \sum_{u\in neighbor(x),u\neq y}\binom{in[u]-2}{2} ∑u∈neighbor(x),u=y(2in[u]−2)
因此脚的情况为 h a n d = ( ( ∑ u ∈ n e i g h b o r ( x ) i n [ u ] − i n [ x ] ) − ( i n [ y ] − 1 ) 2 ) − ∑ u ∈ n e i g h b o r ( x ) , u ≠ y ( i n [ u ] − 1 2 ) hand=\binom{{\textstyle (\sum_{u\in neighbor(x)}} in[u]-in[x])-(in[y]-1)}{2}-\sum_{u\in neighbor(x),u\neq y}\binom{in[u]-1}{2} hand=(2(∑u∈neighbor(x)in[u]−in[x])−(in[y]−1))−∑u∈neighbor(x),u=y(2in[u]−1)
那么身体为 x − y x-y x−y 时对答案的贡献为 ( l e g × h e a d × h a n d ) M O D 998244353 (leg\times head\times hand)\space MOD\space 998244353 (leg×head×hand) MOD 998244353
我们只需要在枚举 x − y x-y x−y 之前 O ( n ) O(n) O(n) 预处理每个点的 i n [ x ] ∑ u ∈ n e i g h b o r ( x ) , u ≠ y ( i n ) [ u ] − 1 ∑ u ∈ n e i g h b o r ( x ) , u ≠ y ( i n [ u ] − 2 2 ) in[x]\space\space \sum_{u\in neighbor(x),u\neq y}\binom in[u]-1 \space\space \sum_{u\in neighbor(x),u\neq y}\binom{in[u]-2}{2} in[x] ∑u∈neighbor(x),u=y(ni)[u]−1 ∑u∈neighbor(x),u=y(2in[u]−2) 以及组合数即可。
代码:
/*
author:wuzx
*/
#include<bits/stdc++.h>
#define ll long long
#define int long long
#define endl "\n"
#define P pair<int,int>
#define f first
#define s second
using namespace std;
const int inf = 0x3f3f3f3f;
int t;
int n,m,k;
const int mod =998244353;
const int maxn = 500010;
//先要init_c()
int ksm(int a,int b,int p=mod,int res=1)
{
for( ; b ; a=a*a%p, b>>=1) if(b&1) res=res*a%p;
return res;
}
int fac[maxn],inv[maxn];
void init_c(int n=maxn-2)
{
inv[0]=fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
inv[n]=ksm(fac[n],mod-2);
for(int i=n-1;i>=1;i--) inv[i]=inv[i+1]*(i+1)%mod;
}
int C(int n,int m)
{
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
init_c();
cin>>t;
while(t--)
{
cin>>n;
vector<vector<int>> g(n);
vector<int> in(n,0),son(n),hand(n,0);
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
u--;v--;
g[u].emplace_back(v);
g[v].emplace_back(u);
in[u]++;
in[v]++;
}
for(int i=0;i<n;i++)
{
for(int x:g[i])
{
son[i]+=in[x];
if(in[x]-1>=2)
hand[i]=(hand[i]+C(in[x]-1,2))%mod;
}
}
int ans=0;
for(int i=0;i<n;i++)
{
if(in[i]>3)
{
for(int x:g[i])
{
if(in[x]>2)
{
int leg=C(in[x]-1,2);
int han=(son[i]-in[i]-in[x]+1);
han=han>=2?C(han,2):0;
han=(han-hand[i]+C(in[x]-1,2)+mod*mod)%mod;
ans=(ans+((leg*han%mod)*(in[i]-3))%mod)%mod;
}
}
}
}
cout<<ans<<endl;
}
return 0;
}
1004 - Black Magic
题意:
给你四种积木以及每种积木的个数 E , L , R , B E,L,R,B E,L,R,B ,其中 E 代表积木的两边都是白色,L 代表积木做白右黑,R 代表左黑右白,B 代表两边都是黑色。要将所有积木都放到一排,其中对于任意两个相邻的块,如果左侧块的右侧和右侧块的左侧都涂成黑色,则将两侧粘贴在一起,使两个块合二为一。问:积木最后最少和最多能变成多少块?
做法:
对于最少的情况,我们尽量让较多的 L L L 和 R R R 匹配,最后将 B B B 放入任意一组 L , R L, R L,R 之中即可
而对于最多的情况,我们将所有的 L L L 放在最左边,再将所有的 R R R 放在最右边,再尽量用 E E E 将 B B B 隔开即可
代码:
/*
author:wuzx
*/
#include<bits/stdc++.h>
#define ll long long
#define int long long
#define endl "\n"
#define P pair<int,int>
#define f first
#define s second
using namespace std;
const int inf = 0x3f3f3f3f;
int t;
int n,m,k;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>t;
while(t--)
{
int e,l,r,b;
cin>>e>>l>>r>>b;
n=e+l+r+b;
int mi=min(l,r);
if(max(l,r)>0)
mi+=b;
else
mi+=max(0ll,b-1);
int ma=max(0ll,b-e-1);
cout<<n-mi<<" "<<n-ma<<endl;
}
return 0;
}
1008 - Triangle Game
题意:
给你三条边,边长分别为 a , b , c a,b,c a,b,c 。现 Kate 和 Emilico 二人做游戏,每轮需要令三角形的一边长度减去一正整数,使这个三角形退化的一方负。Kate 先手,双方均采用最优策略,问 Kate 是否会获胜。
做法:
若 ( a − 1 ) ⊕ ( b − 1 ) ⊕ ( c − 1 ) ≠ 0 (a-1) \oplus (b-1)\oplus (c-1)\neq 0 (a−1)⊕(b−1)⊕(c−1)=0 则先手必胜,反之则先手必败,证明略
代码:
/*
author:wuzx
*/
#include<bits/stdc++.h>
#define ll long long
#define int long long
#define endl "\n"
#define P pair<int,int>
#define f first
#define s second
using namespace std;
const int inf = 0x3f3f3f3f;
int t;
int n,m,k;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>t;
while(t--)
{
int a,b,c;
cin>>a>>b>>c;
if(((a-1)^(b-1)^(c-1))!=0)
cout<<"Win"<<endl;
else
cout<<"Lose"<<endl;
}
return 0;
}