神牛出品,必属精品
Tree Inversion
1.暴力模拟,应该能得50分。
2.通过数据结构优化,用树状数组,可以转化为求 dfs 序,T(u) 的子树的 dfs 序的范围为 [dfnu,dfnu+sizeu−1],最后转化为求二维偏序。(好像也没啥好说的)
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=500010;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n;
ll f[N],g[N],a[N],h[N],tr[N];
vector<ll>v[N];
void add(ll x,ll t){
for(int i=x;i<=n;i+=(i&(-i)))tr[i]+=t;
}
ll qr(ll x){
ll ans=0;
for(int i=x;i>0;i-=(i&(-i)))ans+=tr[i];
return ans;
}
void dfs1(ll x,ll fa){
a[x]=-qr(x-1);
h[x]=-qr(fa-1);
for(auto y:v[x])if(y!=fa)dfs1(y,x);
add(x,1);
a[x]+=qr(x-1);
h[x]+=qr(fa-1);
f[x]=a[x];
for(auto y:v[x])if(y!=fa)f[x]+=f[y];
}
void dfs2(ll x,ll fa){
for(auto y:v[x]){
if(y==fa)continue;
g[y]=g[x]-h[y]+(y-a[y]-1);
dfs2(y,x);
}
}
int main(){
cin>>n;
for(int i=1,x,y;i<n;i++){
cin>>x>>y;
v[x].push_back(y);
v[y].push_back(x);
}
dfs1(1,0);
g[1]=f[1];
dfs2(1,0);
for(int i=1;i<=n;i++)cout<<g[i]<<" ";
return 0;
}
Odd Subarrays
1.要找到尽可能多的逆序对数为奇数的段,为了分成尽可能多的段,我们先将序列 b 中的每个数都单独分成一段[b1] [b2] [b3] ... [bn] ,这样显然序列数最多。
2.接下来考虑如何将这些序列组合起来时的出现逆序对数为奇数的段,由于 1 是奇数,所以显然当 bi>b i+1 时,将这两个数组合成一个序列可以增加逆序对数为奇数序列数的数量,同时剩下的序列数会尽可能的多,使得逆序对数为奇数的序列数可以保证尽可能多。
所以考虑贪心,当 bi>b i+1 时,将这两个数组合成一个序列,答案加一。
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,a[N],sum;
int main(){
cin>>n;
sum=0;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]<a[i-1]){
sum++;
if(i<n) cin>>a[i+1];
i++;
}
}
cout<<sum;
return 0;
}
Range Sorting (Hard Version)
1.一开始想打暴力模拟,用冒泡排序累加答案,然后对了2个点,(其他的寄了)
代码如下:
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e6+10;
int n,ans;
int a[N];
signed main(){
scanf("%lld",&n);
for(int i=0;i<n;i++){
scanf("%lld",&a[i]);
}
for(int i=0;i<n-1;i++){//冒泡排序
bool flag=false;
for(int j=0;j<n-i-1;j++){
if(a[j]>a[j+1]){
ans+=a[j]-a[j+1];
swap(a[j],a[j+1]);
flag=true;
}
}
if(!flag) break;
}
printf("%lld",ans);
return 0;
}
2.先想想部分分,考虑下dp,记 f(l,r) 为子序列 [l,r] 的代价。转移就枚举中间节点 k,若 max i=l k{ai}<min i=k+1 r {ai} 则用 f(l,k)+f(k+1,r) 更新 f(l,r),最后再求和。
再考虑别的方法,对于序列 a,记 k 为最多分得的段数,则 a 的代价为 n−k(n 为 a 的大小) 。
统计所有区间长度和,再减去后面的。对于区间 [l,r],若存在 x 满足 max i=l x {ai}<min i=x+1 r {ai} 则可以分割一次。找出所有 x,计算每个分割点对应的区间个数。
枚举 ai 作为 max,找到 i 前面第一个比 ai 大的数 ax和后面第一个比它大的数 ay,找到 y 后面第一个比 ai 小的数 az。有这个分割点的区间个数为 (i−x)×(z−y),枚举找到 x,y,z 。
进一步优化,用st表+二分(用set也行),然后就能过了(其实还可以继续优化)。
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
int T,n,a[N],f1[N][20],f2[N][20];
void init(){
for(int j=1;j<=19;j++){
for(int i=1;i+(1<<j)-1<=n;i++){
f1[i][j]=max(f1[i][j-1],f1[i+(1<<(j-1))][j-1]);
f2[i][j]=min(f2[i][j-1],f2[i+(1<<(j-1))][j-1]);
}
}
}
int query_max(int l,int r){
int k=log2(r-l+1);
return max(f1[l][k],f1[r-(1<<k)+1][k]);
}
int query_min(int l,int r){
int k=log2(r-l+1);
return min(f2[l][k],f2[r-(1<<k)+1][k]);
}
int main(){
scanf("%d",&T);
while(T--){
for(int j=1;j<=19;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
f1[i][j]=f2[i][j]=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),f1[i][0]=f2[i][0]=a[i];
init();
long long ans=0;
for(int i=1;i<=n;i++)
ans+=1ll*(i-1)*(n-i+1);
for(int i=1;i<=n;i++){
int ll=0,rl=0,rr=n+1;
int l=i+1,r=n;
while(l<=r){
int mid=(l+r)>>1;
if(query_min(i+1,mid)<a[i])
r=mid-1,rr=mid;
else
l=mid+1;
}
l=1,r=i-1;
while(l<=r){
int mid=(l+r)>>1;
if(query_min(mid,i-1)<a[i]) l=mid+1,rl=mid;
else r=mid-1;
}
l=1,r=rl-1;
while(l<=r){
int mid=(l+r)>>1;
if(query_max(mid,rl-1)>a[i]) l=mid+1,ll=mid;
else r=mid-1;
}
ans-=1ll*(rr-i)*(rl-ll);
}
printf("%lld\n",ans);
}
return 0;
}
Final Stage
1.先看看特殊性质,对于5%的数据,c=0,由于A是先手,c=0则A无法进行操作,所以直接输出m行B即可(没仔细分析,直接输出了m行A骗分,结果没骗到)
2.第一档暴力,记先手赢、输、平分别为 1,0,−1。考虑 DP,记 fi,x 为:有 x 个石子,从 i 到 n 操作一遍得到的结果。
第二档(其实思路已经接近正解了),先考虑最后一次操作的情况,发现前面是 01 段交替,后面都是 −1,每次会新增一段。i+1→i 则所有 0 段的下端点加 Li,上端点加 Ri,然后 0 变 1,并将其他位置变为 0,上面没数的为 −1。考虑维护段的变化并维护端点。将重叠的部分合并。
3.正解:用set维护端点的变化,维护两个set,一个记录奇数位,一个记录偶数位。全局加打个标记。0 变 1 交换两个set 。最后插入 Li−1。每次合并都会减少一段,只要快速找到需要合并的端点。维护相邻两个端点的差,每次全局加,然后判断是否有端点差 ≤0,是则合并。(注意合并节点时要保留一个)
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+15;
ll l[N],r[N],x[N],tl,tr,ans[N];
int n,q,pr[N],sf[N];
set<pair<ll,int>> ql,qr;
signed main(){
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%lld%lld",&l[i],&r[i]);
int hd=1,tot=2;
sf[1]=2,pr[2]=1,x[2]=l[n];
ql.insert({l[n],1});
for(int o=n-1;o;--o){
tl+=l[o],tr+=r[o];
while(qr.size()&&qr.begin()->first<=tr-tl){
int i=qr.begin()->second,j=sf[i];
qr.erase(qr.begin());
if(!sf[j]){
sf[i]=0;
continue;
}
if(!pr[i]) hd=sf[j];
sf[pr[i]]=sf[j],pr[sf[j]]=pr[i];
if(pr[i]) ql.erase({x[i]-x[pr[i]],pr[i]});
if(sf[j]) ql.erase({x[sf[j]]-x[j],j});
if(pr[i]&&sf[j]) ql.insert({x[sf[j]]-x[pr[i]],pr[i]});
}
swap(tl,tr),swap(ql,qr);
++tot,pr[hd]=tot,sf[tot]=hd;
x[tot]=-tl,ql.insert({x[hd]-x[tot],tot}),hd=tot;
}
int m=0;
for(int i=hd;i;i=sf[i]) ++m,ans[m]=x[i]+(m&1?tl:tr);
for(int i=1;i<=m;++i) cerr<<ans[i]<<" \n"[i==m];
scanf("%d",&q);
for(ll z;q--;){
scanf("%lld",&z);
int i=upper_bound(ans+1,ans+m+1,z)-ans-1;
puts(i==m?"Draw":(i&1?"Bob":"Alice"));
}
return 0;
}