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:
以下是博客签名,正文无关
本文来自博客园,作者:Wy_x,转载请在文首注明原文链接:https://www.cnblogs.com/Wy-x/p/19149141
版权声明:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC-BY-NC-SA 4.0 协议)进行许可。

试点
浙公网安备 33010602011771号