Codeforces Round #741 (Div. 2) D2. Two Hundred Twenty One (hard version) (思维,前缀和)
-
题意:有一长度为\(n\)的字符串,+表示1,-表示-1,字符串的子区间\([l,r]\)的贡献为,\(a_l-a_{l+1}+a_{l+2}-a_{l+3}...\),\(q\)次询问,每次询问一个区间,问最少删去几个位置的字符是的区间贡献为\(0\),输出答案和删去的位置。
-
题解:首先,假如区间贡献为\(0\)那么肯定什么都不用删。先考虑区间长度为奇数的情况,假设我们删去\(pos\)位置,那么\([pos+1,r]\)这段区间的贡献就会变成原来的相反数,因此,假如原区间的贡献为\(C\),那么我们就要找一个位置\(pos\),使得贡献\([l,pos-1]=[pos+1,r]=\frac{C}{2}\)。区间贡献可以用前缀和来维护,那么\(sum[pos-1]-sum[l-1]=sum[r]-sum[pos]\)。移项得到\(sum[r]+sum[l-1]=sum[pos-1]+sum[pos]\)。所以我们可以用set维护\(sum[i]+sum[i-1]\)的位置,每次二分查找pos。
这里有两个问题,一是起点如果是偶数,那么所有贡献都会变成相反数,不影响等式两边。
二是pos一定会在\([l,r]\)中,因为,起点处贡献为\(0\),终点贡献为\(C\),每个字符的贡献为\(\pm 1\),所以中间一定会有\(\frac{C}{2}\)的位置。
-
代码:
#include <bits/stdc++.h> #define ll long long #define fi first #define se second #define pb push_back #define me memset #define rep(a,b,c) for(int a=b;a<=c;++a) #define per(a,b,c) for(int a=b;a>=c;--a) const int N = 1e6 + 10; const int mod = 1e9 + 7; const int INF = 0x3f3f3f3f; using namespace std; typedef pair<int,int> PII; typedef pair<ll,ll> PLL; ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;} ll lcm(ll a,ll b) {return a/gcd(a,b)*b;} char s[N]; int a[N],sum[N]; int main() { int _; scanf("%d",&_); while(_--){ int n,q; scanf("%d %d",&n,&q); getchar(); scanf("%s",s+1); for(int i=1;i<=n;++i) a[i]=(s[i]=='+'?1:-1); sum[0]=0; for(int i=1;i<=n;++i){ sum[i]=sum[i-1]+(i&1?1:-1)*a[i]; } unordered_map<int,set<int>> pos; for(int i=1;i<=n;++i){ int x=sum[i]+sum[i-1]; pos[x].insert(i); } auto find=[&](int l,int r){ int x=sum[l-1]+sum[r]; return *pos[x].lower_bound(l); }; while(q--){ int l,r; scanf("%d %d",&l,&r); if(sum[r]-sum[l-1]==0) puts("0"); else if((r-l+1)&1) printf("1\n%d\n",find(l,r)); else printf("2\n%d %d\n",r,find(l,r-1)); } } return 0; }
𝓐𝓬𝓱𝓲𝓮𝓿𝓮𝓶𝓮𝓷𝓽 𝓹𝓻𝓸𝓿𝓲𝓭𝓮𝓼 𝓽𝓱𝓮 𝓸𝓷𝓵𝔂 𝓻𝓮𝓪𝓵
𝓹𝓵𝓮𝓪𝓼𝓾𝓻𝓮 𝓲𝓷 𝓵𝓲𝓯𝓮