“蔚来杯“2022牛客暑期多校训练营9
A Car Show
双指针算法,先移动右指针到满足从1~ i的区间内1~m的数每个至少有一个,之后移动左指针,只要满足区间内1 ~ m的数每个至少还有一个,左指针j就能一直移动,直到不满足条件。
这题还有个要注意的点是需要define intg long long,但是题中给的n、m、a都是小于1e6的,唯一可能的就是ans会爆int。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 10;
int n, m;
int a[N];
int ans; //记录答案
int res; //记录不同数的个数
int cnt[N]; //记录每个数出现过没
signed main()
{
cin>>n>>m;
for(int i = 1; i <= n; i ++ ){
cin>>a[i];
}
int j = 1; //左指针
for(int i = 1; i <= n; i ++ ){ //移动区间右指针
if(!cnt[a[i]]){
res ++ ;
}
cnt[a[i]] ++ ;
if(res == m){
while(j < i && cnt[a[j]] > 1){
ans += n - i + 1;
cnt[a[j]] -- ;
j ++ ; //移动左指针
}
ans += n - i + 1; //这个加上的是最后一个符合条件的区间,此时a[j]=1,在while不会被计算
cnt[a[j]] -- ;
j ++ ;
res -- ;
}
}
cout<<ans<<endl;
return 0;
}
B Two Frogs
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 8010, mod = 998244353;
int dp[N][N]; //dp[i][j]表示跳了i次到达j点的概率
int a[N];
int n;
int infac[N]; //逆元数组
int ans;
int di[N]; //一维差分数组
int qmi(int a, int b){
int res = 1;
while(b){
if(b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
void init(){ //求逆元
for(int i = 1; i < 8000; i ++ ){
infac[i] = qmi(i, mod - 2);
}
}
signed main()
{
init();
cin>>n;
for(int i = 1; i < n; i ++ ) cin>>a[i];
dp[0][1] = 1;
for(int i = 1; i < n; i ++ ){ //枚举起点
for(int j = 1; j < n; j ++ ){
di[j + 1] = (di[j + 1] + dp[i - 1][j] * infac[a[j]] % mod + mod) % mod;
di[j + a[j] + 1] = (di[j + a[j] + 1] - dp[i - 1][j] * infac[a[j]] % mod + mod) % mod;
}
for(int j = 1; j <= n; j ++ ){ //这里是在对到达i点的所有步骤进行差分求前缀和
dp[i][j] = (dp[i][j] + dp[i][j - 1] + di[j]) % mod;
}
memset(di, 0, sizeof di);
}
for(int i = 0; i < n; i ++ ){ //同时到达0点也包括在内
ans = (ans + dp[i][n] * dp[i][n] % mod) % mod;
}
cout<<ans<<endl;
return 0;
}
G Magic Spells
一道题搞了两天,终于搞懂了回文自动机,还学会了马拉车算法,也算收货不小,这题是回文自动机套板子
回文自动机讲解看这里
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 10;
int tr[N][27]; //建树
int n;
int ans; //记录答案
bool cnt[5][N]; //cnt[i][j]表示在第i个魔法串内j串是否存在
int fail[N]; //fail[i]指向i的最长回文后缀子串的节点(个人认为是回文树的灵魂数组)
int len[N]; //记录节点长度
int idx; //记录节点个数
int last; //记录上一个新加入的节点
char s[N]; //存储字符串
int now;
int newnode(int x){ //创建新节点
len[idx] = x;
return idx ++ ; //返回之后idx再++
}
int getfail(int x, int n){ 跳fail指针直到找到后缀回文为止,用到的两个参数都是字符串的下标
while(s[n - len[x] - 1] != s[n]) x = fail[x];
return x;
}
void init(){
newnode(0), newnode(-1);
fail[0] = 1;
}
void insert(int j, int i){
int t = s[j] - 'a';
//找到可以回文的位置
int p = getfail(last, j); //返回的是节点编号
if(!tr[p][t]){
//如果有了转移就不用建了,否则要新建
//前后都加上新字符,所以新回文串长度要加2
now = newnode(len[p] + 2);
//因为fail指向的得是原串的严格后缀,所以要从p的fail开始找起
fail[now] = tr[getfail(fail[p], j)][t];
//记录转移
tr[p][t] = now;
}
//以下两个变量不能直接用now赋值,因为tr[p][t]可能存在,此时now的值就会赋错
last = tr[p][t];
cnt[i][last] = true;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
init(); //初始化一定不能忘了
//先把所有串放到一个树内
for(int i = 0; i < n; i ++ ){
last = 0;
cin>>s + 1;
s[0] = -1;
for(int j = 1; s[j]; j ++ ) insert(j, i);
}
for(int i = idx - 1; i > 1; i -- ){ //遍历除两个根节点之外的每个节点,沿着树从下往上遍历,因为树上层的状态没有被更新
for(int j = 0; j < n; j ++ ){ //遍历n个串
cnt[j][fail[i]] |= cnt[j][i];
}
bool ok = true;
for(int j = 0; j < n; j ++ ){
if(!cnt[j][i]) ok = false;
}
ans += ok;
}
cout<<ans<<endl; //答案是所有串中都包含的回文子串的个数
return 0;
}
E Longest Increasing Subsequence
构造类型题,先想最基础的构造,然后根据题意逐步优化
雨巨讲题 从39min开始看
代码是借鉴这个大佬的:这个讲解也很强,和雨巨的结合着更容易理解 传送门
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int T;
int n;
int len; //长度
int cnt; //排列的长度
int cur; //赋值游标
int ans[N]; //答案数组
int main()
{
cin>>T;
while(T -- ){
cnt = 0;
len = 0;
cin>>n;
if(n == 1){
cout<<1<<endl<<1<<endl;
continue;
}
for(int i = 31; i; i -- ){ //从高位向低位遍历
if(n >> i & 1){ 找到第一个1,这是要个构建的基础组数(一组两个数,如21、43、65)
len = i;
break;
}
}
cur = 2 * len;
// cout<<cur<<' '<<len<<"***"<<endl;
for(int i = 0; i < len; i ++ ){
if(n >> i & 1){ //如果遇到要补的1
ans[ ++ cnt] = ++ cur;
int x = i + 1;
while((1 << x) <= n && !((n >> x) & 1)){ //这里循环的次数就是i到更高位1之间0的数量+1
ans[ ++ cnt] = ++ cur;
x ++ ;
// cout<<"x:"<<x<<endl;
}
}
ans[ ++ cnt] = 2 * i + 2;
ans[ ++ cnt] = 2 * i + 1;
}
cout<<cnt<<endl;
for(int i = 1; i <= cnt; i ++ ){
cout<<ans[i]<<' ';
}
cout<<endl;
}
return 0;
}