AtCoder Snuke21 J. Drink Bar - 分段得分题解
这里将每一个三元组 \((a_i,b_i,c_i)\) 称为一组数。
Subtask 1
暴力枚举所有的非空子集即可。
枚举方式可以采用类似状压 DP 的二进制枚举或者直接 DFS。
时间复杂度 \(O(N \times 2^N)\)。
Subtask 2
性质:此时的特征值最多由两个有效组组成,原因可见 Subtask 3。
因为 \(a_i=b_i\),所以三元组 \((a_i,b_i,c_i)\) 实际上可以压缩为二元组 \((a_i,c_i)\),对其进行二维偏序。
我这里采用的是排序后树状数组的写法,具体地说:
将所有二元组按照 \(a\) 排序后扫描所有二元组,可以保证对于当前二元组 \(i\) ,之前组 \(j\) 的 \(a_j\) 一定小于 \(a_i\)。
此时如果之前某二元组 \(j\) 有 \(c_j\) 比当前的 \(c_i\) 大,那么这两个二元组就可以合成一个特征值 \((a_i,c_j)\),组 \(i\) 贡献了 \(a_i\),组 \(j\) 贡献了 \(c_j\)。
如此,我们构造权值树状数组,每次找当前 \(c_i\) 的后缀和就是之前 \(c_j>c_i\) 的 \(j\) 的数量。
注意,我代码里面是先加入 \(c_i\) 再查找,并且查找的是 \(c_j \ge c_i\) 的 \(j\) 的数量,这是为了确保 \(j\) 能够取到 \(i\),表示自己与自己组合,即自己单独构成一个特征值。
时间复杂度 \(O(N \log N)\)。
点击查看代码
namespace BinaryIndexTree{
struct BIT_R{
int c[N];
#define lowbit(x) ((x)&-(x))
void clear()
{
for(int i=1;i<=n;i++)
c[i]=0;
return;
}
void add(int x,int y)
{
for(;x;x-=lowbit(x))
c[x]+=y;
return;
}
int query(int x)
{
long long res=0;
for(;x<=n;x+=lowbit(x))
res+=c[x];
return res;
}
#undef lowbit
}; //反向树状数组(后缀和)
} //namespace BinaryIndexTree
namespace Subtask_2{
pair<int,int> p[N];
void Prework()
{
for(int i=1;i<=n;i++)
p[i]={a[i],c[i]}; //a[i]=b[i]
sort(p+1,p+n+1);
return;
}
BinaryIndexTree::BIT_R bit;
void Solve()
{
Prework();
long long ans=0;
for(int i=1;i<=n;i++)
{
bit.add(p[i].second,1);
ans+=bit.query(p[i].second);
}
printf("%lld\n",ans);
bit.clear();
return;
}
} //namespace Subtask_2
Subtask 3
首先,一个特征值包括三个最大值,其组成有以下三种情况:
- 一组数贡献了所有的最大值,即它的 \(a_i,b_i,c_i\) 都是最大的,此时其它的组可有可无,可以忽略。
- 某一组数贡献了两个最大值,另一组数贡献了剩下的一个最大值,此时其他组可有可无,可以忽略。
- 三个最大值分别由三组构成,剩下的组可有可无,可以忽略。
综上所述,构成一个特征值有效的三元组最多只有三个,直接 \(O(N^3)\) 枚举这三/二/一个三元组即可。
点击查看代码
namespace Subtask_3{
const int N_3=505;
bitset<N_1_3> flag[N_1_3][N_1_3];
void ClearData()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
flag[i][j]&=0;
return;
}
void Solve()
{
int ans=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
for(int k=1;k<=j;k++)
{
int x=max({a[i],a[j],a[k]});
int y=max({b[i],b[j],b[k]});
int z=max({c[i],c[j],c[k]});
if(!flag[x][y][z])
{
ans++;
flag[x][y][z]=true;
}
}
printf("%d\n",ans);
ClearData();
return;
}
}
Subtask 4
根据 Subtask 3 中的结论,可以将选择情况分成三类:选择一个组、选择两个组、选择三个组。
选一个或两个组
先来说选择一个组和选择两个组的小常数做法。
还是先将所有组按照 \(a\) 值排序,设当前组为 \(i\),之前组为 \(j\)。
因为已经排过序,所以 \(a_j<a_i\),接下来考虑什么情况下两者可以合成一组特征值。
如果 \(b_j<b_i\) 且 \(c_j<c_i\),那么实际上两者组合后 \(i\) 贡献了全部的数,\(j\) 可以忽略,此时相当于是只选择了一个组。
反之,只要 \(b_j>b_i\) 或 \(c_j>c_i\) 那么两者组合后,\(j\) 就会有贡献,此时这是一个新的特征值。
注意,我代码中枚举时 \(j \le i\),且 if
中判读那条件为大于等于是为了让 \(j\) 能够取到 \(i\),此时相当于计算了只选 \(i\) 的情况(但是只计算了一次)。
点击查看代码
for(int i=1;i<=n;i++) //选择一个或两个组
for(int j=1;j<=i;j++)
if(p[j].y>=p[i].y||p[j].z>=p[i].z) ans++;
选三个组
接下来考虑选了三个组的情况。
选择三个组,必然是一个组贡献了一个最大值,这里设 \(i\) 贡献了 \(a\) 的最大值,\(j\) 贡献了 \(b\) 的最大值,\(k\) 贡献了 \(c\) 的最大值。
上面已经将所有组按照 \(a\) 排过序了,可以只要找 \(j<i,k<i\),那么一定有 \(a_i>a_j,a_k\),保证了 \(i\) 贡献 \(a\) 的最大值。
如何让 \(j\) 贡献 \(b\) 的最大值?因为所有的 \(j<i\) 都是合法的,所以将这 \(i-1\) 个组单独拿出来,按照 \(b\) 排序后再依次扫描,就可以保证对于当前组 \(j\),如果 \(k<j\),那么 \(b_k<b_j\)。当然,还要另行判断 \(b_j>b_i\),才能保证 \(j\) 贡献了 \(b\) 的最大值,可以计入答案;否则,\(b_i>b_j\) 且 \(b_j>b_k\),\(i\) 同时贡献了 \(a\) 和 \(b\) 的最大值,就退化为了只选两组的情况。
而我们要做的,就是在此时合法的 \(k\)(即小于 \(j\) 的 \(k\))中,找到使 \(c_k\) 最大的 \(k\) 的数量,即 \(c_k > \max\{c_i,c_j\}\) 的 \(k\) 的数量。
该问题与 Subtask 2 中的问题相似,同样可以采用后缀和权值树状数组解决,具体地说:
- 在遍历 \(j\) 的时候,先判断 \(b_j\) 是否可以贡献最大值(\(b_j>b_k\) 已经由排序保证,只需判断 \(b_j\) 是否大于 \(b_i\)),如果可以,将 \(\max\{c_i,c_j\}\) 的后缀和累加到答案里。
- 将 \(c_j\) 加入到树状数组当中。
时间复杂度 \(O(N^2 \log N)\)。
具体实现可看代码注释。
点击查看代码
namespace BinaryIndexTree{
struct BIT_R{
int c[N];
#define lowbit(x) ((x)&-(x))
void clear()
{
for(int i=1;i<=n;i++)
c[i]=0;
return;
}
void add(int x,int y)
{
for(;x;x-=lowbit(x))
c[x]+=y;
return;
}
int query(int x)
{
long long res=0;
for(;x<=n;x+=lowbit(x))
res+=c[x];
return res;
}
#undef lowbit
}; //后缀和树状数组
} //namespace BinaryIndexTree
namespace Subtask_4{
struct MJJ{
int x,y,z;
}p[N];
bool cmp(MJJ x,MJJ y)
{
return x.x<y.x;
}
void Prework()
{
for(int i=1;i<=n;i++)
p[i]={a[i],b[i],c[i]};
sort(p+1,p+n+1,cmp); //先将所有数按照a值排序
return;
}
BinaryIndexTree::BIT_R bit; //后缀和树状数组
pair<int,int> tmp[N]; //用以排序
void Solve()
{
Prework();
long long ans=0;
for(int i=1;i<=n;i++) //选择一个或两个组
for(int j=1;j<=i;j++)
if(p[j].y>=p[i].y||p[j].z>=p[i].z) ans++;
for(int i=1;i<=n;i++) //选择三个组
{
for(int j=1;j<i;j++) //此时已经保证a[i]>a[j]且a[i]>a[k],所以不用再考虑a
tmp[j]={p[j].y,p[j].z}; //将所有在i之前的b[j]和c[j]复制一份,用以排序
//tmp的first和second分别表示b[j]和c[j],a[j]不用考虑
//tmp的长度为(i-1)
sort(tmp+1,tmp+(i-1)+1); //排序以保证从前往后扫描j的时候,前面的b小于后面的b
bit.clear();
for(int j=1;j<i;j++)
{
//因为从前向后扫描已经保证b[k]<b[j],所以这里只需要判断b[i]<b[j]即可证明b[j]合法
if(tmp[j].first>p[i].y) //保证b[j]贡献了最大值
ans+=bit.query(max(p[i].z,tmp[j].second)); //保证c[k]>c[i]且c[k]>c[j]的k的数量
//上面其实改成query(max(...)+1)更准确,不过因为c值互不相同,所以这样写也对
bit.add(tmp[j].second,1); //将当前c[j]加入到后缀和树状数组当中
}
}
printf("%lld\n",ans);
return;
}
} //namespace Subtask_4
Subtask 5
这部分其实是上面的代码卡常优化卡过的😅。
因为所有的 \(b\) 是一个排列,所以可以把上面的快排优化为桶排。
主要就是这个,可以优化近一半的时间,这样就可以卡过 Subtask 5 了。
和上面区别不大,就只贴核心代码(选择三个组的代码)了。
点击查看代码
for(int i=1;i<=n;i++) //选择三个组
{
for(int j=1;j<i;j++) //p[j].a<p[i].a
toilet[p[j].y]=p[j].z;
int tmp_idx=0;
for(int j=1;j<=n;j++)
if(toilet[j]) tmp[++tmp_idx]={j,toilet[j]};
for(int j=1;j<=n;j++) toilet[j]=0;
bit.clear();
for(int j=1;j<=tmp_idx;j++)
{
if(tmp[j].first>p[i].y)
ans+=bit.query(max(p[i].z,tmp[j].second));
bit.add(tmp[j].second,1);
}
}
总:70 分代码。
其实只用 Subtask 2 和 Subtask 5 的就够了,不过还是把刚才的都放上来吧。
#include<cstdio>
#include<bitset>
#include<algorithm>
using namespace std;
const int N=2e5+5;
int n,a[N],b[N],c[N];
namespace BinaryIndexTree{
struct BIT_R{
int c[N];
#define lowbit(x) ((x)&-(x))
void clear()
{
for(int i=1;i<=n;i++)
c[i]=0;
return;
}
void add(int x,int y)
{
for(;x;x-=lowbit(x))
c[x]+=y;
return;
}
int query(int x)
{
long long res=0;
for(;x<=n;x+=lowbit(x))
res+=c[x];
return res;
}
#undef lowbit
};
} //namespace BinaryIndexTree
namespace Subtask_2{
pair<int,int> p[N];
void Prework()
{
for(int i=1;i<=n;i++)
p[i]={a[i],c[i]}; //a[i]=b[i]
sort(p+1,p+n+1);
return;
}
BinaryIndexTree::BIT_R bit;
void Solve()
{
Prework();
long long ans=0;
for(int i=1;i<=n;i++)
{
bit.add(p[i].second,1);
ans+=bit.query(p[i].second);
}
printf("%lld\n",ans);
bit.clear();
return;
}
} //namespace Subtask_2
namespace Subtask_3{
const int N_3=505;
bitset<N_3> flag[N_3][N_3];
void ClearData()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
flag[i][j]&=0;
return;
}
void Solve()
{
int ans=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
for(int k=1;k<=j;k++)
{
int x=max({a[i],a[j],a[k]});
int y=max({b[i],b[j],b[k]});
int z=max({c[i],c[j],c[k]});
if(!flag[x][y][z])
{
ans++;
flag[x][y][z]=true;
}
}
printf("%d\n",ans);
ClearData();
return;
}
} //namespace Subtask_3
namespace Subtask_4{
struct MJJ{
int x,y,z;
}p[N];
bool cmp(MJJ x,MJJ y)
{
return x.x<y.x;
}
void Prework()
{
for(int i=1;i<=n;i++)
p[i]={a[i],b[i],c[i]};
sort(p+1,p+n+1,cmp);
return;
}
BinaryIndexTree::BIT_R bit;
pair<int,int> tmp[N];
void Solve()
{
Prework();
long long ans=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
if(p[j].y>=p[i].y||p[j].z>=p[i].z) ans++;
for(int i=1;i<=n;i++)
{
for(int j=1;j<i;j++)
tmp[j]={p[j].y,p[j].z};
sort(tmp+1,tmp+(i-1)+1);
bit.clear();
for(int j=1;j<i;j++)
{
if(tmp[j].first>p[i].y)
ans+=bit.query(max(p[i].z,tmp[j].second));
bit.add(tmp[j].second,1);
}
}
printf("%lld\n",ans);
return;
}
} //namespace Subtask_4
namespace Subtask_5{
struct MJJ{
int x,y,z;
}p[N];
bool cmp(MJJ x,MJJ y)
{
return x.x<y.x;
}
void Prework()
{
for(int i=1;i<=n;i++)
p[i]={a[i],b[i],c[i]};
sort(p+1,p+n+1,cmp);
return;
}
BinaryIndexTree::BIT_R bit;
pair<int,int> tmp[N];
int toilet[N];
int tmp_idx;
void Solve()
{
Prework();
long long ans=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
if(p[j].y>=p[i].y||p[j].z>=p[i].z) ans++;
for(int i=1;i<=n;i++)
{
for(int j=1;j<i;j++)
toilet[p[j].y]=p[j].z;
int tmp_idx=0;
for(int j=1;j<=n;j++)
if(toilet[j]) tmp[++tmp_idx]={j,toilet[j]};
for(int j=1;j<=n;j++) toilet[j]=0;
bit.clear();
for(int j=1;j<=tmp_idx;j++)
{
if(tmp[j].first>p[i].y)
ans+=bit.query(max(p[i].z,tmp[j].second));
bit.add(tmp[j].second,1);
}
}
printf("%lld\n",ans);
return;
}
} //namespace Subtask_5
int main()
{
// freopen("max.in","r",stdin);
// freopen("max.out","w",stdout);
int T; scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
bool is_sub2=true;
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&a[i],&b[i],&c[i]);
if(a[i]!=b[i]) is_sub2=false;
}
if(is_sub2) Subtask_2::Solve();
else if(n<=100) Subtask_3::Solve();
else if(n<=500) Subtask_4::Solve();
else if(n<=2000) Subtask_5::Solve();
else printf("Oh no!\n");
}
return 0;
}
补充内容-正解代码
点击查看代码
#include<cstdio>
#include<bitset>
#include<algorithm>
using namespace std;
const int N=2e5+5;
int n,a[N],b[N],c[N];
namespace BinaryIndexTree{
struct BIT{
int c[N];
#define lowbit(x) ((x)&-(x))
void clear()
{
for(int i=1;i<=n;i++)
c[i]=0;
return;
}
void add(int x,int y)
{
for(;x<=n;x+=lowbit(x))
c[x]+=y;
return;
}
int query(int x)
{
int res=0;
for(;x;x-=lowbit(x))
res+=c[x];
return res;
}
#undef lowbit
};
struct BIT_R{
int c[N];
#define lowbit(x) ((x)&-(x))
void clear()
{
for(int i=1;i<=n;i++)
c[i]=0;
return;
}
void add(int x,int y)
{
for(;x;x-=lowbit(x))
c[x]+=y;
return;
}
int query(int x)
{
int res=0;
for(;x<=n;x+=lowbit(x))
res+=c[x];
return res;
}
#undef lowbit
};
} //namespace BinaryIndexTree
namespace Subtask_2{
pair<int,int> p[N];
void Prework()
{
for(int i=1;i<=n;i++)
p[i]={a[i],c[i]}; //a[i]=b[i]
sort(p+1,p+n+1);
return;
}
BinaryIndexTree::BIT_R bit;
void Solve()
{
Prework();
long long ans=0;
for(int i=1;i<=n;i++)
{
bit.add(p[i].second,1);
ans+=bit.query(p[i].second);
}
printf("%lld\n",ans);
bit.clear();
return;
}
} //namespace Subtask_2
namespace Subtask_3{
const int N_3=505;
bitset<N_3> flag[N_3][N_3];
void ClearData()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
flag[i][j]&=0;
return;
}
void Solve()
{
int ans=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
for(int k=1;k<=j;k++)
{
int x=max({a[i],a[j],a[k]});
int y=max({b[i],b[j],b[k]});
int z=max({c[i],c[j],c[k]});
if(!flag[x][y][z])
{
ans++;
flag[x][y][z]=true;
}
}
printf("%d\n",ans);
ClearData();
return;
}
} //namespace Subtask_3
namespace Subtask_4{
struct MJJ{
int x,y,z;
}p[N];
bool cmp(MJJ x,MJJ y)
{
return x.x<y.x;
}
void Prework()
{
for(int i=1;i<=n;i++)
p[i]={a[i],b[i],c[i]};
sort(p+1,p+n+1,cmp);
return;
}
BinaryIndexTree::BIT_R bit;
pair<int,int> tmp[N];
void Solve()
{
Prework();
long long ans=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
if(p[j].y>=p[i].y||p[j].z>=p[i].z) ans++;
for(int i=1;i<=n;i++)
{
for(int j=1;j<i;j++)
tmp[j]={p[j].y,p[j].z};
sort(tmp+1,tmp+(i-1)+1);
bit.clear();
for(int j=1;j<i;j++)
{
if(tmp[j].first>p[i].y)
ans+=bit.query(max(p[i].z,tmp[j].second));
bit.add(tmp[j].second,1);
}
}
printf("%lld\n",ans);
return;
}
} //namespace Subtask_4
namespace Subtask_5{
struct MJJ{
int x,y,z;
}p[N];
bool cmp(MJJ x,MJJ y)
{
return x.x<y.x;
}
void Prework()
{
for(int i=1;i<=n;i++)
p[i]={a[i],b[i],c[i]};
sort(p+1,p+n+1,cmp);
return;
}
BinaryIndexTree::BIT_R bit;
pair<int,int> tmp[N];
int toilet[N];
int tmp_idx;
void Solve()
{
Prework();
long long ans=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
if(p[j].y>=p[i].y||p[j].z>=p[i].z) ans++;
for(int i=1;i<=n;i++)
{
for(int j=1;j<i;j++)
toilet[p[j].y]=p[j].z;
int tmp_idx=0;
for(int j=1;j<=n;j++)
if(toilet[j]) tmp[++tmp_idx]={j,toilet[j]};
for(int j=1;j<=n;j++) toilet[j]=0;
bit.clear();
for(int j=1;j<=tmp_idx;j++)
{
if(tmp[j].first>p[i].y)
ans+=bit.query(max(p[i].z,tmp[j].second));
bit.add(tmp[j].second,1);
}
}
printf("%lld\n",ans);
return;
}
} //namespace Subtask_5
namespace Subtask_6{
namespace PartialOrder_3{
struct LZX{
int x,y,z;
int id;
}p[N];
bool cmp_x(LZX x,LZX y)
{
if(x.x!=y.x) return x.x<y.x;
else if(x.y!=y.y) return x.y<y.y;
else return x.z<y.z;
}
void Prework()
{
for(int i=1;i<=n;i++)
p[i]={a[i],b[i],c[i],i};
sort(p+1,p+n+1,cmp_x);
return;
}
BinaryIndexTree::BIT bit;
bool cmp_y(LZX x,LZX y)
{
if(x.y!=y.y) return x.y<y.y;
else return x.z<y.z;
}
int ans[N];
void CDQ(int l,int r)
{
if(l==r) return;
int mid=l+r>>1;
CDQ(l,mid),CDQ(mid+1,r);
sort(p+l,p+mid+1,cmp_y);
sort(p+mid+1,p+r+1,cmp_y);
int i=mid+1,j=l;
for(;i<=r;i++)
{
for(;p[j].y<=p[i].y && j<=mid;j++)
bit.add(p[j].z,1);
ans[p[i].id]+=bit.query(p[i].z);
}
for(i=l;i<j;i++)
bit.add(p[i].z,-1);
return;
}
}
namespace PartialOrder_2{
pair<int,int> p[N];
BinaryIndexTree::BIT bit;
long long PO2(const int g[],const int h[])
{
bit.clear();
for(int i=1;i<=n;i++)
p[i]={g[i],h[i]};
sort(p+1,p+n+1);
long long res=0;
for(int i=1;i<=n;i++)
{
long long tmp=bit.query(p[i].second);
res+=tmp*(tmp-1)/2;
bit.add(p[i].second,1);
}
return res;
}
}
void Solve()
{
PartialOrder_3::Prework();
PartialOrder_3::CDQ(1,n);
long long po3=0;
for(int i=1;i<=n;i++)
po3+=PartialOrder_3::ans[i];
long long po2_ab=PartialOrder_2::PO2(a,b);
long long po2_bc=PartialOrder_2::PO2(b,c);
long long po2_ac=PartialOrder_2::PO2(a,c);
long long po3_cpl=0;
for(int i=1;i<=n;i++)
{
int tmp=PartialOrder_3::ans[i];
po3_cpl+=1ll*tmp*(tmp-1)/2;
}
long long ans=0;
ans += 1ll*n;
ans += 1ll*n*(n-1)/2 - po3;
ans += 1ll*n*(n-1)*(n-2)/6 - (po2_ab+po2_bc+po2_ac) + po3_cpl*2;
printf("%lld\n",ans);
for(int i=1;i<=n;i++)
PartialOrder_3::ans[i]=0;
return;
}
}
int main()
{
scanf("%d",&n);
bool is_sub2=true;
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&a[i],&b[i],&c[i]);
if(a[i]!=b[i]) is_sub2=false;
}
if(is_sub2) Subtask_2::Solve();
else if(n<=100) Subtask_3::Solve();
else if(n<=500) Subtask_4::Solve();
else if(n<=2000) Subtask_5::Solve();
else Subtask_6::Solve();
return 0;
}
本文采用 「CC-BY-NC 4.0」 创作共享协议,转载请注明作者及出处,禁止商业使用。
作者:Jerrycyx,原文链接:https://www.cnblogs.com/jerrycyx/p/18498882