2025多校冲刺 CSP 模拟赛 6(CSP-S模拟34)

2025多校冲刺CSP模拟赛6

A. 最长不下降子序列 (sequence)

小 W 有一个长度为 \(n\) 的序列 \(a_1,a_2 \dots a_n\),且 \(a_i\) 的取值只可能为 \(1\)\(2\)
现在,你可以任意选择该序列的一个区间进行翻转操作,但你只能翻转一次。
小 W 希望执行操作之后,整个序列的最长不下降子序列长度最大。请你求出这个最大值。

答案一定为 一段 \(1 +\) (一段 \(2 +\) 一段 \(1\))(为翻转的序列) \(+\) 一段 \(2\) 的形式。

直接从后往前 dp 即可。甚至不需要把我代码中的 dp 数组建出,改为序列中 \(1\) 的个数的前缀和,只用建 dp2 数组即可。甚至 dp2 也可以滚成两个变量。

Upd:可以设 \(dp[i]\) 为考虑到前 \(i\) 段,最长合法序列长度。显然 \(i \in [1,4]\)。直接枚举 \(1\)\(n\) 转移即可。时间 \(O(n)\),空间 \(O(1)\)

Code:

#include<bits/stdc++.h>
#define int long long

using namespace std;

const int Size=(1<<20)+1;
char buf[Size],*p1=buf,*p2=buf;
char buffer[Size];
int op1=-1;
const int op2=Size-1;
#define getchar()                                                              \
(tt == ss && (tt=(ss=In)+fread(In, 1, 1 << 20, stdin), ss == tt)     \
	? EOF                                                                 \
	: *ss++)
char In[1<<20],*ss=In,*tt=In;
inline int read()
{
	int x=0,c=getchar(),f=0;
	for(;c>'9'||c<'0';f=c=='-',c=getchar());
	for(;c>='0'&&c<='9';c=getchar())
		x=(x<<1)+(x<<3)+(c^48);
	return f?-x:x;
}
inline void write(int x)
{
	if(x<0) x=-x,putchar('-');
	if(x>9)  write(x/10);
	putchar(x%10+'0');
}

// #ifndef ONLINE_JUDGE
// #define ONLINE_JUDGE
// #endif

int n;
int a[1<<20];
int dp[2][3][1<<20];
int dp2[3][1<<20];

void dodp(int id)
{
	for(int i=1;i<=n;i++)
	{
		dp[id][1][i]=dp[id][1][i-1]+(a[i]==1);
		dp[id][2][i]=max(dp[id][2][i-1],dp[id][1][i-1])+(a[i]==2);
	}
}

void dodp2()
{
	int sum=0;
	for(int i=n;i>=1;i--)
	{
		sum+=a[i]==2;
		dp2[1][i]=max(dp2[1][i+1],sum)+(a[i]==1);
		dp2[2][i]=max(dp2[2][i+1],dp2[1][i+1])+(a[i]==2);
	}
}

signed main()
{
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	dodp(0);
	dodp2();

	int ans=0;
	for(int i=0;i<=n;i++)
	{
		int nw=dp[0][1][i]+dp2[2][i+1];
		ans=max(ans,dp[0][1][i]+dp2[2][i+1]);
	}
	cout<<ans<<"\n";
	return 0;
}

B. 美食节 (food)

哥们设 \(lim_i = \lfloor \frac{j+1}{2} \rfloor\)\(j\) 为当前还没有到过的位置总数,当前是第 \(i\) 次去摊位。设第 \(k\) 类摊位一共有 \(t[k]\) 个。

显然当初始时 $\max{t[i]} > lim_n $ 时无解。否则一定有解。

当选第 \(x\) 次去的摊位时,如果不存在 \(\max\{t[i]\} = lim_x\) 时,则选一个当前位置最小的且与上一个选的摊位不同类的摊位。

