11.7比赛总结

神牛出品,必属精品

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;
}
相关推荐
cwj&xyp21 分钟前
Python(二)str、list、tuple、dict、set
前端·python·算法
无 证明30 分钟前
new 分配空间;引用
数据结构·c++
xiaoshiguang35 小时前
LeetCode:222.完全二叉树节点的数量
算法·leetcode
爱吃西瓜的小菜鸡5 小时前
【C语言】判断回文
c语言·学习·算法
别NULL5 小时前
机试题——疯长的草
数据结构·c++·算法
TT哇5 小时前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯
ZSYP-S6 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
yuanbenshidiaos6 小时前
C++----------函数的调用机制
java·c++·算法
唐叔在学习6 小时前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
ALISHENGYA7 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法