noip31

T1

关于我考场上乱冲平衡树这件sb

很快就冲了出来

然后手抖打错样例,把我hack了

sb字典序

正解:

先不考虑字典序问题,先将最大分数找出来,然后按照顺序考虑每一个位置填什么那个数能让分数尽可能的大,题解说显然,这样的数是具有单调性的,所以可以二分来求。

然后就可以用线段树来维护最大分数,然后对每一位二分,来找出字典序大的方案即可。

代码缩进可能有亿一点点怪,不要在意。

附带精美调试信息

Code
#include<set>
#include<cstdio>
#include<algorithm>
#define MAX 100010
#define re register
#define INF INT_MAX
using std::multiset;
namespace OMA
{
   multiset<int>my;
   multiset<int>::iterator it;
   int n,a[MAX],b[MAX],ans;
   struct stream
   {
     template<typename type>inline stream &operator >>(type &s)
     {
	   int w=1; s=0; char ch=getchar();
	   while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
	   while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
	   return s*=w,*this;
     }
   }cin;
   struct Segment_Tree
   {
	 struct TREE
	 {
		int ans;
		int l,r;
		int vl,vr;
	 }st[MAX<<2];
	 inline int ls(int p)
	 { return p<<1; }
	 inline int rs(int p)
	 { return p<<1|1; }
	 inline int min(int a,int b)
	 { return a<b?a:b; }
	 inline void Push_up(int p)
	 {
		int nim = min(st[ls(p)].vr,st[rs(p)].vl);
		st[p].ans = st[ls(p)].ans+st[rs(p)].ans+nim;
		st[p].vl = st[ls(p)].vl+st[rs(p)].vl-nim;
		st[p].vr = st[ls(p)].vr+st[rs(p)].vr-nim;
	 }
	 inline void build(int p,int l,int r)
	 {
		st[p].l = l,st[p].r = r;
		if(l==r)
		{ return ; }
		int mid = (l+r)>>1;
		build(ls(p),l,mid),build(rs(p),mid+1,r);
	 }
	 inline void update(int p,int pos,int vl,int vr)
	 {
		if(st[p].l==st[p].r)
		{ st[p]. vl += vl,st[p]. vr += vr; return ; }
		int mid = (st[p].l+st[p].r)>>1;
		if(pos<=mid)
		{ update(ls(p),pos,vl,vr); }
	    else
		{ update(rs(p),pos,vl,vr); }
		Push_up(p);
	 }
}Tree;
signed main()
{
        cin >> n;
		Tree.build(1,1,MAX);
        for(re int i=1; i<=n; i++)
        { cin >> b[i],Tree.update(1,b[i],0,1); /*printf("b[%d]=%d ",i,b[i]);*/ }
		//printf("\n");
        for(re int i=1; i<=n; i++)
		{ cin >> a[i],Tree.update(1,a[i],1,0),my.insert(a[i]); /*printf("a[%d]=%d ",i,a[i]);*/ }
		//printf("\n");
		//printf("marks=%d\n",Tree.st[1].ans);
		ans = Tree.st[1].ans;
		for(re int i=1; i<=n; i++)
		{
			//printf("i=%d\n",i);
			Tree.update(1,b[i],0,-1);
			int l = b[i]+1,r = *--my.end();
			while(l<r)
			{
				int mid = (l+r+1)>>1;
				Tree.update(1,mid,-1,0);
				if(Tree.st[1].ans+1==ans)
				{ l = mid; }
				else
				{ r = mid-1; }
				Tree.update(1,mid,1,0);
			}
			Tree.update(1,l,-1,0);
			if(l<=r&&Tree.st[1].ans+1==ans)
			{
				printf("%d ",l),ans--;
				if(((it = my.find(l))!=my.end()))
			    { my.erase(it); }
			}
			else
			{
				Tree.update(1,l,1,0);
				l = 1,r = b[i];
				while(l<r)
				{
					int mid = (l+r+1)>>1;
				    Tree.update(1,mid,-1,0);
				    if(Tree.st[1].ans==ans)
					{ l = mid; }
					else
					{ r = mid-1; }
					Tree.update(1,mid,1,0);
				}
				Tree.update(1,l,-1,0);
				printf("%d ",l);
				if(((it = my.find(l))!=my.end()))
				{ my.erase(it); }
			}
		}
	    return 0;
   }
}
signed main()
{ return OMA::main(); }