如果 \(\max\{t[i]\} = lim_x\) ,则必须选第 \(i\) 类摊位,满足 \(\max\{t[j]\}=t[i]\)。发现直接选就行,一定不会与上一个选的产生矛盾。因为 \(x\) 变化 \(2\)\(lim\) 才会减小 \(1\)

哥们先用 vector 存下每类摊位的出现位置(由小到大)。

为实现快速寻找最小的那个位置,哥们直接用一个 set 存 每类摊位没有被访问过的 位置最小的 摊位的位置。每次先判断 set 中的第一个元素所对应的摊位类别是否和上一个选的摊位类别相同,若相同则 set 中的下一个元素就是答案。若不同则这个元素就是答案。然后 set 里删掉答案,再插入答案所对应类别在 vector 里的下一个位置。发现这样可以轻松维护最小位置。

为快速判断是否存在元素达到极限,哥们使用 multiset 或 priority_queue(大根堆)存一个 pair 表示(当然你可以直接压缩入一个 long long 里):摊位个数,摊位类别。每次操作直接取第一个元素,判断是否达到极限。操作完记得更新 multiset 或 priority_queue。当然 priority_queue 需要懒惰删除。

我使用 priority_queue。

样例过不去,发现是有漏洞的。

思考发现问题:

  • 可能两类摊位同时达到极限,而哥们判不了这两个哪个最先出现。

这样的话,哥们修正使大根堆每个元素依次存 摊位个数,该类别最先没有被访问过的摊位位置,摊位类别。发现摊位类别可以由摊位位置得到,所以扔掉摊位类别即可。还是可以用 pair 存。

发现还是不对。

思考发现问题:

  • 当 当前选第 \(x\) 去,\(lim_x = lim_{x-1}\) 时,不选极限元素也行。

你直接判断一下即可。当然你也可以修正 \(lim\) 定义。

Code:

#include<bits/stdc++.h>
#define int long long

using namespace std;

const int Size=(1<<20)+1;
char buf[Size],*p1=buf,*p2=buf;
char buffer[Size];
int op1=-1;
const int op2=Size-1;
#define getchar()                                                              \
(tt == ss && (tt=(ss=In)+fread(In, 1, 1 << 20, stdin), ss == tt)     \
	? EOF                                                                 \
	: *ss++)
char In[1<<20],*ss=In,*tt=In;
inline int read()
{
	int x=0,c=getchar(),f=0;
	for(;c>'9'||c<'0';f=c=='-',c=getchar());
	for(;c>='0'&&c<='9';c=getchar())
		x=(x<<1)+(x<<3)+(c^48);
	return f?-x:x;
}
inline void write(int x)
{
	if(x<0) x=-x,putchar('-');
	if(x>9)  write(x/10);
	putchar(x%10+'0');
}

// #ifndef ONLINE_JUDGE
// #define ONLINE_JUDGE
// #endif

int n;
const int N=3e5+5,maxn=3e5;
int a[N];
int cnt[N];
priority_queue<pair<int,int> >q; // cnt,first_pos
vector<int> v[N];
int head[N],tail[N];

#define limit(x) (((x)+1)>>1)
bool vis[N];
// bool isout[N];
set<int> s2;

