题解 寿司
论我调n方暴力一调一下午为了写个n方对拍调试信息写了一大串细节题本来就毒瘤序列上子序列还卡细节真的很让人崩溃啊
还有,为什么输入文件是「速食.cpp」
刚开始以为是逆序对,后来拿线段树分治水了十分
线段树分治做法:
然而这个做法其实是假的
考虑将区间划分为左右两个子区间,分别统计出将两区间中所有1移至左右两边的最少步数,令其为\(lcnt, rcnt\)
再令\(dp[i]\)为在此序列中将所有1合并,第一个1在此序列中位置i的最小步数
最后取\(root\)节点中\(lcnt, rcnt, dp[i]_{min}\)的最小值输出即可
而且这样的时间复杂度和空间复杂度都是\(nlogn\)的
然而这种方法是假的是因为它无法处理例如「左区间中一部分R左移至左端点,另一部分R跨区间右移,与右区间中R合并」的情况
例子吗?样例「BBRBBRBBBRRR」就是。对我样例都没过
这里左边第二个R应该移到右边去。
暂时没想到改法
放一下线段树分治10pts代码:
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 1000100
#define ll long long
#define ld long double
#define usd unsigned
#define ull unsigned long long
#define int long long
// B->0 R->1
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
char buf[1<<21], *p1=buf, *p2=buf;
inline int read() {
int ans=0, f=1; char c=getchar();
while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
return ans*f;
}
bool s[N];
const char check=1<<4;
inline int reads(bool* s) {
int pos=0; char c=getchar();
while (!isalpha(c)) c=getchar();
while (isalpha(c)) {
if (c&check) s[++pos]=1;
else s[++pos]=0;
c=getchar();
}
return pos;
}
struct segment{
int l, r, len, cnt, lcnt, rcnt;
int* dp;
inline void build(int l_, int r_) {l=l_; r=r_; len=r_-l_+1; cnt=0; lcnt=0; rcnt=0; dp=new int[len+3]; memset(dp, 0, sizeof(int)*(len+3));}
#define t(p) tree[p]
#define len(p) tree[p].len
#define cnt(p) tree[p].cnt
#define lcnt(p) tree[p].lcnt
#define rcnt(p) tree[p].rcnt
#define dp(p) tree[p].dp
}tree[N<<2];
inline void pushup(int p) {
cnt(p)=cnt(p<<1)+cnt(p<<1|1);
lcnt(p)=lcnt(p<<1)+lcnt(p<<1|1)+cnt(p<<1|1)*(len(p<<1)-cnt(p<<1));
rcnt(p)=rcnt(p<<1|1)+rcnt(p<<1)+cnt(p<<1)*(len(p<<1|1)-cnt(p<<1|1));
//cout<<"pushup "<<p<<' '<<t(p).l<<' '<<t(p).r<<' '<<lcnt(p)<<' '<<rcnt(p)<<' '<<cnt(p)<<endl;
int len=len(p);
dp(p)[1]=lcnt(p); dp(p)[len-cnt(p)+1]=rcnt(p);
//for (int i=1; i<=len; ++i) cout<<dp(p)[i]<<' '; cout<<endl;
for (int i=2; i<=len; ++i) {
if (t(p).l+i+cnt(p)-2>t(p).r) {dp(p)[i]=INF; continue;}
if (t(p<<1).l+i+cnt(p<<1)-2 > t(p<<1).r) {
//cout<<"1: "<<i<<endl;
//int dlt = t(p<<1).l+i+cnt(p<<1)-2 - t(p<<1).r;
//dp(p)[i] = dp(p<<1)[i] + dp(p<<1|1)[dlt+1];
//if (dlt+cnt(p<<1|1)>t(p).r) dp(p)[i]=INF;
//dp(p)[i] = rcnt(p<<1) + cnt(p<<1)*(cnt(p<<1)+(i-t(p<<1|1).l)) + dp(p<<1|1)[i-t(p<<1).len+cnt(p<<1)];
dp(p)[i] = rcnt(p<<1) + cnt(p<<1)*(t(p).l+i+cnt(p<<1)-t(p<<1|1).l) + dp(p<<1|1)[i-t(p<<1).len+cnt(p<<1)];
//cout<<rcnt(p<<1)<<' '<<cnt(p<<1)*(cnt(p<<1)+(i-t(p<<1|1).l))<<' '<<dp(p<<1|1)[i-t(p<<1).len+cnt(p<<1)]<<endl;
}
else {
//cout<<"2: "<<i<<endl;
//cout<<1<<endl;
int dlt = t(p<<1).r-(t(p<<1).l+i+cnt(p<<1)-2);
dp(p)[i] = dp(p<<1)[i] + lcnt(p<<1|1)+cnt(p<<1|1)*dlt;
}
}
//for (int i=1; i<=len; ++i) cout<<dp(p)[i]<<' '; cout<<endl;
}
void build(int p, int l, int r) {
t(p).build(l, r);
if (l>=r) {cnt(p)=s[l]; /*cout<<"cnt "<<l<<" = "<<s[l]<<endl;*/ return ;}
int mid=(l+r)>>1;
if (l<=mid) build(p<<1, l, mid);
if (r>mid) build(p<<1|1, mid+1, r);
pushup(p);
}
inline int query(int p) {return min(lcnt(p<<1)+rcnt(p<<1|1), rcnt(p<<1)+lcnt(p<<1|1));}
signed main()
{
#ifdef DEBUG
freopen("1.in", "r", stdin);
#endif
int n, T;
T=read();
while (T--) {
n=reads(s);
//cout<<n<<endl;
//for (int i=1; i<=n; ++i) cout<<s[i]<<' '; cout<<endl;
build(1, 1, n);
//printf("%lld\n", query(1));
//for (int i=1; i<=n; ++i) cout<<dp(1)[i]<<' '; cout<<endl;
int minn=INF;
for (int i=1; i<=n; ++i) minn=min(minn, dp(1)[i]);
minn = min(minn, query(1));
printf("%lld\n", minn);
}
return 0;
}
然后是\(n^2\)的正解暴力:
这个是裸暴力,
考虑到将每个R移到左边的步数就是这个R左边B的个数(右边也一样)
则破环为链后\(ans = min(\sum\limits^{n}_{i=1}min(l_i, r_i))\)
放这个代码是为了对比下我调了好久的那个\(n^2\)做法
ll ans = INF;
for (int i=1; i<=n; ++i) {
if (s[i]) continue;
ll t=0;
for (int j=i+1; j<=i+n; ++j) {
if (!s[j]) continue;
int li=l[j]-l[i-1], ri=r[j]-r[i+n];
t += min(li, ri);
}
ans = min(ans, t);
}
printf("%lld\n", ans);
我调的\(n^2\):
对就是从下午两点调到第二天早上六点那个
这个是基于上面式子的变形,我们有
而
所以我们可以把那个\(l_i+r_i\)提出来,问题就转化为了求 \(\sum\limits^{n}_{i=1}|l_i-r_i|\) 的最大值
破环为链时显然需要维护一个前缀和,这里令\(l[\ ], r[\ ]\)为左边B个数的前缀和和右边B个数的后缀和
考虑所选区间在所成链上滑动时绝对值的变化,则
这里因为不知道\(l_i-r_i\)的正负不能直接在绝对值上加减2
\(l_i, r_i\)均可求,那考虑讨论它们的大小关系
注意一个细节,当
此时绝对值不发生变化。我也就写错了十多遍
而当\(l_i \leqslant r_i\) 时绝对值会增大2,否则减小2
就可以动态地维护出其和\(sum\),
然后\(ans = \frac{\sum\limits^{n}_{i=1}(l_i+r_i)-sum_{max}}{2}\) 即可
但是这里细节极多
考虑当枚举到在i位置破环时,我们实际上是在枚举把位置i的字母移到序列最后,
所以处理绝对值变化时的\(l_i, r_i\)实际上是\(l_{i-1}, r_{i-1}\). 我调到晚上九点才看出来
这种n^2代码:
for (int i=1; i<=n; ++i) {
if (s[i]) continue;
for (int j=i+1,li,ri; j<=i+n; ++j) {
if (!s[j]) continue;
li=l[j]-l[i-1]; ri=r[j]-r[i+n];
if (li==ri+1) ;
else if (li<=ri) sum+=2;
else sum-=2;
}
maxn = max(maxn, sum);
}
printf("%lld\n", (sum2-maxn)/2);
注意到这里\(l_i, r_i\)的大小关系具有单调性,
可以二分出分界点,则分界点左侧R贡献+2,右侧贡献-2
再特判一下\(l_i=r_i+1\),可以优化到\(O(nlogn)\),可以通过本题
for (int i=1,pos=1,l1,r1,mid; i<=n; ++i) {
if (s[i]) {++pos; continue;}
l1=pos; r1=pos+size2-1;
while (l1<r1) {
mid = (l1+r1+1)>>1;
if (l[R[mid]]-l[i-1] <= r[R[mid]]-r[i+n]) l1=mid;
else r1=mid-1;
}
if (l[R[l1]]-l[i-1]<=r[R[l1]]-r[i+n]) sum += 2ll*(l1-pos+1);
r1 = pos+size2-1;
while (l1<=r1 && l[R[l1]]-l[i-1]<=r[R[l1]]-r[i+n]+1) ++l1;
if (l1<=r1) sum -= 2ll*(pos+size2-l1);
maxn = max(maxn, sum);
}
printf("%lld\n", (sum2-maxn)/2);
然而这样并不是最优,
发现当区间在链上滑动时,这个分界点的位置是单调不减的
当然不是我发现的
那么我们可以用单调指针把二分优化掉
这里就变成了\(O(n)\)
for (int i=1,pos=1,l1=1,r1; i<=n; ++i) {
if (s[i]) {++pos; continue;}
r1 = pos+size2-1;
while (l1<=r1 && l[R[l1+1]]-l[i-1]<=r[R[l1+1]]-r[i+n]) ++l1;
if (l[R[l1]]-l[i-1]<=r[R[l1]]-r[i+n]) sum += 2ll*(l1-pos+1);
while (l1<=r1 && l[R[l1]]-l[i-1]<=r[R[l1]]-r[i+n]+1) ++l1;
if (l1<=r1) sum -= 2ll*(pos+size2-l1);
maxn = max(maxn, sum);
}
printf("%lld\n", (sum2-maxn)/2);
就可以通过此题了。
最后放一下我调到最后注释比代码多的完整代码:
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 2000100
#define ll long long
#define ld long double
#define usd unsigned
#define ul1 unsigned long long
//#define int long long
// B->0 R->1
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
char buf[1<<21], *p1=buf, *p2=buf;
inline int read() {
int ans=0, f=1; char c=getchar();
while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
return ans*f;
}
bool s[N<<1];
int l[N<<1], r[N<<1], R[N], size;
int cnt, cnt2, dlt, size2;
ll ans, sum, maxn, sum2;
const char check=1<<4;
inline int abs2(int a) {return a>=0?a:-a;}
inline int reads(bool* s) {
int pos=0; char c=getchar();
while (!isalpha(c)) c=getchar();
while (isalpha(c)) {
s[++pos]=c✓
c=getchar();
}
return pos;
}
signed main()
{
#ifdef DEBUG
freopen("1.in", "r", stdin);
#endif
int n, T, lim, t;
T=read();
while (T--) {
size=sum=maxn=cnt=sum2=cnt2=dlt=ans=size2=0;
n=reads(s); lim=n<<1;
//cout<<n<<endl;
//memset(l, 0, sizeof(l));
//memset(r, 0, sizeof(r));
memcpy(s+n+1, s+1, sizeof(bool)*n);
#if 0
for (int i=1; i<=n; ++i)
if (s[i]) l[++size]=cnt, sum2+=l[size];
else ++cnt;
for (int i=n+1; i<=lim; ++i)
if (s[i]) l[++size]=cnt;
else ++cnt;
t=size;
for (int i=lim; i>n; --i)
if (s[i]) r[t]=cnt2, sum2+=r[t--];
else ++cnt2;
for (int i=n; i; --i)
if (s[i]) r[t]=cnt2;
else ++cnt2;
for (int i=1; i<=n; ++i) sum += abs2(l[i]-r[i]);
#else
for (int i=1; i<=lim; ++i) {l[i]=l[i-1]+!s[i]; if (i<=n&&s[i]) sum2+=l[i]; if (s[i]) R[++size]=i;}
for (int i=lim; i; --i) {r[i]=r[i+1]+!s[i]; if (i>n&&s[i]) sum2+=r[i];}
for (int i=1; i<=n; ++i) if (s[i]) sum+=abs2(l[i]-(r[i]-r[n+1])); //, cout<<"at "<<i<<" += "<<abs2(l[i]-(r[i]-r[n+1]))<<endl;
#endif
maxn=sum;
size2=size>>1;
#if 0
cout<<maxn<<' '<<sum<<endl;
cout<<sum2<<endl;
cout<<"size: "<<size<<endl;
//cout<<"size2: "<<size2<<endl;
cout<<"s: "; for (int i=1; i<=lim; ++i) cout<<setw(2)<<s[i]<<' '; cout<<endl;
cout<<"l: "; for (int i=1; i<=lim; ++i) cout<<setw(2)<<l[i]<<' '; cout<<endl;
cout<<"r: "; for (int i=1; i<=lim; ++i) cout<<setw(2)<<r[i]<<' '; cout<<endl;
#endif
#if 0
for (int i=1,pos=1,l1,r1,gap; i<=n; ++i) {
if (s[i]) continue;
#if 0
l1=pos; r1=pos+size2-1; gap=1;
while (l1<=r1 && (l[l1]-l[pos-1])<(r[l1]-r[l1+pos+1]-2) && gap) {
if (l1+gap<=r1 && (l[l1+gap]-l[pos-1])<(r[l1+gap]-r[l1+pos+1]-2)) l1+=gap, gap<<=1;
else gap>>=1, ++l1;
}
//cout<<l1<<' '<<pos<<endl;
//cout<<2ll*(l1-pos)<<' '<<2ll*(pos+n-l1)<<endl;
sum += 2ll*(l1-pos)-2ll*(pos+n-l1);
#else
//cout<<"sum: "<<sum<<endl;
for (int j=i,li,ri; j<=i+n-1; ++j) {
if (!s[j]) continue;
li=l[j]-l[i-1]; ri=r[j]-r[i+n];
#if 0
cout<<pos<<endl;
cout<<li<<' '<<ri<<endl;
#endif
if (ri==li+1) ;
else if (li<=ri) sum+=2;
else sum-=2;
//cout<<sum<<' ';
} //cout<<endl;
//dlt=0;
//cout<<"sum: "<<sum<<endl<<endl;
#endif
maxn = max(maxn, sum);
}
#endif
for (int i=1,pos=1,l1=1,r1; i<=n; ++i) {
if (s[i]) {++pos; continue;}
r1 = pos+size2-1;
while (l1<=r1 && l[R[l1+1]]-l[i-1]<=r[R[l1+1]]-r[i+n]) ++l1;
if (l[R[l1]]-l[i-1]<=r[R[l1]]-r[i+n]) sum += 2ll*(l1-pos+1); //, cout<<"+="<<2ll*(l1-pos+1)<<endl;
//cout<<"check lr "<<l[R[l1]]-l[i-1]<<' '<<r[R[l1]]-r[i+n]<<endl;
while (l1<=r1 && l[R[l1]]-l[i-1]<=r[R[l1]]-r[i+n]+1) ++l1; //, cout<<"check lr "<<l[R[l1]]-l[i-1]<<' '<<r[R[l1]]-r[i+n]<<endl;
if (l1<=r1) sum -= 2ll*(pos+size2-l1); //, cout<<"-="<<2ll*(pos+size2-l1)<<endl;
maxn = max(maxn, sum);
}
printf("%lld\n", (sum2-maxn)/2);
#ifdef MY
//cout<<"sum: "<<sum<<endl;
for (int i=1,pos=1,l1,r1,mid; i<=n; ++i) {
if (s[i]) {++pos; continue;}
#if 0
l1=pos; r1=pos+size-1; gap=1;
while (l1<=r1 && (l[R[l1]]-l[i-1])<(r[R[l1]]-r[i+n])) {
if (l1+gap<=r1 && (l[R[l1+gap]]-l[i-1])<(r[R[l1+gap]]-r[i+n])) l1+=gap, gap<<=1;
else gap>>=1, ++l1;
}
--l1; cout<<R[l1]<<' '<<i<<endl;
cout<<R[l1]<<endl;
#endif
l1=pos; r1=pos+size2-1;
while (l1<r1) {
mid = (l1+r1+1)>>1;
//cout<<"mid: "<<mid<<' '<<R[mid]<<endl;
if (l[R[mid]]-l[i-1] <= r[R[mid]]-r[i+n]) l1=mid;
else r1=mid-1;
}
#if 0
cout<<"s: "; for (int k=i; k<=i+n; ++k) cout<<setw(2)<<(s[k]?'R':'B')<<' '; cout<<endl;
cout<<"l: "; for (int k=i; k<=i+n; ++k) cout<<setw(2)<<char(s[k]?'0'+l[k]-l[i-1]:' ')<<' '; cout<<endl;
cout<<"r: "; for (int k=i; k<=i+n; ++k) cout<<setw(2)<<char(s[k]?'0'+r[k]-r[i+n]:' ')<<' '; cout<<endl;
cout<<"ij: "<<i<<' '<<l1<<endl;
cout<<"lr: "<<l[R[l1]]-l[i-1]<<' '<<r[R[l1]]-r[i+n]<<endl;
#endif
if (l[R[l1]]-l[i-1]<=r[R[l1]]-r[i+n]) sum += 2ll*(l1-pos+1); //, cout<<"+="<<2ll*(l1-pos+1)<<endl;
//cout<<"check lr "<<l[R[l1]]-l[i-1]<<' '<<r[R[l1]]-r[i+n]<<endl;
r1 = pos+size2-1;
while (l1<=r1 && l[R[l1]]-l[i-1]<=r[R[l1]]-r[i+n]+1) ++l1; //, cout<<"check lr "<<l[R[l1]]-l[i-1]<<' '<<r[R[l1]]-r[i+n]<<endl;
if (l1<=r1) sum -= 2ll*(pos+size2-l1); //, cout<<"-="<<2ll*(pos+size2-l1)<<endl;
//cout<<pos<<' '<<l1<<' '<<pos+size2<<endl;
//cout<<sum<<endl;
//cout<<2ll*(l1-pos)<<' '<<2ll*(pos+n-l1)<<endl;
//sum += 2ll*(l1-i)-2ll*(i+n-l1);
#if 0
for (int j=i+1,li,ri; j<=i+n; ++j) {
if (!s[j]) continue;
li=l[j]-l[i-1]; ri=r[j]-r[i+n];
#if 0
cout<<"ij: "<<i<<' '<<j<<endl;
cout<<"lr: "<<li<<' '<<ri<<endl;
#endif
if (li==ri+1) ;
else if (li<=ri) sum+=2; //, cout<<"+=2"<<endl;
else sum-=2; //, cout<<"-=2"<<endl;
//cout<<sum<<' ';
} //cout<<endl;
#endif
//cout<<"sum: "<<sum<<endl<<endl;
maxn = max(maxn, sum);
}
//cout<<sum2<<' '<<maxn<<endl;
printf("%lld\n", (sum2-maxn)/2);
#endif
//#ifdef FORCE
#if 0
cout<<"sum: "<<sum<<endl;
ans = INF;
for (int i=1; i<=n; ++i) {
if (s[i]) continue;
ll t=0;
for (int j=i+1; j<=i+n; ++j) {
if (!s[j]) continue;
int li=l[j]-l[i-1], ri=r[j]-r[i+n];
#if 1
cout<<"ij: "<<i<<' '<<j<<endl;
cout<<"lr: "<<li<<' '<<ri<<endl;
#endif
//assert(li+ri==7);
t += min(li, ri);
cout<<t<<endl;
}
cout<<"sum: "<<sum2-t*2<<endl;
ans = min(ans, t);
}
printf("%lld\n", ans);
#endif
}
return 0;
}