今天状态一般,A了四个题
看题吧
目录
[1. 最小合法总和推导](#1. 最小合法总和推导)
[2. 存在性判断](#2. 存在性判断)
[3. 增量构造方法](#3. 增量构造方法)
A:小红的电话亭

签到题
cpp
#include<bits/stdc++.h>
using namespace std;
string s;
int main()
{
cin>>s;
for(int i=0;i<s.size();i++)
{
cout<<s[i];
if(i==2||i==6)cout<<'-';
}
return 0;
}
B:小红的01串构造

字符串构造
先判断n的奇偶性,类似于111111000000的肯定是最好的呀
cpp
#include<bits/stdc++.h>
using namespace std;
int n;
int main()
{
cin>>n;
if(n&1)
{
cout<<-1<<endl;
return 0;
}
for(int i=1;i<=n/2;i++)cout<<1;;
for(int i=n/2+1;i<=n;i++)cout<<0;
return 0;
}
C:小红的矩阵构造

还是构造
整体采用「最小基底 + 增量集中填充」的构造策略,分为三步:先算出合法矩阵的最小总和,判断是否有解;有解则先铺最小基底,再把多余的增量集中加到一个格子上。
1. 最小合法总和推导
要满足「相邻不等、全正整数」的约束,总和最小的构造方式就是国际象棋棋盘式填充:
- 奇数行奇数列、偶数行偶数列填 1
- 其余位置填 2
这种布局天然保证相邻格子一个是 1、一个是 2,必然不相等,且使用的都是最小的正整数,因此总和是所有合法矩阵里最小的。
设总格子数 tot = n * m:
- 值为 2 的格子数量:tot / 2(整数除法,向下取整)
- 值为 1 的格子数量:tot - tot / 2
因此最小总和为: min_sum = 1 * (tot - tot/2) + 2 * (tot/2) = tot + tot / 2
2. 存在性判断
- 若 k < min_sum:不可能构造出合法矩阵,直接输出 NO。 原因:所有元素至少为 1,且相邻不能相等,不可能全填 1,最小总和就是棋盘布局的和,比这个更小的话无解。
- 若 k >= min_sum:一定存在解,接下来进行构造。
3. 增量构造方法
计算差值 sup = k - min_sum,这是我们需要额外增加的数值。
核心技巧:把所有增量全部加到同一个值为 2 的格子上。 选择值为 2 的格子的原因:它的上下左右四个相邻格子全都是 1,哪怕把它的值改成 2 + sup(一个很大的数),它和周围的 1 依然永远不相等,完全不会破坏「相邻不等」的规则。
修改后矩阵总和恰好为 min_sum + sup = k,且依然满足好矩阵的全部条件。
cpp
#include<bits/stdc++.h>
using namespace std;
int n,m,k;
typedef long long LL;
int main()
{
cin>>n>>m>>k;
LL sum=1LL*n*m;
sum+=sum/2;
if(k<sum)
{
cout<<"NO"<<endl;
return 0;
}
cout<<"YES"<<endl;
LL sup=k-sum;
//cout<<sup<<endl;
bool flag=false;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
int x=2;
if(i%2==1&&j%2==1||i%2==0&&j%2==0)x=1;
if(x==2&&!flag)
{
cout<<x+sup<<' ';
flag=true;
}
else cout<<x<<' ';
}
cout<<endl;
}
return 0;
}
D:小红的数字染色

贪心+前缀和+双指针
核心数学结论
设数组总和为 sum,选出的连续子数组和为 x,未被选中的元素和就是 sum - x。 我们的目标是最大化 x * (sum - x)。这是一个开口向下的二次函数,对称轴在 x = sum / 2 处: x 越接近 sum 的一半,乘积就越大。
因此问题转化为:在所有连续子数组的和中,找到最接近 sum/2 的那个值。
代码实现思路
- 从中点左侧找:找所有 不超过
ceil(sum/2)(上取整的一半)的连续子数组和,取其中最大的,也就是离中点最近的左侧点。 - 从中点右侧找:找所有 不小于
floor(sum/2)(下取整的一半)的连续子数组和,取其中最小的,也就是离中点最近的右侧点。 - 比较两个候选谁离中点更近,用更优的那个计算最终乘积。
因为数组全是正整数,前缀和严格单调递增,所以两次查找都可以用 ** 双指针(滑动窗口)** 在线性时间内完成,总时间复杂度 O (n)。
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e5+10;
const int mod=998244353;
LL a[N];
int main()
{
int n;
cin>>n;
LL sum=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum+=a[i];
}
LL res1=1e18;
LL res2=1e18;
LL num1=sum/2+sum%2;
for(int i=1;i<=n;i++)a[i]+=a[i-1];
int j=1;
for(int i=1;i<=n;i++)
{
while(a[i]-a[j-1]>num1)j++;
res1=min(res1,num1-(a[i]-a[j-1]));
}
j=1;
LL num2=sum/2;
for(int i=1;i<=n;i++)
{
while(j <= n && a[j]-a[i-1]<num2)
{
j++;
}
if(j <= n)
{
res2=min(res2,a[j]-a[i-1]-num2);
}
if(j > n)break;
}
if(res1<=res2)
{
LL x = (num1 - res1) % mod;
LL y = (sum - num1 + res1) % mod;
cout << x * y % mod << endl;
}
else
{
LL x = (num2 + res2) % mod;
LL y = (sum - num2 - res2) % mod;
cout << x * y % mod << endl;
}
return 0;
}
E:小红的矩阵计数

