noip模拟31 Game Time Cover
考场上顺序开题。
\(\mathrm{A.}\mathbb{Game}\):数据结构
\(\mathrm{B.}\mathbb{Time}\):数据结构
\(\mathrm{C.}\mathbb{Cover}\):树形 \(\mathrm{dp}\)
首看 \(\mathrm{A}\),发现很容易求出其中一种情况,于是又绕进去了,不过在大约一个小时的时候发现了亿些问题,于是便转 \(\mathrm{B}\) 了。(事实上按照这个思路是能打一部分分的,但是最后也没调出来)
看 \(\mathrm{B}\) 时,也有一定的思路,由于以前做过一道类似的题,便考虑最大值,期间也考虑过最小值,但当时并不知道如何贪心,于是便叉掉了。
\(\mathrm{C}\) 没看几眼,只看出来可以建成树的形式,没有细想。
最后的一个小时主要还是卡在 \(\mathrm{A}\) 上了,数据结构还是应用的不够熟练,应该多做些题,大胆尝试使用数据结构(
对于贪心,应该在证明正确性后再尝试,不要等到写完了才发现不对,浪费时间(
估分:\([0,100]+0+0=[0,100]\)
实际:\(10+0+0=10\)
还是说正解吧(
A.Game
这题考场上想的与正解的前半部分比较相似,但后面贪心的做法是假的,对拍几组就很容易被叉掉。
正解
建一棵权值线段树,把 \(a\) 和 \(b\) 都放进去,先算出最高的得分 \(ans\),并找到一组满足解,然后再对每一位进行贪心,至于 \(check\) 的过程则用权值线段树实现。
具体地说,对于每一位,先对 \([a_i+1,\max{b}]\) 进行二分,若找不到满足的值,再对 \([1,a_i]\) 进行二分,此时必能找到值。
权值线段树中维护三个值:\(a,b,s\),分别表示 \(a\) 的尚未匹配的个数,\(b\) 的尚未匹配的个数与已匹配的个数。
\(check\) 时,先把 \(a_i\) 和 \(mid\) 对应的值挖掉(即 \(-1\)),然后每次询问 \(tree_1(s)\) 的值,若是在第一个二分中,\(check\) 即是 \([tree_1(s)+1=ans]\),若是在第二个,\(check\) 则是 \([tree_1(s)=ans]\)
剩下的就是亿些细节问题。
记得要在二分完判一下是否满足 \(l=r\),有可能在二分前就已经使得 \(l>r\) 了。
时间复杂度为 \(O(n\log^2n)\),常数有亿点大,推荐使用 \(\mathrm{zkw}\) 线段树或者卡常或换一个编译器。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n;
int a[N],b[N],book[N];
int mx,ans;
inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*f;
}
struct Tree
{
	int l,r;
	int a,b,s;
}tree[N*4];
void pushup(int p)
{
	int k=min(tree[p*2].a,tree[p*2+1].b);
	tree[p].s=tree[p*2].s+tree[p*2+1].s+k;
	tree[p].a=tree[p*2].a+tree[p*2+1].a-k;
	tree[p].b=tree[p*2].b+tree[p*2+1].b-k;
	return;
}
void build(int p,int l,int r)
{
	tree[p].l=l;
	tree[p].r=r;
	if(l==r) return;
	int mid=(l+r)/2;
	build(p*2,l,mid);
	build(p*2+1,mid+1,r);
	return;
}
int ask(int p,int l,int r)
{
	if(tree[p].l>=l&&tree[p].r<=r) return tree[p].s;
	int mid=(tree[p].l+tree[p].r)/2;
	int sum=0;
	if(l<=mid) sum+=ask(p*2,l,r);
	if(r>=mid+1) sum+=ask(p*2+1,l,r);
	return sum;
}
void change(int p,int l,int r,int a,int b)
{
	if(tree[p].l>=l&&tree[p].r<=r)
	{
		tree[p].a+=a;
		tree[p].b+=b;
		return;
	}
	int mid=(tree[p].l+tree[p].r)/2;
	if(l<=mid) change(p*2,l,r,a,b);
	if(r>=mid+1) change(p*2+1,l,r,a,b);
	pushup(p);
	return;
}
int main()
{
	n=read();
	build(1,1,N);
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		change(1,a[i],a[i],1,0);
	}
	for(int i=1;i<=n;i++)
	{
		b[i]=read();
		mx=max(mx,b[i]);
		book[b[i]]++;
		change(1,b[i],b[i],0,1);
	}
	ans=ask(1,1,N);
	for(int i=1;i<=n;i++)
	{
		while(!book[mx]) mx--;
		change(1,a[i],a[i],-1,0);
		int l=a[i]+1,r=mx,nw;
		while(l<r)
		{
			int mid=(l+r+1)/2;
			change(1,mid,mid,0,-1);
			nw=ask(1,1,N);
			if(nw+1==ans) l=mid;
			else r=mid-1;
			change(1,mid,mid,0,1);
		}
		change(1,l,l,0,-1);
		nw=ask(1,1,N);
		if(nw+1==ans&&l==r)
		{
			book[l]--;
			ans--;
			printf("%d ",l);
			continue;
		}
		change(1,l,l,0,1);
		l=1,r=a[i];
		while(l<r)
		{
			int mid=(l+r+1)/2;
			change(1,mid,mid,0,-1);
			nw=ask(1,1,N);
			if(nw==ans) l=mid;
			else r=mid-1;
			change(1,mid,mid,0,1);
		}
		change(1,l,l,0,-1);
		printf("%d ",l);
		book[l]--;
	}
	printf("\n");
	return 0;
}
B.Time
考场上考虑答案为最大值移动后所分成的两个区间以及最大值移动的操作数之和,以为最大值移动的次数越少越好,很明显是假的(
正解
考虑最小值,只能在序列的最左边或是最右边,每次贪心的选择更优(操作数更少)的那一边,将操作数累计到答案里,再去掉这个点,解决子问题。
用 \(\mathrm{deque}\) 以值为下标,把位置存进去,按照值域从小到大扫一遍。
需要单独考虑有值相同的情况,每次取出队首与队尾,比较它们谁更优,然后再将其弹出。
由于需要查询删去数之后的排名,因此用一个线段树(或树状数组),来支持单点修改和区间查询的操作。
时间复杂度为 \(O(n\log n)\)
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n;
int l,r,mx;
deque<int> q[N];
long long ans;
struct Tree
{
	int l,r;
	int sum;
}tree[N*4];
void pushup(int p)
{
	tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;
	return;
}
void build(int p,int l,int r)
{
	tree[p].l=l;
	tree[p].r=r;
	if(l==r)
	{
		tree[p].sum=1;
		return;
	}
	int mid=(l+r)/2;
	build(p*2,l,mid);
	build(p*2+1,mid+1,r);
	pushup(p);
	return;
}
int ask(int p,int l,int r)
{
	if(tree[p].l>=l&&tree[p].r<=r) return tree[p].sum;
	int mid=(tree[p].l+tree[p].r)/2;
	int sum=0;
	if(l<=mid) sum+=ask(p*2,l,r);
	if(r>=mid+1) sum+=ask(p*2+1,l,r);
	return sum;
}
void change(int p,int l,int r,int d)
{
	if(tree[p].l>=l&&tree[p].r<=r)
	{
		tree[p].sum+=d;
		return;
	}
	int mid=(tree[p].l+tree[p].r)/2;
	if(l<=mid) change(p*2,l,r,d);
	if(r>=mid+1) change(p*2+1,l,r,d);
	pushup(p);
	return;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		q[x].push_back(i);
		mx=max(mx,x);
	}
	build(1,1,n);
	for(int i=1;i<=mx;i++)
	{
		while(q[i].size())
		{
			int l=q[i].front(),r=q[i].back();
			int a=ask(1,1,l-1),b=ask(1,r+1,n);
			if(a<b)
			{
				ans+=a;
				change(1,l,l,-1);
				q[i].pop_front();
			}
			else
			{
				ans+=b;
				change(1,r,r,-1);
				q[i].pop_back();
			}
		}
	}
	printf("%d\n",ans);
	return 0;
}
/*
(12)
{7
2 6 1 3 4 5 7}
*/
C.Cover
考场上看到“彩灯能够覆盖的区间两两之间只有包含和不相交的关系”时,就想到要转换成树上问题了,但当时觉得没有前两题好写,于是便连暴力都没写。事实上确实没前两题好写
正解
首先,将所有的区间都转换成节点,由于只有包含或不相交的关系,便能建成一棵树。
设 \(f_{u,k}\) 表示节点 \(u\)(所代表的区间)覆盖 \(k\) 次的最大美观程度。
容易得到 \(\mathrm{dp}\) 方程:
发现时间复杂度有问题。
容易发现 \(f_u\) 的差分表是单调不增的,(具体原因可见此处),便可以用优先队列维护差分表。
使用长链剖分,可以将时间复杂度降到 \(O(n)\),优先队列一次 \(push\) 和 \(pop\) 的复杂度为 \(O(\log n)\),每一次转移时做了 \(\log\) 次,故总复杂度为 \(O(n\log^2n)\)
code
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
#define int long long
int n,m;
struct In
{
	int l,r,a;
}e[N];
vector<int> q[N];
priority_queue<int> q1[N];
int mxdep[N],t[N];
long long ans=0;
int p;
bool cmp(In x,In y)
{
	return x.r-x.l>y.r-y.l;
}
struct Tree
{
	int l,r;
	int mx,id;
}tree[N*4];
void build(int p,int l,int r)
{
	tree[p].l=l;
	tree[p].r=r;
	if(l==r) return;
	int mid=(l+r)/2;
	build(p*2,l,mid);
	build(p*2+1,mid+1,r);
}
void pushup(int p)
{
	tree[p].mx=max(tree[p*2].mx,tree[p*2+1].mx);
	return;
}
long long ask(int p,int l,int r)
{
	if(l<=tree[p].l&&r>=tree[p].r) return tree[p].id;
	int mid=(tree[p].l+tree[p].r)/2;
	long long val=tree[p].id;
	if(l<=mid) val=max(val,ask(p*2,l,r));
	if(r>=mid+1) val=max(val,ask(p*2+1,l,r));
	return val;
}
void change(int p,int l,int r,int d)
{
	if(l<=tree[p].l&&r>=tree[p].r)
	{
		tree[p].id=d;
		return;
	}
	int mid=(tree[p].l+tree[p].r)/2;
	if(l<=mid) change(p*2,l,r,d);
	if(r>=mid+1) change(p*2+1,l,r,d);
	pushup(p);
}
void dfs(int u)
{
	mxdep[u]=1;
	int son=0;
	for(int v:q[u])
	{
		dfs(v);
		mxdep[u]=max(mxdep[u],mxdep[v]+1);
		if(mxdep[v]>mxdep[son]) son=v;
	}
	swap(q1[u],q1[son]);
	for(int v:q[u])
	{
		if(v==son) continue;
		while(!q1[v].empty())
		{
			t[++p]=q1[u].top()+q1[v].top();
			q1[u].pop();
			q1[v].pop();
		}
		while(p) q1[u].push(t[p--]);
	}
	q1[u].push(e[u].a);
	return;
}
signed main()
{
	cin>>n>>m;
	e[m+1]=(In){1,n,0};
	for(int i=1;i<=m;i++) cin>>e[i].l>>e[i].r>>e[i].a;
	sort(e+1,e+m+2,cmp);
	build(1,1,n);
	for(int v=2;v<=m+1;v++)
	{
		int u=ask(1,e[v].l,e[v].r);
		u=u?u:m+2;
		change(1,e[v].l,e[v].r,v);
		q[u].push_back(v);
	}
	dfs(m+2);
	for(int i=1;i<=m;i++)
	{
		ans+=q1[m+2].top();
		q1[m+2].pop();
		cout<<ans<<" ";
	}
	cout<<endl;
	return 0;
}

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号