2025 GPCPC/JSCPC补题

补题链接 参考视频

在这里补一些没做出来的题,还是看不懂的就投降了。

J. Puzzle Competition

题意:有向图,每个点有激活的能量要求(其中必有0要求的点),激活后的点向所有边发出1点能量,经过边权的时间后抵达。然后还有一个自动启动装置,到达他的启动时间后,他会把他相连的所有点启动。

当时想到了拓扑排序,想到了自动启动装置作为一个点(但是发送的能量应是INF),但是没想到基于边的拓扑排序。好歹补的时候想到了。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define double long double
#define endl '\n'
#define fff(x,y,z) for(int x=(y);x<=(z);x++)
#define rrr(x,y,z) for(int x=(y);x>=(z);x--)
using pii = pair<int,int>;
using ari = array<int,3>;
 const int MAX=1e5+2;
// const int MOD;
int N,M,K;
vector<pii> e[MAX];
struct Edge{
	int ti,val,idx;
	//传到时间 边值大小 目标点
	bool operator<(const Edge&other)const{
		if(ti==other.ti)return val>other.val;
		return ti<other.ti;
	}
	bool operator>(const Edge&other)const{
		if(ti==other.ti)return val<other.val;
		return ti>other.ti;
	}
};
priority_queue<Edge,vector<Edge>,greater<Edge>> pq;
void solve(){
	cin>>N>>M>>K;
	vector<int> a(N+1),ans(N+1,-1);
//	vector<bool> vis(N+1);
	fff(i,1,N)cin>>a[i];
	fff(i,1,K){
		int t,sc;cin>>t>>sc;
		fff(j,1,sc){
			int v;cin>>v;
			pq.push({t,LONG_MAX,v});
		}
	}
	fff(i,1,M){
		int u,v,w;cin>>u>>v>>w;
		e[u].emplace_back(make_pair(v,w));
	}
	fff(i,1,N){
		if(a[i]==0){
//			vis[i]=1;
			ans[i]=0;
			for(auto [v,w]:e[i]){
				pq.push({w,1,v});
			}
		}
	}
	while(pq.size()){
		auto [ti,val,cur]=pq.top();pq.pop();
		if(a[cur]<=0)continue;
		a[cur]-=val;
		if(a[cur]<=0){
			ans[cur]=ti;
			for(auto [v,w]:e[cur]){
				pq.push({ti+w,1,v});
			}
		}
	}
	fff(i,1,N)cout<<ans[i]<<' ';
}
signed main() {
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	// int times;cin>>times;
	// while(times--)
	solve();
	return 0;
}

H. Loose Subsequences

题意:本质不同子序列,但是多了个选出的字符距离大于K的限制。

考虑K==0时。此时就是本质不同子序列。对于第一次出现的字符,dp[i]=i+dp[1]+...+dp[i-1];否则对于last[str[i]]之前的,累加会与last[str[i]]重合,就不对了,只能累加last[str[i]]i-1之间的。

考虑K>0时。此时多了K的限制。对于第一次出现的字符,dp[i]=i+dp[1]+...+dp[i-K-1];对于last[str[i]]-K之前的,累加会与last[str[i]]算过的重合,只能累加last[str[i]]-Ki-1之间的。

前缀和优化即可。同时要小心加不了的情况,要注意比较端点大小、以及端点小于0的情况。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define double long double
#define endl '\n'
#define fff(x,y,z) for(int x=(y);x<=(z);x++)
#define rrr(x,y,z) for(int x=(y);x>=(z);x--)
using pii = pair<int,int>;
using ari = array<int,3>;
// const int MAX;
 const int MOD=998244353;
int N,M;
int las[27];
void solve(){
	string str;
	cin>>N>>M>>str;
	int len=str.length();
	str='*'+str;
	vector<int> dp(N+1),pre(dp);
	fill(las+1,las+1+26,0ll);
	int ans=0;
	for(int i=1;i<=len;i++){
		int cs=str[i]-'a'+1;
		if(las[cs]){
			int lb=max(0ll,las[cs]-M-1);
			int rb=max(0ll,i-M-1);
//			if(i-M-1>las[cs]-1){
			dp[i]=(pre[rb]-pre[lb]+MOD)%MOD;
//			}
		}else{
			dp[i]=1+pre[max(i-M-1,0ll)];
		}
		dp[i]%=MOD;
		pre[i]=pre[i-1]+dp[i];
		pre[i]%=MOD;
		las[cs]=i;
		ans+=dp[i];
		ans%=MOD;
//		cout<<dp[i]<<' ';
	}
//	cout<<endl;
	cout<<ans<<endl;
}
signed main() {
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	 int times;cin>>times;
	 while(times--)
	solve();
	return 0;
}

