P17729 [SCP-J 2026] 进位 / carry [题解]
Mid_night_ · · 题解
P17729 [SCP-J 2026] 进位 [题解]
题目大意
使用
要求构造一个序列
Solution
快速计算进位
我们使用
不难发现,如果
可以这样理解:在二进制下计算时,如果当前位都为
此时,区间
发现中间一减一加消掉了,原式化简为:
这显然可以使用两个前缀和数组维护,即可做到
扫描区间统计答案
首先,假定我们有区间
又因为题目要求进位次数为
定义
- 维护
l 表示当右端点R 确定时,第一个使C(l,r) 为1 的左端点。 - 维护
r 表示当右端点R 确定时,第一个使C(l,r) 为0 的左端点。
于是合法左端点成为一个区间
状态转移式即为:
同样我们可以使用一个
最终算法流程
开三个指针
同时维护三个前缀和数组:
对于每个固定右端点,计算答案:
注意不要忘记取模,处理负数情况。
Code
::::success[Code]
#include<bits/stdc++.h>
using namespace std;
#define cnt(x) __builtin_popcountll(x)
#define int long long
const int MOD = 998244353;
const int N = 6e6+5;
int t;
int n;
int a[N];
int s1[N],s2[N];
int sdp[N],dp[N];
int C(int l,int r){
return s2[r]-s2[l-1]-(cnt(s1[r]-s1[l-1]));
}
signed main(){
cin>>t;
while(t--){
scanf("%lld",&n);
memset(dp,0,sizeof dp);
memset(sdp,0,sizeof sdp);
s1[0] = 0;
s2[0] = 0;
for(int i = 1;i<=n;i++) scanf("%lld",&a[i]),s1[i] = s1[i-1]+a[i];
for(int i = 1;i<=n;i++) s2[i] = s2[i-1]+cnt(a[i]);
int l = 1,r = 1,R = 1,ans = 0;
while(R<=n){
while(l<R&&C(l,R)>1) l++;
while(r<R&&C(r,R)>0) r++;
dp[R] = (1+sdp[r-1]-sdp[l-1]+MOD)%MOD;//+MOD处理负数
sdp[R] = (sdp[R-1]+dp[R]+MOD)%MOD;//+MOD处理负数
R++;
}
for(int i = 1;i<=n;i++) ans = (ans+dp[i])%MOD;
cout<<ans<<'\n';
}
return 0;
}
:::: ::::warning[警告]
- 注意不要忘记取模。
- 记得加上模数处理负数情况。
-
不要用 cin,不然你会 T 飞。