D
题意
给定一个长度为n的数组,每次操作可以给两个数加一个任意的数,目的是最少的操作使任意的连续子串的和不为0
思路
我们可以从前往后用贪心的思路,找到一个点如果他和前面的子串和为0,那么我就给这个点的前面添加一个数,可能不太会证明,但是思路就是这样的
我们只需要统计出这个点到上一个前面加过操作点的之间的所有点的前缀和,可以放在set里面,如果set里面有这个点的权值,就说明这个点前面的子串有和为0的,即进行一次操作,然后以这个点为开始继续重新计算,直到最后一个点
代码
#include<iostream>
#include<cstring>
#include<unordered_set>
using namespace std;
#define int long long
const int N = 2e5+10;
int a[N];
signed main(){
ios::sync_with_stdio(false);
unordered_set<int > s;
int n;
cin>>n;
for(int i = 1;i<=n;i++) cin>>a[i];
int sum = 0;
s.insert(0);
int ans = 0;
for(int i = 1;i<=n;i++){
sum += a[i];
if(s.count(sum)){
ans++;
s.clear();
s.insert(0);
sum = a[i];
}
s.insert(sum);
}
cout<<ans<<endl;
return 0;
}
E
题意
两个人玩石头剪刀布,第一个人会出石头a1次,剪刀a2次,布a3次,第二个人会出石头b1次,剪刀b2次,布b3次,其中a1 + a2 + a3 = b1 + b2 + b3,求第一个人最少和最多赢多少次
思路
第一个人最多赢多少次,可以发现赢的情况里面两两是没有关联的,所以最需要统计 石头可以赢多少,剪刀可以赢多少,布可以赢多少就是最多赢的可能性
最少可以赢多少,也就是输和平的次数最多,输和平是和两个有关系的,比如石头要输和平的最大次数就是 min(a1,b1+b2),因为第二个人的石头和布消耗的个数不知道,所以石头和布在不影响最大个数的情况下是可以相互转换的, 因为对于下一个剪刀来说,输和平的影响是 石头和剪刀 所以在第一次操作完成后应该使石头的个数尽可能的多,也就是消耗布尽可能多,对于剪刀也是如此,最后剩下的布,影响是剪刀和布,所以多的石头可以等价转换为布(但是注意布的最大数量),这样就能求出输和平的最大个数剩下就是最少赢的个数,感觉有点绕,仔细想想应该还是可以想通的,如果不想想应该用最大流也也是可以的
代码
#include<iostream>
using namespace std;
#define int long long
signed main(){
int n;
cin>>n;
int a1,a2,a3;
int b1,b2,b3;
cin>>a1>>a2>>a3;
cin>>b1>>b2>>b3;
int maxp = 0;
maxp += min(a1,b2) + min(a2,b3) + min(a3,b1); // 石头剪刀布的赢的可能
int bb1 = b1,bb2 = b2,bb3 = b3;
int aa1 = a1,aa2 = a2,aa3 = a3;
int t = bb1 + bb3; // 石头和布总个数
aa1 -= min(a1,t); // 石头除去输和平的个数
t -= min(a1,t); // 剩下的石头和布的总个数
bb1 = min(b1,t); // 使石头尽可能多
bb3 = t - bb1; // 剩下为布
t = bb1 + bb2; // 石头和剪刀的总个数
aa2 -= min(a2,t); // 剪刀除去输和平的个数
t -= min(a2,t); // 剩下的剪刀和石头的个数
bb2 = min(b2,t); // 使剪刀的个数尽可能的多
bb1 = t - bb2; // 石头的个数
bb3 = min(b3,bb1 + bb3); // 使剩下的石头尽可能转化为布
t = bb2 + bb3; // 布和剪刀的个数
aa3 -= min(t,a3); // 布除去输和平的个数
int minp = aa1 + aa2 + aa3;
cout<<minp <<' '<<maxp<<endl;
return 0;
}
F
题意
给定一个长度为n的字符串,由’a’,‘b’,‘c’,’?’,组成’?‘可以转换为’a’,‘b’,‘c’,统计‘abc’子串的个数,‘abc’可以不连续但是顺序一定要正确
思路
官方题解的一个很好的思路,找到所有的含有‘abc’的子串需要’?‘个数,也就是说最多可能需要3个’?’,该子串的所有可能性为 这个子串的个数 * 剩余’?‘的所以可能。使用dp[i][j][k]表示为在第i位已经匹配了第j个字符使用了k个’?'的方案数,然后我们可以用选这个数或不选这个数来表示当前状态可以转移到其他状态
这个数不选 dp[i][j][k]
dp[i-1][j][k]
选 dp[i + 1][j + 1][t] (当前这个数等于下一位数或者为?,如果是? t = k+ 1否则为k)
代码
#include<iostream>
#include<cstring>
using namespace std;
const int N = 2e5+10,mod = 1e9+7;
char s[N];
int dp[N][4][4];
int pow3[N];
int main(){
int n;
scanf("%d",&n);
scanf("%s",s + 1);
int cnt = 0;
for(int i = 1;i<=n;i++) // 统计?个数
if(s[i] == '?') cnt++;
pow3[0] = 1;
for(int i = 1;i <= n;i++)
pow3[i] = 1ll * pow3[i - 1] * 3 % mod;
dp[0][0][0] = 1; //开始
for(int i = 0;i<n;i++){
for(int j = 0;j<=3;j++){
for(int k = 0;k<=3;k++){
dp[i + 1][j][k] = (0ll + dp[i + 1][j][k] + dp[i][j][k])%mod; // 选
if(k < 3 && j < 3 && (s[i + 1] == '?'||s[i + 1] - 'a' == j)){ // 不选
int t = s[i + 1] == '?'?k + 1:k;
dp[i + 1][j + 1][t] = (0ll + dp[i + 1][j +1][t] + dp[i][j][k])%mod;
}
}
}
}
int ans = 0;
for(int i = 0;i<=3;i++)
ans = (ans + 1ll * dp[n][3][i] * pow3[cnt - i] % mod)%mod; // 当前个数 * 没有使用?的所以可能
cout<<ans<<endl;
return 0;
}