signed main()
{
	// #ifndef ONLINE_JUDGE
	// food
	freopen("food.in","r",stdin);
	freopen("food.out","w",stdout);
	
	n=read();
	for(int i=1;i<=n;i++) 
	{
		// s.insert(i);
		a[i]=read();
		cnt[a[i]]++;
		v[a[i]].push_back(i);
		tail[a[i]]++;
		if(cnt[a[i]]==1) s2.insert(i);
	}
	for(int i=1;i<=maxn;i++) 
	if(cnt[i]) q.push(make_pair(cnt[i],v[i][0]));

	if(q.top().first>limit(n))
	{
		cout<<"-1\n";
		return 0;
	}
	int last=-1;
	for(int i=1;i<=n;i++)
	{
		while(q.top().first!=cnt[a[q.top().second]]) q.pop();
		// cerr<<i<<" "<<q.size()<<" "<<s2.size()<<"\n";

		if(q.top().first>limit(n)) { cout<<"-1\n"; return 0; }
		if(q.top().first==limit(n-i+1)&&limit(n-i+1)!=limit(n-i))
		{
			// cout<<"pos="<<i<<" limit="<<limit(n-i+1)<<"\n";
			if(last==a[q.top().second]) { cout<<"-1\n"; return 0; }
			int val=a[q.top().second];
			q.pop();

			s2.erase(s2.find(v[val][head[val]]));
			write(v[val][head[val]]);
			// isout[v[val][head[val]]]=1;
			last=val;
			putchar(' ');

			head[val]++;
			cnt[val]--;
			q.push(make_pair(cnt[val],v[val][head[val]]));
			s2.insert(v[val][head[val]]);
			continue;
		}

		// continue;
		int flag=-1;
		if(a[(*s2.begin())]==last) flag=(*s2.begin()),s2.erase(s2.begin());		
		if(s2.empty()) { cout<<"-1\n"; return 0; }

		int l=*s2.begin();
		s2.erase(s2.begin());
		int val=a[l];
		last=val;
		// isout[l]=1;

		write(l);
		putchar(' ');

		head[val]++;
		cnt[val]--;
		if(cnt[val])
		{
			q.push(make_pair(cnt[val],v[val][head[val]]));
			s2.insert(v[val][head[val]]);
		}

		if(flag>0) s2.insert(flag);
	}

	// #endif
	//mt19937_64 myrand(time(0));
	return 0;
}

C. 字符串 (str)

考虑大力字典树匹配,发现匹配是 \(O(x^2)\),反正过不去。

回文串匹配自然而然考虑用二分哈希求解。

(QEDQEDQED 正在施工)

会了。先考虑单串内的回文串,对答案贡献是好求的。只用考虑一组,另一组直接 swap 到这一组即可。

考虑回文中心在左边的 \(pos\),且该回文串跨中间分界线贡献。显然没连接时必须在左串以 \(pos\) 回文的最长回文串的右端点为左串尾。

后缀不好求,考虑转前缀,再考虑把所有右边的串 reverse 之后插入 trie 中,在对 trie 进行 dfs,记录根链 hash 值和根链前缀和,查询时直接二分哈希求出字典树上存在的长度最长(深度最大)的一条根链,而且这条根链可以进行匹配。

简单(并不)实现即可。

注意:数组开到 \(2\times 10^6\);被卡空间,调空间;用哪个 hash 数组判断回文;字典树部分若找不到回文串,则答案为 \(0\)

代码不想写了。一会写

Code (QEDQEDQED Wy_x):

#include<bits/stdc++.h>
#define ll long long
#define int long long

using namespace std;

// #ifndef ONLINE_JUDGE
// #define ONLINE_JUDGE
// #endif

#define ull unsigned long long

const signed N=1e5+5,M=2e6+5;
signed n,m;
string s[N],t[N];
ll ans=0;
ull h1[M],h2[M];
signed logn[M];
ull ksm[M];
const ull prime=131;

signed tr[(1<<20)][26];
ll cnt[((1<<20))*26];
signed tot;
unordered_map<ull,ll> mp;
signed root=0;

void insert(const string &s)
{
	int nw=root;
	for(int i=0;i<s.size();i++)
	{
		int c=s[i]-'a';
		if(!tr[nw][c]) tr[nw][c]=++tot;
		nw=tr[nw][c];
		// cout<<nw<<" "<<"\n";
		cnt[nw]++;
	}
}

int sum;
// int sum[(M)*26];
void dfs(int nw,ull H)
{
	// cout<<H<<" "<<cnt[nw]<<"\n";
	mp[H]=cnt[nw];
	// mp[H]=sum[nw];
	for(int i=0;i<26;i++)
	{
		int to=tr[nw][i];
		if(to)
		{
			cnt[to]+=cnt[nw];
			dfs(to,H*prime+i+'a');
		}
	}
}