T2

直接逆序对,30pts。

正解:

发现,序列中的最小值一定是在序列的最左侧或最右侧,显然,将最小值挪到这两端中的一个后,不会产生逆序对,所以可以将最小值挪到,最左/右中更优的位置。

\(deque\) 将每个数出现的位置存下来,直接枚举值域,因为根据上面的贪心策略,要优先更新值更小的,每次更新肯定是从当前这个数出现的位置最左端或最右端开始进行,用 \(BIT\) 维护一下移动对答案造成的影响即可。

发现STL里的 \(deque\) 太慢,可以手写一个为了竞争最快,那么手写的位置数组需要开多大呢? 在WYZG的怂恿下经过我的一波测试疯狂提交,发现最多不超过十个。

考试时请不要这么做

旁边的WYZG:开到8试试

Code
#include<cstdio>
#define MAX 100010
#define re register
namespace OMA
{
   int n;
   int ans,xam,nim;
   //deque<int>q[MAX];
   struct deque
   {
	 int q[10],l,r;
	 deque()
	 { l = 1,r = 0; }
	 inline void push_back(int val)
	 { q[++r] = val; }
	 inline int front()
	 { return q[l]; }
	 inline int back()
	 { return q[r]; }
	 inline void pop_front()
	 { l++; }
	 inline void pop_back()
	 { r--; }
	 inline bool empty()
	 { return l>r; }
   }q[MAX];
   struct BIT
   {
	 int tree[MAX];
	 inline int lowbit(int x)
	 { return x&-x; }
	 inline void update(int x,int cnt)
	 {
	   for(re int i=x; i<=n; i+=lowbit(i))
	   { tree[i] += cnt; }
	 }
	 inline int query(int x)
	 {
	   int res = 0;
	   for(re int i=x; i; i-=lowbit(i))
	   { res += tree[i]; }
	   return res;
	 }
   }BIT;
   inline int read()
   {
	 int s=0,w=1; char ch=getchar();
	 while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
	 while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
	 return s*w;
   }
   inline int max(int a,int b)
   { return a>b?a:b; }
   inline int min(int a,int b)
   { return a<b?a:b; }
   signed main()
   {
	 n = read();
	 for(re int i=1,a; i<=n; i++)
	 { xam = max(xam,a = read()),nim = min(nim,a),q[a].push_back(i),BIT.update(i,1); }
	 for(re int i=nim; i<=xam; i++)
	 {
	   while(!q[i].empty())
	   {
		 int p1 = q[i].front(),p2 = q[i].back();
		 int cnt1 = BIT.query(p1-1),cnt2 = BIT.query(n)-BIT.query(p2);
		 if(cnt1<cnt2)
		 { ans += cnt1,BIT.update(p1,-1),q[i].pop_front(); }
		 else
		 { ans += cnt2,BIT.update(p2,-1),q[i].pop_back(); }
	   }
	 }
	 printf("%d\n",ans);
	 return 0;
   }
}
signed main()
{ return OMA::main(); }

T3

区间的包含和不相交关系,显然是可以建一颗树来表示这种关系的,然后就可以愉快的用树形dp来解决此题。

然而考场上并没有写出来,丢了个sb暴力,样例还一下子就过了,导致我就去看前边的题了。

正解:

仍然是上边的建树表示区间之间的关系,仍然是dp。

\(dp_{i,j}\) 表示在以 \(i\) 为根的子树中点被覆盖的次数最多为 \(j\) 时的最大美观程度,转移时,首先将所有子树的答案合并,然后考虑点 \(i\) 的贡献

然后就有一个比较显然的dp方程:

\[dp_{i,j}=\max(dp_{i,j},dp_{i,j-1}+a_{i}) \]