状态压缩+线性DP
核心转化:把二维约束降为相邻列约束
矩阵只有固定 2 行,任意一个 2×2 的区域,本质就是相邻的两列拼接而成。 四个元素全相同的情况只有两种:
- 相邻两列都是「上 0、下 0」→ 拼出四个 0,非法
- 相邻两列都是「上 1、下 1」→ 拼出四个 1,非法
除此之外所有相邻列的组合都是合法的。比如两列都是 0/1,拼出来是 0 0 / 1 1,四个元素不全相同,完全合法。
于是问题就转化为: 给每一列填 0/1(受原字符限制),要求不能出现连续两列全 0、也不能出现连续两列全 1,求总方案数。 这是非常经典的线性动态规划问题。
状态设计
每一列只有上下两个格子,每个格子是 0 或 1,总共只有 4 种固定形态,我们给它们编号:
- 状态 0:上行 0,下行 0 → 全 0 列
- 状态 1:上行 0,下行 1 → 混合列
- 状态 2:上行 1,下行 0 → 混合列
- 状态 3:上行 1,下行 1 → 全 1 列
定义 dp is 表示:处理完前 i 列,且第 i 列恰好是状态 s 时,合法的填法总数。 最终答案就是第 n 列四个状态的方案数之和。
状态转移规则
我们先算出前一列所有状态的方案总和 sum_prev,再根据当前列的状态做调整:
- 当前列是状态 0(全 0 列) 前一列不能是状态 0(否则连续两列全 0,非法),因此转移值 = sum_prev - dp i-10
- 当前列是状态 3(全 1 列) 前一列不能是状态 3,因此转移值 = sum_prev - dp i-13
- 当前列是状态 1 或 状态 2(混合列) 混合列和任何列相邻都不会出现四个全相同,因此前一列可以是任意状态,转移值 = sum_prev
注意:取模运算中减法可能得到负数,所以要先加模数再取模,保证结果非负
逐列可行状态判断
对于第 i 列的两个字符(上行、下行),要先判断每个状态是否能填出来:
- 字符是 '0' → 对应位置只能填 0
- 字符是 '1' → 对应位置只能填 1
- 字符是 '?' → 0 和 1 都可以
上下两个位置都匹配,这个状态就是可行的,方案数为 1;否则为 0。
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const int mod = 998244353;
LL dp[N][4];
int main()
{
int n;
string s1, s2;
cin >> n >> s1 >> s2;
char c1 = s1[0], c2 = s2[0];
dp[1][0] = ((c1 == '0' || c1 == '?') && (c2 == '0' || c2 == '?')) ? 1 : 0; // 0 0
dp[1][1] = ((c1 == '0' || c1 == '?') && (c2 == '1' || c2 == '?')) ? 1 : 0; // 0 1
dp[1][2] = ((c1 == '1' || c1 == '?') && (c2 == '0' || c2 == '?')) ? 1 : 0; // 1 0
dp[1][3] = ((c1 == '1' || c1 == '?') && (c2 == '1' || c2 == '?')) ? 1 : 0; // 1 1
for(int i = 2; i <= n; i++)
{
c1 = s1[i-1];
c2 = s2[i-1];
LL sum_prev = (dp[i-1][0] + dp[i-1][1] + dp[i-1][2] + dp[i-1][3]) % mod;
if((c1 == '0' || c1 == '?') && (c2 == '0' || c2 == '?'))
dp[i][0] = (sum_prev - dp[i-1][0] + mod) % mod;
else
dp[i][0] = 0;
if((c1 == '0' || c1 == '?') && (c2 == '1' || c2 == '?'))
dp[i][1] = sum_prev % mod;
else
dp[i][1] = 0;
if((c1 == '1' || c1 == '?') && (c2 == '0' || c2 == '?'))
dp[i][2] = sum_prev % mod;
else
dp[i][2] = 0;
if((c1 == '1' || c1 == '?') && (c2 == '1' || c2 == '?'))
dp[i][3] = (sum_prev - dp[i-1][3] + mod) % mod;
else
dp[i][3] = 0;
}
LL ans = (dp[n][0] + dp[n][1] + dp[n][2] + dp[n][3]) % mod;
cout << ans << endl;
return 0;
}