void build()
{
	// mp.clear();
	for(int i=1;i<=m;i++) insert(t[i]);
	dfs(0,0);
}

ull hash1(int l,int r) { if(l>r) swap(l,r); return h1[r]-h1[l-1]*ksm[r-l+1]; }
ull hash2(int l,int r) { if(l>r) swap(l,r); return h2[l]-h2[r+1]*ksm[r-l+1]; }

int dohash(int mid,int maxn_len)
{
	int len=1;
	// while(k>=0)

	bool f=0;
	// f=mid==4;
	// if(f)cout<<"doHash:\nmid="<<mid<<" maxn_len="<<maxn_len<<"\n";
	for(int k=logn[maxn_len+1];k>=0;k--)
	{
		int to=len+(1<<k);
		if(to>maxn_len+1) continue;
		// if(f)cout<<" to="<<to<<" h2="<<hash2(mid,mid+to-1)<<" h1="<<hash1(mid-to+1,mid)<<"\n";
		if(hash2(mid,mid+to-1)==hash1(mid-to+1,mid)) len=to;
		// k--;
	}
	// if(f)cout<<"len="<<len<<"\n\n";
	return len;
}

int dotrie(int r)
{
	int l=r+1;
	for(int k=logn[r+1];k>=0;k--)
	{
		int to=l-(1<<k);
		if(to<1) continue;
		if(mp.count(hash2(to,r))) l=to;
	}
	// cout<<"query:["<<l<<","<<r<<"]\n";
	int ans=0;
	if(l<=r) ans=mp[hash2(l,r)];
	return ans;
}

void solve(int x)
{
	build();
	for(int i=1;i<=n;i++)
	{
		int siz=s[i].size();
		h2[siz+1]=0;
		for(int j=0;j<siz;j++) h1[j+1]=h1[j]*prime+s[i][j];
		for(int j=siz;j>0;j--) h2[j]=h2[j+1]*prime+s[i][j-1];

		// cout<<s[i]<<"\n";
		// cout<<hash2(4,5)<<"\n";
		// cout<<hash1(3,4)<<"\n";
		for(int j=1;j<=siz;j++)
		{
			int len=dohash(j,min(siz-j,j-1));
			// cout<<"j="<<j<<" len="<<len<<" siz="<<siz<<"\n";
			ans+=len*m;
			if(j+len-1==siz&&2*len-1!=siz) ans+=dotrie(j-len);
		}
	}
}

void clear()
{
	for(int i=0;i<=tot;i++) cnt[i]=0,memset(tr[i],0,sizeof(tr[i]));
	tot=0;
	mp.clear();
}

void solve()
{
	ksm[0]=1;
	ksm[1]=prime;
	for(int i=2;i<(M);i++)
	{
		ksm[i]=ksm[i-1]*prime;
		logn[i]=logn[i>>1]+1;
	}

	cin>>n>>m;
	// n=read();
	// m=read();
	for(int i=1;i<=n;i++) cin>>s[i];
	for(int i=1;i<=m;i++) cin>>t[i];
	
	solve(1);
	swap(s,t);
	swap(n,m);
	for(int i=1;i<=n;i++) reverse(s[i].begin(),s[i].end());
	for(int i=1;i<=m;i++) reverse(t[i].begin(),t[i].end());
	
	clear();

	solve(1);
	cout<<ans<<"\n";
	clear();
	ans=0;
}

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	freopen("str.in","r",stdin);
	freopen("str.out","w",stdout);
	// freopen("a.in","r",stdin);
	// freopen("a.out","w",stdout);

	int T;
	cin>>T;
	while(T--) solve();

	return 0;
}

D. 概率 (pr)

50 分代码是好写的。

(正在施工)

可以转化为 \(\frac{1 - P(lsum=rsum)}{2}\)

直接大力生成函数。

答案为 nm 项系数。

Code:

posted @ 2025-10-18 07:59  Wy_x  阅读(44)  评论(2)    收藏  举报