然后就可以维护 \(dp_{i}\) 的差分表,子树合并对应将差分表相加,点 \(i\) 的贡献则相当于将 \(a_{i}\) 按照大小顺序插入到差分表中。

所以考虑用优先队列/大根堆来实现。

以及,最后输出答案不换行!!!

请使用C++11提交,然而我不知道为什么交C++会MLE

好吧,根堆的 \(swap\) 只有在C++11里是 \(O(1)\) 的。

附带精美调试信息

Code
#include<set>
#include<queue>
#include<cstdio>
#define MAX 300010
#define re register
//#define int long long
#define int64_t long long
using std::multiset;
using std::priority_queue;
namespace OMA
{
	int n,m,a[MAX];
	int64_t ans[MAX];
	priority_queue<int64_t>q[MAX];
	struct node
	{
		int l,r,id;
		inline friend bool operator <(const node &a,const node &b)
		{ return a.l==b.l?(a.r==b.r?a.id<b.id:a.r>b.r):a.l<b.l; }
	};
	multiset<node>set;
	multiset<node>::iterator it;
	struct graph
	{
		int next;
		int to;
	}edge[MAX<<1];
	int cnt=1,head[MAX];
	inline void add(int u,int v)
	{ edge[++cnt] = (graph){head[u],v},head[u] = cnt; }
	struct stream
	{
		template<typename type>inline stream &operator >>(type &s)
		{
			int w=1; s=0; char ch=getchar();
			while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
			while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
			return s*=w,*this;
		}
	}cin;
	inline void build(node p1)
	{
		while(!set.empty())
		{
			it = set.lower_bound(p1);
			//printf("1\n");
			if(it==set.end())
			{ break ; }
			//printf("2\n");
			node p2 = *it;
			//printf("%d %d %d %d\n",p1.l,p1.r,p2.l,p2.r);
			if(p2.l>=p1.l&&p2.r<=p1.r)
			{
				set.erase(it);
				add(p1.id,p2.id);
				//printf("CNMD\n");
				build(p2);
			}
			else
			{ break ; }
		}
	}
	inline void dfs(int u)
	{
		int son = m+1;
		for(re int i=head[u],v; i; i=edge[i].next)
		{
			v = edge[i].to; dfs(v);
			if(q[v].size()>q[son].size())
			{ son = v; }
		}
		swap(q[u],q[son]);
		for(re int i=head[u],v; i; i=edge[i].next)
		{
			v = edge[i].to;
			if(v!=son)
			{
				cnt = q[v].size();
				for(re int j=1; j<=cnt; j++)
				{
					ans[j] = q[u].top()+q[v].top();
					//printf("%d %d\n",q[u].top(),q[v].top());
					q[u].pop(),q[v].pop()/*,q[u].push(ans[j])*/;
				}
				for(re int j=1; j<=cnt; j++)
				{ q[u].push(ans[j]); }
			}
		}
		q[u].push(a[u]);
	}
	signed main()
	{
		cin >> n >> m;
		for(re int i=1,l,r; i<=m; i++)
		{ set.insert((node){(cin >> l,l),(cin >> r,r),i}),cin >> a[i]; }
		build((node){1,n,0}); dfs(0);
		/*for(re int i=1; i<=m; i++)
		{
			printf("u=%d\n",i);
			for(re int j=head[i]; j; j=edge[j].next)
			{
				printf("v=%d\n",edge[i].to);
			}
		}*/
		cnt = q[0].size();
		for(re int i=1; i<=cnt; i++)
		{ ans[i] = q[0].top(); q[0].pop(); }
		for(re int i=1; i<=m; i++)
		{ /*printf("1:%d %d\n",ans[i-1],ans[i]);*/ ans[i] += ans[i-1],printf("%lld ",ans[i]); }
		return 0;
	}
}
signed main()
{ return OMA::main(); }
posted @ 2021-08-06 21:45  -OMA-  阅读(95)  评论(1编辑  收藏  举报
浏览器标题切换
浏览器标题切换end