A. Matrix Game

题意:给你个01组成的矩阵,任意反转行与列(01互换),要你求所有a{i,j}==1A*i+B*j的和最大时的结果。

m很小,考虑枚举2^m种列的反转情况,然后考察行反转情况

定义cnt为一行的1的数量,W为一行中为一的列下标和。

i行不翻:Val = A*i*cnt+B*W

i行翻:Val' = A*i*(m-cnt)+B*(m*(m+1)/2-W)

则我们想要 Val' > Val 才翻。

整理不等式得到条件是 A*i*(m-2*cnt)>B*(2*W-m*(m+1)/2)。可以发现不等式只和行号i以及Wcnt有关。而后两者在特定的列翻转条件下是固定的,所以我们认为会有分界线分开行号的翻转。

不同的情况分别存储行号,找到分界线用二分。我找到后,直接各种情况取max了。

话说这个map不太好,卡到990ms多,建议换数组。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define double long double
#define endl '\n'
#define fff(x,y,z) for(int x=(y);x<=(z);x++)
#define rrr(x,y,z) for(int x=(y);x>=(z);x--)
using pii = pair<int,int>;
using ari = array<int,3>;
// const int MAX;
// const int MOD;
int N,M,A,B;
pii Cal(const string&str,const int state){
//	vector<bool> nstate;
	int cnt=0,W=0;
	for(int i=0;i<M;i++){
		int cur=str[i]-'0';
		if((state>>i)&1)cur=1-cur;
		if(cur){
			cnt++;
			W+=i+1;
		}
	}
	return{cnt,W};
}
void solve(){
	cin>>N>>M>>A>>B;
	map<string,vector<int>>mp;
	map<string,vector<int>>mpre;
	fff(i,1,N){
		string str;cin>>str;
		mp[str].emplace_back(i);
	}
	for(auto[str,temp]:mp){
		int pre=0;
		for(auto t:temp){
			pre+=t;
			mpre[str].emplace_back(pre);
		}
	}
	int ans=LONG_MIN;
	fff(state,0,(1<<M)-1){
		int sans=0;
		for(auto[str,temp]:mp){
			int part_ans=LONG_MIN;
			auto[cnt,W]=Cal(str,state);
			int len=temp.size();
			int Ap=A*(M-2*cnt),Bp=B*(2*W-M*(M+1)/2);
			int ll=-1,rr=len;
			if(Ap>0) while(ll+1!=rr){
				int mid=(ll+rr)/2;
				if(Ap*temp[mid]>Bp)rr=mid;
				else ll=mid;
			}else if(Ap<0) while(ll+1!=rr){
				int mid=(ll+rr)/2;
				if(Ap*temp[mid]>Bp)ll=mid;
				else rr=mid;
			}
			if(Ap==0||ll==len-1||rr==0){
				part_ans=max(part_ans,max(A*mpre[str][len-1]*cnt+len*B*W,A*mpre[str][len-1]*(M-cnt)+len*B*(M*(M+1)/2-W)));
				sans+=part_ans;
				continue;
			}
			part_ans=max(part_ans,A*mpre[str][ll]*cnt+B*W*rr+A*(mpre[str][len-1]-mpre[str][ll])*(M-cnt)+B*(M*(M+1)/2-W)*(len-rr));
			part_ans=max(part_ans,A*(mpre[str][len-1]-mpre[str][ll])*cnt+B*W*(len-rr)+A*mpre[str][ll]*(M-cnt)+B*(M*(M+1)/2-W)*rr);
			sans+=part_ans;
		}
		ans=max(sans,ans);
	}
	cout<<ans<<endl;
}
signed main() {
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	// int times;cin>>times;
	// while(times--)
	solve();
	return 0;
}

K. Typewriter

题意:给你个字符串,要你求每个前缀的最短的字符串ALB(长度大于0即可),满足这个前缀是ALBLR(LR意思是L反转)无限重复的前缀。

字符串好题。复习了一下马拉车和Z函数。

