abc_470 训练记录
很神奇的难度分布,C 读错题了,想了特别久。F 想明白一个关键点就很容易。
D
设 \(p\) 通过操作 \(2\) 变化一次就会变成 \(p'\)
观察到 \(p'\) 通过操作 \(2\) 变化一次又会变成 \(p\)
那么问题就很简单了。暴力交换 \(p\) 序列或者 \(p'\) 序列即可。
C
题意:
1 操作会将 \(p\) 位置的数字 \(+1\)
2 操作会将 所有大于 0 的数字 \(-1\)
每一步,动态维护当前所有数的异或和
其实 C 好像很简单,不知道为什么评那么难。
做法:
考虑用一个 set 维护当前每个大于 0 的数字,及其位置。
对于操作 1,如果有这个数字,找到这个数字,并删除后重新加入。
对于操作 2,暴力将所有位置 -1 ,如果等于 0,则从集合中删除。
对于操作 2,由于\(\sum a_i\) 的总和最多是 \(5\times 10^5\),因此被删的次数的总和也不会超过这个上限,直接暴力做复杂度是对的。
F
对问题建模,这 \(m\) 次操作就等价于是一个 \(n\) 点 \(m\) 边的图,对于同一连通块内的两点,可以任意交换。
题目本质在问你,通过无限次的交换,且最终操作次数是偶数,这样最终可能得到的字符串个数是多少个。
如果不考虑最终操作次数是偶数这一限制,对于每个连通块,我们记连通块大小是 \(cnt\),且对于第 \(i\) 个字符,它的出现次数是 \(ch_i\),每个连通块可以任意重排,这是一个多重集全排列计数的经典模型,方案数是 \(\frac{cnt!}{\prod_{i=0}^{25} ch_i!}\)。
情况 1 :如果一个集合中,每个字符都是互不相同的,那么经过奇数次可以得到的方案数和经过偶数次可以得到的方案数是一样的,都是 \(\frac{cnt!}{2}\)。
情况 2 :如果一个集合中,至少有两个位置字符是相同的,可以通过把两个相同字符移动到相邻边,然后交换一次,奇数偶数次可以相互转化!设这些连通块的贡献分别是\(g_x\)。
不妨记\(F_{x,0/1}\) 表示情况 1 中,第 \(x\) 个连通块,通过偶数/奇数次交换得到的方案数。这个东西我们之前已经处理好了。然后,对于 1 类情况,可以大力 dp ,设 \(dp_{x,0/1}\) 表示当前已经考虑到了前 \(x\) 个连通块,并且当前经过了偶数/奇数次交换的方案数。转移方程很显然:\(dp_{i,0}=(dp_{i-1,1}\times F_{i,1}+dp_{i-1,0}\times F_{i,0})\),奇数情况留给读者自行思考。
我在这里思考的时候犯了一个错误,我把情况 1,情况 2都直接存进 F 里面,去大力跑了这个 dp,这其实是错的。
认真思考一下情况 2 的贡献,其实是可以全部乘起来的,因为这些方案全部可以通过最后一次通过奇数变成偶数,所以奇偶贡献都可以存在,那贡献记作 \(sum\) 吧。如果存在情况 2 ,最终答案就是 \(sum\times (dp_{m,0}+dp_{m,1})\),如上述所述,如果不存在,那么我们必须求偶数次交换的贡献,答案是 \(dp_{m,0}\)。
/*
* ┏┓ ┏┓
* ┏┛┗━━━━━━━┛┗━━━┓
* ┃ ┃
* ┃ ━ ┃
* ┃ > < ┃
* ┃ ┃
* ┃... ⌒ ... ┃
* ┃ ┃
* ┗━┓ ┏━┛
* ┃ ┃ Code is far away from bug with the animal protecting
* ┃ ┃ 神兽保佑,代码无bug
* ┃ ┃
* ┃ ┃
* ┃ ┃
* ┃ ┃
* ┃ ┗━━━┓
* ┃ ┣┓
* ┃ ┏┛
* ┗┓┓┏━━━━━━━━┳┓┏┛
* ┃┫┫ ┃┫┫
* ┗┻┛ ┗┻┛
*/
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<cmath>
#include<bitset>
#include<cstring>
#include<queue>
#include<iomanip>
#define mt make_tuple
//cout << setprecision(3) ; //输出3位小数,3.142
#define pb push_back
#define fi first
#define se second
using namespace std;
using ll=long long;
typedef pair<int,int>pii;
typedef tuple<int,int,int>ti3;
const int mod=998244353;
const int N=1e6+10;
ll qpow(ll x,int y){
ll res=1;
while(y){
if(y&1)res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
ll f[N],invf[N];
ll inv(int x){
return qpow(x,mod-2);
}
void init(){
f[0]=1;
for(int i=1;i<N;i++)f[i]=f[i-1]*i%mod;
invf[N-1]=inv(f[N-1]);
for(int i=N-2;i>=0;i--)invf[i]=invf[i+1]*(i+1)%mod;
}
void Silverwolf(){
int n,m;
cin>>n>>m;
string s;
cin>>s;
s=" "+s;
vector<vector<int>>e(n+1);
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
e[u].pb(v);
e[v].pb(u);
}
vector<vector<ll>>F;//F[x][0] 表示x这个联通块 奇贡献,F[x][1] 表示偶贡献
vector<int>vis(n+1,0);
vector<int>ch(26,0);
int cnt=0;
auto dfs=[&](auto &&dfs,int u)->void{
ch[s[u]-'a']++;
vis[u]=1;
cnt++;
for(int v:e[u]){
if(vis[v])continue;
dfs(dfs,v);
}
};
ll tmp=1;
bool fl=false;
for(int i=1;i<=n;i++){
if(!vis[i]){
ch.assign(26,0);
cnt=0;
dfs(dfs,i);
if(cnt==1){
//不参与置换
continue;
}
bool eq=false;//有没有字符相同
for(int j=0;j<=25;j++){
if(ch[j]>1)eq=true;
}
ll res=f[cnt];
for(int j=0;j<=25;j++)res=res*invf[ch[j]]%mod;
vector<ll>g(2,0);
if(!eq){
g[0]=g[1]=(res*inv(2)%mod);
F.pb(g);
}else{
fl=true;
tmp=tmp*res%mod;
}
}
}
vector<ll>dp(2,0);
dp[0]=1;
for(int i=0;i<F.size();i++){
vector<ll>ndp(2,0);
ndp[0]=(dp[1]*F[i][1]+dp[0]*F[i][0])%mod;
ndp[1]=(dp[1]*F[i][0]+dp[0]*F[i][1])%mod;
dp=ndp;
}
ll ans=0;
ans=(ans+tmp*dp[0])%mod;
if(fl)ans=(ans+tmp*dp[1])%mod;
cout<<ans<<'\n';
//有一些位置为奇数
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
init();
// int T;cin>>T;while(T--)
Silverwolf();
//愿艾利欧三度为你窥见,令你的生命永不停歇,骇入永远成功,游戏永远胜利。
//游戏就只是为了游戏,仅此而已
//acm这种东西,三两下搞定就好啦
return 0;
}
浙公网安备 33010602011771号