恩偶爱皮模拟尸体-31

水博客太快乐了

RT

考场

先大概看了下题。。。
感觉 \(T2\) 比较可做,于是先看是水 \(T2\) ,口胡了一个结论,大概 \(50mins\) 写完了。。。
感觉大概是过了。。。。

然后去水 \(T1\) 又口胡了一个贪心,感觉显然是正确的。。。
考完发现显然是错误的。。。
又大概 \(1h\) 写完了。。。
感觉至少能拿到一半分。。。

然后去看 \(T3\) ,继续口胡,感觉树形结构是显然的(终于口胡对一个了。。。
于是写了树形 \(dp\) 。。。

分数

预估 : \(t1\ 40pts\ +\ t2\ 100pts\ +\ t3\ 45pts\ =\ 185pts\)
实际 : \(t1\ 10pts\ +\ t2\ 10pts\ +\ t3\ 45pts\ =\ 65pts\)
估分严重虚高啊。。。
发现只有 \(t3\) 口胡的是对的。。。
其他口胡的都挂惨了。。。。
以后不要乱口胡了,至少给出一定的证明。。。
没有证明还不如交暴力分高。。。

题解

A. Game

乍一看不是很好做。。。。
考虑如何构造一个合法序列。。。
假设现在已经构造出长度为 \(i\) 的序列,要求第 \(i+1\) 项是什么。。。
显然可以 \(O(n)\) 求出最大有多少个 \(b_{i}\) 大于 \(a_{i}\) ,前 \(i\) 的序列已经求出,也就是说知道在区间 \([i+1,n]\) 中有多少个 \(b_{i}\) 大于 \(a_{i}\)
可以确定当前点对答案是否有贡献,再用权值线段树动态地求出区间 \([i+2,n]\) 中最多的有多少 \(b_{i}\) 大于 \(a_{i}\),大力二分即可。。。
时间复杂度 \(O(n\ log^{2}n)\)

code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
inline int read(){
	int f=1, x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
	while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
	return f*x;
}
int n;
int a[N], b[N], maxn, ans, M;
int l[N<<2], r[N<<2], s[N<<2];
void change(int x, int x1, int x2, int p){
	x+=M-1, l[x]+=x1, r[x]+=x2; x>>=1; int minn;
	for(; x; x>>=1){
		minn=min(l[x<<1], r[x<<1|1]);
		s[x]=s[x<<1]+s[x<<1|1]+minn;
		l[x]=l[x<<1]+l[x<<1|1]-minn;
		r[x]=r[x<<1]+r[x<<1|1]-minn;
	}
}
multiset<int> ul;
int main(void){
	n=read();
	for(int i=1; i<=n; ++i) maxn=max(maxn, a[i]=read());
	for(int i=1; i<=n; ++i) maxn=max(maxn, b[i]=read()), ul.insert(b[i]);
	for(M=1; M<maxn; M<<=1);
	for(int i=1; i<=n; ++i) change(a[i], 1, 0, 1), change(b[i], 0, 1, 1);
	ans=s[1];
	for(int i=1; i<=n; ++i){
		int l=a[i]+1, r=*ul.rbegin(), mid;
		change(a[i], -1, 0, 1);
		while(l<r){
			mid=(l+r+1)>>1;
			change(mid, 0, -1, 1);
			if(s[1]+1==ans) l=mid;
			else r=mid-1;
			change(mid, 0, 1, 1);
		}
		change(l, 0, -1, 1);
		if(l<=r&&1+s[1]==ans) { --ans, printf("%d ", l); ul.erase(ul.find(l)); continue; }
		change(l, 0, 1, 1);
		l=1, r=a[i];
		while(l<r){
			mid=(l+r+1)>>1;
			change(mid, 0, -1, 1);
			if(s[1]==ans) l=mid;
			else r=mid-1;
			change(mid, 0, 1, 1);
		}
		change(l, 0, -1, 1); ul.erase(ul.find(l));
		printf("%d ",l);
	}
	printf("\n");
	return 0;
}

\(ZKW\) 线段树又快又好写。。。

B. Time

考场上口胡,认为肯定是先将最大值移到某个位置,再将最大值两边分别排序。。。
于是维护两边逆序对数量。。。。
显然是错的。。。

正解刚好相反,由于最小值显然会被放到序列的最左侧或最右侧,所以对于每个还未被移动过的最小值,考虑将其移动到最左侧或做右侧,选择代价最小的。。。

显然可以用线段树维护,将一个点移动至最右侧,则显然它右侧所有点的编号都会加一,若移动到最左侧,则显然它左边所有点的编号都会减一,因此可以用线段数维护。。。
每个点维护它本身的至,它在原序列中的位置,以及在被修改后序列中的位置。。。

code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10, INF=0x3f3f3f3f;
inline int read(){
	int f=1, x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
	while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
	return f*x;
}
int n, a[N], l, r, ans;
struct TRE{
	int l, r;
	int lz, min1, p, p1;
}t[N<<2];
void upd(int p){
	t[p].p=t[p<<1].min1 < t[p<<1|1].min1 ? t[p<<1].p : t[p<<1|1].p;
	t[p].p1=t[p<<1].min1 < t[p<<1|1].min1 ? t[p<<1].p1 : t[p<<1|1].p1;
	if(t[p<<1].min1==t[p<<1|1].min1) t[p].p1= min(t[p<<1].p1-l, r-t[p<<1].p1) < min(t[p<<1|1].p1-l, r-t[p<<1|1].p1) ? t[p<<1].p1 : t[p<<1|1].p1;
	if(t[p<<1].min1==t[p<<1|1].min1) t[p].p= min(t[p<<1].p1-l, r-t[p<<1].p1) < min(t[p<<1|1].p1-l, r-t[p<<1|1].p1) ? t[p<<1].p : t[p<<1|1].p;
	t[p].min1=min(t[p<<1].min1, t[p<<1|1].min1);
}
void pushdown(int p){
	if(!t[p].lz) return;
	int ls=p<<1, rs=p<<1|1;
	t[ls].p1+=t[p].lz, t[rs].p1+=t[p].lz;
	t[ls].lz+=t[p].lz, t[rs].lz+=t[p].lz;
	t[p].lz=0;
}
void built(int l, int r, int p){
	t[p].l=l, t[p].r=r;
	if(l==r) { t[p].min1=a[l]; t[p].p=l; t[p].p1=l; return; }
	int mid=(l+r)>>1;
	built(l, mid, p<<1); built(mid+1, r, p<<1|1);
	upd(p);
}
void erase(int x, int p){
	if(t[p].l==t[p].r) { t[p].p1=0; t[p].min1=INF; return; }
	pushdown(p); int mid=(t[p].l+t[p].r)>>1;
	if(x<=mid) erase(x, p<<1);
	else erase(x, p<<1|1); upd(p);
}
void deal(int l, int r, int x, int p){
	if(l<=t[p].l&&r>=t[p].r) { t[p].p1+=x; t[p].lz+=x; return; }
	pushdown(p); int mid=(t[p].l+t[p].r)>>1;
	if(l<=mid) deal(l, r, x, p<<1);
	if(r>mid) deal(l, r, x, p<<1|1);
}
int main(void){
	n=read(); l=1, r=n;
	for(int i=1; i<=n; ++i) a[i]=read();
	built(1, n, 1); int id, id1;
	for(int i=1; i<=n; ++i){
		id=t[1].p1; id1=t[1].p; ans+=min(id-l, r-id);
		if(l-id<r-id) deal(1, id1, 1, 1), ++l;
		else deal(id1, r, -1, 1), --r;
		erase(id1, 1);
	}
	printf("%d\n", ans);
	return 0;
}

C. Cover

由于区间间只有包含与不相交的关系,因此显然是树形结构的。。。。
可以考虑建出树,大力树形 \(dp\) 即可。。。
\(f_{i,j}\) 表示 \(i\) 号点的子树内,共选了 \(j\) 层节点的最大答案。
先将子树的贡献转移过来,再考虑当前点的共享。。。
显然转移方程为:
\(f_{i,j}\ =\ max(f_{i,j-1}+a_{i}, f_{i, j})\)
对于 \(f_{i}\) ,显然它是左上凸的。。。。
即它的差分表是单调不增的。。。
如何理解?
在选择第 \(j\) 层的时候显然会选择当前能选的最大一层
\(f_{i,j+1}\ -\ f_{i,j}\ <\ f_{i,j}\ -\ f_{i,j-1}\)
。。。。简单易懂。。。。

于是选择维护差分表,可以用堆或 \(vector\) 维护。。
转移子树贡献相当与将差分表相加,当前节点贡献相当与按照权值插入到堆中。。。
直接转移显然会 \(T\) 。。。
考虑用长连剖分优化。。。。
天知道出题人是怎么想到长连剖分的。。。
对于每个重儿子,直接用 \(swap\)\(O(1)\) 转移。。。
对于每个轻儿子,直接暴力转移,将对应权值相加即可。。。
复杂度是长连剖分的玄学复杂度。。。

code
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+10;
#define ll long long
inline int read(){
	int f=1, x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
	while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
	return f*x;
}
int n, m, fa[N];
ll ans;
struct S{
	int l, r, len, a;
	friend bool operator < (const S &x, const S &y) { return x.len > y.len; }
}s[N];
vector<int> l[N];
priority_queue<ll> q[N];
struct TRE{
	int l, r, id;
}t[N<<3];
void built(int l, int r, int p){
	t[p].l=l, t[p].r=r;
	if(l==r) return; int mid=(l+r)>>1;
	built(l, mid, p<<1), built(mid+1, r, p<<1|1);
}
int check(int l, int r, int p){
	if(l<=t[p].l&&r>=t[p].r) return t[p].id;
	int mid=(t[p].l+t[p].r)>>1;
	if(l<=mid&&r>mid) return max(t[p].id, max(check(l, r, p<<1), check(l, r, p<<1|1)));
	if(l<=mid) return max(t[p].id, check(l, r, p<<1));
	return max(t[p].id, check(l, r, p<<1|1));
}
void cover(int l, int r, int x, int p){
	if(l<=t[p].l&&r>=t[p].r) { t[p].id=x; return; }
	int mid=(t[p].l+t[p].r)>>1;
	if(l<=mid) cover(l, r, x, p<<1);
	if(r>mid) cover(l, r, x, p<<1|1);
}
int dep[N], son[N], top, mx[N];
ll stk[N];
void dfs(int u){
	for(int v : l[u]){
		dep[v]=dep[u]+1; dfs(v);
		if(mx[v]>mx[son[u]]) son[u]=v;
	}
	if(son[u]) swap(q[u], q[son[u]]);
	for(int v : l[u]){
		if(son[u]==v) continue; top=0;
		while(!q[v].empty()){
			stk[++top]=q[v].top()+q[u].top();
			q[u].pop(); q[v].pop();
		}
		while(top) q[u].push(stk[top--]);
	}
	q[u].push(s[u].a); mx[u]=max(dep[u], mx[son[u]]);
}
int main(void){
	n=read(), m=read();
	int x, y, z;
	for(int i=1; i<=m; ++i){
		x=read(), y=read(), z=read();
		s[i]=(S){x, y, y-x, z};
	}
	sort(s+1, s+1+m); built(1, n+1, 1);
	for(int i=1; i<=m; ++i){
		x=check(s[i].l, s[i].r, 1);
		cover(s[i].l, s[i].r, i, 1);
		l[x].push_back(i);
	}
	dfs(0);
	for(int i=1; i<=m; ++i){
		ans+=q[0].top(); q[0].pop();
		printf("%lld ", ans);
	}
	printf("\n");
	return 0;
}
posted @ 2021-08-05 21:20  Cyber_Tree  阅读(45)  评论(2编辑  收藏  举报