对于位置i有3种情况(1-base)(长度为len)。

  1. len>|ALBLR|,要求ALB为str[1~i]的周期、且ALBLRA也是回文。此时答案为|ALB|
  2. len>|ALB| && len<=|ALBLR|,此时答案为最长回文后缀的中心到开头的字符串长度
  3. ALB=str[1~i],此时答案为前缀长度

对于情况1,我们要认识到,随着i不断增长,字符串的周期只能不变或变长,不会再变短。

对于情况2,我们要认识到,随着i不断增长,回文后缀的中心是不会往前移动的。

这两种情况都可以分别维护一个位置,配合遍历时的i来维护答案。

情况1要判断,从开头到维护的位置后1位的字符串是不是回文串,又要看看从开头到维护的位置能否成为周期,这些可以分别用马拉车和Z函数。

情况二判断尾部回文,马拉车即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define double long double
#define endl '\n'
#define fff(x,y,z) for(int x=(y);x<=(z);x++)
#define rrr(x,y,z) for(int x=(y);x>=(z);x--)
using pii = pair<int,int>;
using ari = array<int,3>;
// const int MAX;
// const int MOD;
int N,M;

#define ra(i) m[2*i+1]/2-1

std::vector<int> manacher(std::string s) {
	std::string t = "#";
	for (auto c : s) {
		t += c;
		t += '#';
	}
	int n = t.size();
	std::vector<int> r(n);
	for (int i = 0, j = 0; i < n; i++) {
		if (2 * j - i >= 0 && j + r[j] > i) {
			r[i] = std::min(r[2 * j - i], j + r[j] - i);
		}
		while (i - r[i] >= 0 && i + r[i] < n && t[i - r[i]] == t[i + r[i]]) {
			r[i] += 1;
		}
		if (i + r[i] > j + r[j]) {
			j = i;
		}
	}
	return r;
}
std::vector<int> Z(std::string s) {
	int n = s.size();
	std::vector<int> z(n + 1);
	z[0] = n;
	for (int i = 1, j = 1; i < n; i++) {
		z[i] = std::max(0ll, std::min(j + z[j] - i, z[i - j]));
		while (i + z[i] < n && s[z[i]] == s[i + z[i]]) {
			z[i]++;
		}
		if (i + z[i] > j + z[j]) {
			j = i;
		}
	}
	return z;
}
void solve(){
	string str;cin>>str;
	int len=str.length();
	vector<int> ans1(len,LONG_MAX),ans2(ans1);
//	iota(ans3.begin(),ans3.end(),1);
	auto z=Z(str);
	auto m=manacher(str);
//	cout<<"Z:";
//	for(auto t:z)cout<<t<<' ';cout<<endl;
//	cout<<"M:";
//	for(auto t:m)cout<<t<<' ';cout<<endl;
//	fff(i,0,len-1){
//		int mi=2*i+1;
//		int r=m[mi]/2-1;
//		fff(j,i,i+r){
//			ans2[j]=min(ans2[j],i+1);
//		}
////		i+=r;
//	}
	int pos=0;
	fff(i,0,len-1){
		if(pos==0&&str[i]==str[0])ans1[i]=1;
		else while(pos<i){
			int pmid=(pos+1)/2;
//			if(pos==0)
			if((pos&1)&&pmid+ra(pmid)>=pos+1&&z[pos+1]>=i-pos){
//			[0~pos+1]得是奇数串 这个奇数串得是回文的 [0~pos]得是[0~i]的周期
//				cout<<"Y";
				ans1[i]=(pos+3)/2;
				break;
			}
			pos++;
		}
//		ans1[i]=(pos+3)/2;
	}
	int centre=0;
	fff(i,0,len-1){
		while(centre<i&&centre+ra(centre)<i)centre++;
		ans2[i]=centre+1;
	}
//	cout<<"ans1:";
//	for(auto t:ans1)cout<<t<<' ';cout<<endl;
//	cout<<"ans2:";
//	for(auto t:ans2)cout<<t<<' ';cout<<endl;
	int ans=0;
	fff(i,0,len-1){
		ans^=((i+1)*min(min(ans1[i],ans2[i]),i+1));
	}
	cout<<ans<<endl;
}
signed main() {
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	 int times;cin>>times;
	 while(times--)
	solve();
	return 0;
}
posted @ 2025-09-24 15:57  Treow  阅读(35)  评论(0)    收藏  举报