2025.06.02__GDCPC(H,J)
H题题目链接

题目大意
给定一个小写字母串 S ,问有多少个本质不同的非空 k 松散子序列(从 S 中不改变顺序的取若干个元素,子序列中相邻的元素在S中的下标相差至少 k )。
思路
这题很明显要使用dp来解题,我们可以用 dp[ i ] 表示在前 i 个字母中以第 i 个字母为子序列最后字母的本质不同子序列个数,用 occ [ i ] 表示 ('A' + i)这个字母上次出现的位置下标。
因为 k 松散子序列中每一个字母下标要至少相隔 k ,计算每一个 dp[ i ] 时要往前找相隔 k 的状态,为了防止越界,我们可以让 S 中 i 下标的字母对应到 dp[ i + k ] 。
分情况讨论:(1)如果字母('A' + i)在前 i - 1 个字母中没有出现过,那么 dp[ i ] 就可以从之前的所有状态转移过来,同时别忘了,它自己就可以单独形成一个本质不同的子序列,所以要加1,即 dp[ i + k ]=\(\sum_{j=0}^{i-1}\) dp[ j ] + 1 。
(2)如果字母('A' + i)在前 i - 1 个字母中有出现过,也就是occ[ i ]有记录,那么我们计算时不能计算 0 ~ occ[ i ] - 1 这一段,因为在上次的计算中就已经算过前面了,所以状态转移方程就是:dp[ i + k ]=\(\sum_{j=occ[i]}^{i-1}\) dp[ j ] 。
然后我们可以用 sum 数组做一下前缀和,便于我们快速算出dp。
代码
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
using namespace std;
const int N=1e6+10,M=1010,mod=998244353;
const int INF=0x3f3f3f3f;
const int inf=0x3f3f3f3f3f3f3f3f;
int n,m,k;
void solve(){
cin>>n>>k;
vector<int> occ(30,0);
vector<int> dp(n+k+10,0);
vector<int> sum(n+k+10,0);
string s;
cin>>s;
s=" "+s;
for(int i=1;i<=n;i++){
int x=s[i]-'a';
if(occ[x]){
dp[i+k]=((sum[i-1]-sum[occ[x]-1])%mod+mod)%mod;
}else{
dp[i+k]=((sum[i-1]-sum[0]+1)%mod+mod)%mod;
}
occ[x]=i;
sum[i+k]=(dp[i+k]+sum[i+k-1])%mod;
}
cout<<sum[n+k]<<endl;
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int _=1;
cin>>_;
while(_--) solve();
return 0;
}
赛后总结
赛时有看过这题,但我想不出要怎么做,遂放弃,去做J题, 交给 lsy 在想这题,可惜他也没有想出来。
J题题目链接

题目大意
给定一个图,每个点有一个解锁能量值 \(a_i\) ,点的初始能量为 0 ,当能量达到 \(a_i\) 时点解锁,每一个点解锁时会给与其相连的点传递 1 点能量。同时有一些强制刷新器,刷新器会在 t 时刻启动,将它管辖的点解锁。问每一个点能不能解锁,在什么时候最先解锁。
思路
这题思路其实很清晰,很有模拟的感觉。我们可以把一个点接收到能量与解锁这两种变化看成事件,把它放进一个优先队列,按时间顺序处理,优先处理传递能量的事件。因为一个点有两种解锁的途径:接收能量和强制刷新器解锁,解锁能量有可能比强制解锁更早。
代码
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
using namespace std;
const int N=1e5+10,M=2020,mod=1e9+7;
const int INF=0x3f3f3f3f;
const int inf=0x3f3f3f3f3f3f3f3f;
int n,m,k;
int a[N],cnt[N],ans[N],tt[N];
bool st[N];
vector<int> nodes[N];
vector<vector<PII>> g(N);
struct Event {
int time;
int type; // 0 = 能量到达,1 = 强制解锁
int node;
bool operator <(const Event& u) const {
if(time!=u.time) return time>u.time;
return type>u.type;
}
};
void bfs(){
priority_queue<Event> heap;
for(int i=1;i<=n;i++){
if(a[i]==0) heap.push({0,1,i});
}
for(int i=0;i<k;i++){
for(auto& u:nodes[i]){
heap.push({tt[i],1,u});
}
}
while(!heap.empty()){
auto ev=heap.top();heap.pop();
int t=ev.time,type=ev.type,u=ev.node;
if(st[u]) continue;
if(type){
st[u]=true;
ans[u]=t;
for(auto& edge:g[u]){
int v=edge.fi,w=edge.se;
heap.push({t+w,0,v});
}
}else{
cnt[u]++;
if(cnt[u]==a[u]){
st[u]=true;
ans[u]=t;
for(auto& edge:g[u]){
int v=edge.fi,w=edge.se;
heap.push({t+w,0,v});
}
}
}
}
}
void solve(){
memset(ans,-1,sizeof ans);
cin>>n>>m>>k;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=0;i<k;i++){
int sc,x;
cin>>tt[i]>>sc;
while(sc--){
cin>>x;
nodes[i].pb(x);
}
}
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
g[u].pb({v,w});
}
bfs();
for(int i=1;i<=n;i++){
cout<<ans[i]<<(i==n?endl:' ');
}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
赛后总结
比赛后期一直在想这题,我那时候是把已经解锁的点放进优先队列里,然后去传递能量。如果优先队列没有点了,就使用强制刷新器。但这么写不好写,而且也无法保证正确性,寄了。

浙公网安备 33010602011771号