2025.06.02__GDCPC(H,J)

H题题目链接

image

题目大意

给定一个小写字母串 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题题目链接

image

题目大意

给定一个图,每个点有一个解锁能量值 \(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; 
} 

赛后总结

比赛后期一直在想这题,我那时候是把已经解锁的点放进优先队列里,然后去传递能量。如果优先队列没有点了,就使用强制刷新器。但这么写不好写,而且也无法保证正确性,寄了。

posted @ 2025-06-04 15:23  _hu  阅读(83)  评论(0)    收藏  举报