2022杭电多校第七场
Independent Feedback Vertex Set(思维)
题意
将一个无向图
G
G
G的顶点
V
V
V分成两个互补子集
V
I
V_I
VI和
V
F
V_F
VF,其中
V
I
V_I
VI是独立集,诱导子图
G
[
V
F
]
G[V_F]
G[VF]是森林。初始状态下,图
G
G
G是含有三个顶点的完全图,之后会添加若干个顶点,添加的顶点会向图
G
G
G中存在的一条边的两个顶点各连一条边。图
G
G
G的每个顶点都有一个权值,求
V
I
V_I
VI中顶点权值和的最大值。
森林:无环的无向图。
独立集:图中的一个顶点集,对于图中任意两个顶点,没有连接这两个顶点的边。
分析
图上每增加一个顶点,相当于增加了一个三元环,因此图
G
G
G是由若干个三元环构成。对于每一个三元环,至少要选择一个顶点作为独立集中的顶点,否则会破坏森林约束,选两个及以上的点会破坏独立集约束,于是每个三元环必须选择一个点作为独立集中的顶点。如果一个顶点确定选做独立集中的顶点,其他顶点选或不选的情况就确定了,分三种情况讨论即可。时间复杂度
O
(
n
)
O(n)
O(n)。
AC代码
typedef long long ll;
const int N=1e5+10;
struct node { int x,y; }p[N];
ll a[N],b[N];
int main()
{
int T;
cin>>T;
while(T--)
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=4;i<=n;i++) cin>>p[i].x>>p[i].y;
ll ans=0,sum;
b[1]=b[2]=0,b[3]=1;
sum=a[3];
for(int i=4;i<=n;i++)
{
int x=p[i].x,y=p[i].y;
if(b[x]||b[y]) b[i]=0;
else b[i]=1,sum+=a[i];
}
ans=max(ans,sum);
b[1]=b[3]=0,b[2]=1;
sum=a[2];
for(int i=4;i<=n;i++)
{
int x=p[i].x,y=p[i].y;
if(b[x]||b[y]) b[i]=0;
else b[i]=1,sum+=a[i];
}
ans=max(ans,sum);
b[2]=b[3]=0,b[1]=1;
sum=a[1];
for(int i=4;i<=n;i++)
{
int x=p[i].x,y=p[i].y;
if(b[x]||b[y]) b[i]=0;
else b[i]=1,sum+=a[i];
}
ans=max(ans,sum);
cout<<ans<<endl;
}
return 0;
}
Counting Stickmen(树形DP)
题意
给定一棵树,求树中火柴人的数量,其中火柴人的形状如下图红色结点所示。
分析
火柴人可以分为4个部分:头(2)、脖子(3)、手(4-6和9-10)、身体(5-7-8),其中只有脖子和其他三个部位都相连,因此从脖子入手,枚举可以作为脖子的位置。下面分析每个部位的特征,任何一个结点都可以作为头或者脖子,度数大于等于2的结点可以作为手,度数大于等于3的结点可以作为身体。对于一个火柴人,先确定脖子的位置,手有两个,而且两只手不能共用结点,身体只有一个,可以先确定身体的位置,之后确定手,两只手不能占用身体的位置,这就需要去重,具体可以参考下面的代码,最后确定头的位置。时间复杂度
O
(
n
)
O(n)
O(n)。
AC代码
typedef long long ll;
const int N=5e5+10;
const int mod=998244353;
vector<int> v[N];
ll d[N],body[N],hand[N];
ll ans;
int n;
ll C(ll x) { return x*(x-1)/2%mod; }
void dfs(int x,int p)
{
ll b=0,h=0;
ll res=0;
for(auto y:v[x])
{
b=(b+body[y])%mod;
h=(h+hand[y])%mod;
if(y==p) continue;
dfs(y,x);
}
if(d[x]<4) return ;
for(auto y:v[x])
{
res=(res+body[y]*((C(h-hand[y])-(b-body[y]))%mod)%mod*(d[x]-3)%mod)%mod;
}
res=(res%mod+mod)%mod;
ans=(ans+res)%mod;
}
int main()
{
int T;
cin>>T;
while(T--)
{
cin>>n;
for(int i=1;i<=n;i++) v[i].clear();
for(int i=1;i<=n;i++) d[i]=hand[i]=body[i]=0;
for(int i=1;i<n;i++)
{
int x,y;
cin>>x>>y;
v[x].push_back(y);
v[y].push_back(x);
d[x]++,d[y]++;
}
for(int i=1;i<=n;i++)
{
hand[i]=d[i]-1;
if(d[i]>2) body[i]=C(d[i]-1);
}
ans=0;
dfs(1,0);
cout<<ans<<endl;
}
return 0;
}
注意
运算过程中注意取模,中间结果可能爆long long
。
Black Magic(贪心)
题意
HoshiYo有
n
n
n个积木,每个积木的左右两边被涂成黑色或白色。HoshiYo将积木按照一定的顺序排成一排,但不旋转,然后释放黑魔法。对于任意两个相邻的积木,如果左边积木的右边和右边积木的左边都被涂成黑色,那么这两个积木就会被粘在一起,使两个积木变成一个。HoshiYo想知道释放黑魔法后他能得到的最小和最大数量的积木。
分析
对于连通块最多的情况,要让能够相连的方块对尽量少。可以发现,所有L可以全放在最左边,所有R可以全放在最右边,这样他们就不会和中间的方块相连,之后尽可能用E把B隔开即可。对于连通块最少的情况,要让能够相连的方块对尽可能多。可以先把所有B拼在一起,之后可以把一个L和一个R分为一组拼起来。注意如果有至少一组LR并且也有至少一块B的话,可以把一组LR拆开拼在B的两边,这样可以再减少一个连通块。时间复杂度
O
(
1
)
O(1)
O(1)。
AC代码
int main()
{
int T;
cin>>T;
while(T--)
{
int e,l,r,b;
cin>>e>>l>>r>>b;
int n=e+l+r+b;
if(b)
{
int ans=n;
ans-=b-1;
int z=max(0,min(l,r)-1);
ans-=z;
l-=z,r-=z;
if(l) ans--;
if(r) ans--;
cout<<ans<<" ";
}
else
{
cout<<n-min(l,r)<<" ";
}
cout<<n-max(0,b-e-1)<<endl;
}
return 0;
}
Sumire(数位DP)
题意
计算
∑
i
=
l
r
f
k
(
i
,
B
,
d
)
\sum_{i=l}^{r}f^k(i,B,d)
∑i=lrfk(i,B,d),
f
(
x
,
B
,
d
)
f(x,B,d)
f(x,B,d)表示数字
d
d
d 出现在
x
x
x(忽略前导0)的
B
B
B 进制表示下的次数。在本题中有
0
0
=
0
0^0=0
00=0.
分析
AC代码
const int mod=1e9+7;
typedef long long ll;
ll k,B,d,l,r;
using mint=Modint<mod>; //mint:Modint
mint dp[70][2][2][70];
bool vis[70][2][2][70];
int a[105];
int pos;
mint dfs(int cur,int limit,bool zero,int cnt)
{
if(cur==pos&&!cnt) return 1;
if(cur==pos) return 0;
if(cnt<0) return 0;
if(vis[cur][limit][zero][cnt]) return dp[cur][limit][zero][cnt];
vis[cur][limit][zero][cnt]=1;
dp[cur][limit][zero][cnt]=0;
int z=limit?a[cur]:B-1;
int used=0,v=0;
int cost=(v==d);
if(zero&&(d==0)) cost=0;
dp[cur][limit][zero][cnt]+=dfs(cur+1,limit&&(a[cur]==v),zero&&(v==0),cnt-cost);
used++;
if(v!=d&&d<=z)
{
used++; v=d;
dp[cur][limit][zero][cnt]+=dfs(cur+1,limit&&(a[cur]==v),zero&&(v==0),cnt-1);
}
if(v!=z)
{
used++; v=z;
dp[cur][limit][zero][cnt]+=dfs(cur+1,limit&&(a[cur]==v),zero&&(v==0),cnt);
}
v=B;
dp[cur][limit][zero][cnt]+=dfs(cur+1,limit&&(a[cur]==v),zero&&(v==0),cnt)*max(0,z-used+1);
return dp[cur][limit][zero][cnt];
}
mint solve(ll n)
{
pos=0;
while(n) a[pos++]=n%B,n/=B;
memset(vis,0,sizeof(vis));
reverse(a,a+pos);
mint ans=0;
for(int i=1;i<=pos;i++)
{
ans+=dfs(0,1,1,i)*(mint(i)^k);
}
if(!d) return ans+1;
return ans;
}
int main()
{
int T;
cin>>T;
while(T--)
{
cin>>k>>B>>d>>l>>r;
cout<<(int)(solve(r)-solve(l-1))<<endl;
}
return 0;
}
Triangle Game(博弈论)
题意
Kate和Emilico正在玩一个游戏,有3个整数a,b,c,保证存在一个边长分别为a,b,c的非退化三角形。游戏过程如下:玩家轮流在这3个整数中的一个上减去某个正整数。如果在某位玩家的操作之后不存在边长为a,b,c的非退化三角形,该玩家就输了。Kate先手,如果两人都以最优策略进行游戏,Kate会赢吗?
分析
(1,1,1)是必败态,哪位玩家能到达这个状态就必胜,否则必败,这就相当于石子个数
−
1
-1
−1的Nim游戏,判断
(
a
−
1
)
⊕
(
b
−
1
)
⊕
(
c
−
1
)
(a-1) \oplus (b-1) \oplus (c-1)
(a−1)⊕(b−1)⊕(c−1)是否为
0
0
0即可。但是游戏要求每次操作后三角形是非退化的,而Nim游戏没有这个限制,下面证明这个结论仍然是正确的。
AC代码
int main()
{
int T;
cin>>T;
while(T--)
{
int a,b,c;
cin>>a>>b>>c;
if((a-1)^(b-1)^(c-1)) cout<<"Win"<<endl;
else cout<<"Lose"<<endl;
}
return 0;
}