题目链接:https://codeforces.com/problemset/problem/1692/E
前缀和 + 二分 或 双向队列双指针
大意:输入n个数,只包含 0 和1,输入一个数s。sum为数列的和,每次操作可以删除第一个数或最后一个数,最后求最少通过几次这样的操作可以使得sum=s,如果无法实现输出 -1。
方法:这个题十分巧妙,里面用来确定mid 的 l,仅仅起到了控制范围的作用,而真正的sum的左边界是循环的 i 这样做的复杂度就是 nlogn了
这个题把我整懵的地方就是要找到两个合适的边界,我只想到了枚举。虽然题目给出了二分的类别,但是还是没明白怎么二分
后来想了想,这题其实并不难,我们可以暴力一个边界,另一个边界通过强秒的方法来计算出来,而不需要首位两两对应一遍
前缀和+二分代码:
#include<bits/stdc++.h> using namespace std; int a[100000*2+10]; int sq[100000*2+10]; int main() { int t; scanf("%d",&t); while(t--) { int n,s,sum=0; scanf("%d%d",&n,&s); memset(a,0,sizeof a); memset(sq,0,sizeof sq); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); sq[i]=sq[i-1]+a[i]; } int mint = 1e9; for(int i=1;i<=n;i++)//以i作为一个起点 { int l = i, r = n; while(l<=r) { int mid = l+r>>1; if(sq[mid]-sq[i-1] <=s)//如果 mid 减去 l前的 <=s,那么我们就往后推导,增加mid的值 { l = mid +1;//l 仅仅用来控制距离,我们最终需要的是 i - j这个范围的sum }else//如果 mid 减去 l前的 >s,那么我们就往前推导,这么mid的位置靠前,减少mid的值 { r = mid -1; } } if(sq[r]-sq[i-1]==s) mint = min(mint,n-(r-i+1)); } printf("%d\n",mint<1e9?mint:-1); } }双向队列指针代码(没get到)感觉和上一个二分差不多,只不过这里也算暴力了,就当作是另一种写法吧:
//#pragma comment(linker, "/STACK:1024000000,1024000000") #include <bits/stdc++.h> #define int long long #define double long double void solve() { int n, s; std::cin >> n >> s; std::vector<int> a(n + 1); for (int i = 1; i <= n; ++i) { std::cin >> a[i]; a[i] += a[i - 1]; } int ans = 1e9; int r = 1; for (int i = 1; i <= n; ++i) { while (r < n && a[r + 1] - a[i - 1] <= s) r++; if (a[r] - a[i - 1] == s) ans = std::min(ans, n - (r - i + 1)); } std::cout << (ans < 1e9 ? ans : -1) << "\n"; } signed main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); int tt(1); std::cin >> tt; while (tt--) { solve(); } return 0; }最后是我的笨拙的
#include<bits/stdc++.h> using namespace std; int a[100000*2+10]; int sq[100000*2+10],sh[100000*2+10]; int main() { int t; scanf("%d",&t); while(t--) { int n,s,sum=0; scanf("%d%d",&n,&s); memset(a,0,sizeof a); memset(sq,0,sizeof sq); memset(sh,0,sizeof sh); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); sum += a[i]; sq[i]=sq[i-1]+a[i]; } for(int i=n;i>=1;i--) { sh[i]=sh[i+1]+a[i]; } // 前后缀和 int cha = sum - s; if(cha==0) { printf("0\n"); }else if(cha<0) { printf("-1\n"); }else { int mint = 2*100000+10; for(int i = 0;i<=n;i++) { if(sq[i]>cha) break; for(int j = n+1;j>i;j--) { if(sh[j]>cha) break; if(sq[i]+sh[j] == cha) { mint=min(mint,i+n-j+1); }else if(sq[i]+sh[j]>cha) { break; } } } printf("%d\n",mint); } } }暴力写法(前缀和+后缀和,相加计算差值):