NOI.AC 2019 CSP-S全国排位赛第一场 小结

废话

感觉不是很难吧,也怪不得差评连连

要是下面的代码锅了还请通知一声,可能是贴错了qwq

T1

差不多是改编题而已啊,显然对于每一组序列的参数,当 n n 增大时计算出来的数也肯定会增大,那么开一个小根堆维护一下就好了。

在洛谷试炼场找堆的专题,以及在提高(好像是?)一本bug通里面的贪心专题(可能是堆?)里面都能找到十分类似的题。

贴个代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 30010
#define ll long long

int n,m,k;
ll a[maxn][10];
struct node{ll x;int y;};
struct heap{
	node dui[maxn];
	int t;
	heap():t(0){}
	void up(int x)
	{
		while(x>1&&dui[x].x<dui[x/2].x)
		{
			swap(dui[x],dui[x/2]);
			x>>=1;
		}
	}
	void add(ll x,int y)
	{
		dui[++t]=(node){x,y};
		up(t);
	}
	void down(int x)
	{
		if(x>t/2)return;
		int ans=x;
		if(dui[x*2].x<dui[x].x)ans=x*2;
		if(x*2+1<=t&&dui[x*2+1].x<dui[ans].x)ans=x*2+1;
		if(x!=ans)
		{
			swap(dui[x],dui[ans]);
			down(ans);
		}
	}
	node pop()
	{
		node re=dui[1];
		dui[1]=dui[t--];
		down(1);
		return re;
	}
}dui;
int tot[maxn];
void add(int x,int z)
{
	ll sum=0,now=1;
	for(int i=0;i<=n;i++)
	sum+=(ll)a[x][i]*now,now*=(ll)z;
	dui.add(sum,x);
}

int main()
{
	scanf("%d %d %d",&k,&m,&n);k--;
	for(int i=1;i<=m;i++)
	for(int j=0,x;j<=n;j++)
	scanf("%d",&a[i][j]);
	for(int i=1;i<=m;i++)
	add(i,1),tot[i]++;
	while(k--)
	{
		node x=dui.pop();
		tot[x.y]++;
		add(x.y,tot[x.y]);
	}
	printf("%lld",dui.pop().x);
}

T2

考场上打崩了祭

做法也不难想,将所有点按行从小到大排个序,遍历一次,考虑弄一个 s e t set ,对于一段点,我们压成一个区间存在 s e t set 里面,每次加点的时候考虑与相邻的区间合并(就是这里炸了qwq)。然后往上一行走的时候,将所有区间的左右端点往内缩一格即可,缩没了的区间丢掉,然后在缩的时候统计一下答案就好了。

我觉得就是加点比较需要注意,别忘记搞一搞其他的细节就好了。

#include <cstdio>
#include <cstring>
#include <set>
#include <algorithm>
using namespace std;
#define maxn 300010
#define it set<node>::iterator

int n;
struct node{
	mutable int x,y;
	node(int xx=0,int yy=0):x(xx),y(yy){}
	bool operator <(const node b)const{return x<b.x;}
};
set<node> s;
node a[maxn];
void add(int x)
{
	if(s.empty()){s.insert(node(x,x+1));return;}
	it p=s.lower_bound(node(x));
	if(p!=s.end()&&p->x==x)return;
	if(x+2==p->x)
	{
		p->x-=2;
		int x=p->x,y=p->y;
		if(p!=s.begin())
		{
			p--;
			if(p->y+1==x)
			{
				p->y=y;
				p++;s.erase(p);
			}
		}
		return;
	}
	if(p==s.begin()){s.insert(node(x,x+1));return;}
	p--;
	if(x<=p->y)return;
	if(x==p->y+1)
	{
		p->y+=2;
		int x=p->x,y=p->y;p++;
		if(p!=s.end()&&p->x==y+1)
		{
			p->x=x;
			p--;s.erase(p);
		}
	}
	else s.insert(node(x,x+1));
}
long long ans=0;

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d %d",&a[i].x,&a[i].y);
	sort(a+1,a+n+1);
	int h=a[1].x,now=1;
	while(now<=n||!s.empty())
	{
		while(a[now].x==h&&now<=n)add(a[now].y),now++;
		
		it st=s.begin();
		while(st!=s.end())
		{
			ans+=(st->y-st->x+1)/2;
			st->x++;st->y--;
			if(st->x>st->y)
			{
				it p=st;st++;
				s.erase(p);
			}
			else st++;
		}
		h++;
		if(s.empty())h=a[now].x;
	}
	printf("%lld",ans);
}
/*
附赠我的样例一组:
7
0 0
0 2
-1 1
-1 3
-1 5
4 0
4 2

ans=12
*/

T3

这个算是有一点水平的吧。

显然最后的排班方式肯定是形如 A B C D A B C D . . . ABCDABCD... 这样的循环,而且在每一个循环节里面每个保镖恰好出现一次(这样满足保镖不希望多工作这个条件)。

那么设 S S 为选择的所有保镖的 a a 之和,即 S = i = 1 m a i S=\sum_{i=1}^m a_i

扫描二维码关注公众号,回复: 9578643 查看本文章

那么对于任意一个选中的保镖 x x ,肯定满足 S a x b x S-a_x\geq b_x ,移项就是 a x + b x S a_x+b_x \leq S

那么看到这种柿子就不禁(??)想要对所有保镖以 a i + b i a_i+b_i 为关键字排序了对吧!

排完序后,从小到大枚举保镖,设现在枚举到保镖 i i ,假设我们只在 1 1 ~ i i 这些保镖里面去选择,并且一定要选择第 i i 个保镖,考虑此时的最优方案。

我们回到上面的柿子, a x + b x S    ( x [ 1 , m ] ) a_x+b_x\leq S~~(x\in[1,m]) ,这个柿子等价于: S max x = 1 m { a x + b x } S\geq \max\limits_{x=1}^m\{a_x+b_x\}

因为我们强行选择了第 i i 个保镖,而第 i i 个保镖的 a i + b i a_i+b_i 是比 1 1 ~ i 1 i-1 这些保镖都大的,所以这个不等式的右边已经确定了,即 S a i + b i S\geq a_i+b_i

现在的目标是选尽可能少的保镖让 S S 大于等于 a i + b i a_i+b_i

那么肯定贪心得选 a a 大的保镖嘛,那么要怎么选这个问题用数据结构大力维护然后二分就好了,但是由于它不卡我,所以我打了个暴力,稍稍优化了一下,每次从上一次的选择中转移过来而不是每次都从头选。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define ll long long
#define maxn 500010

int n;
struct node{ll x,y;};
node a[maxn];
bool cmp(node x,node y){return (x.x+x.y)<(y.x+y.y);}
struct heap{
	ll dui[maxn];
	int t,type;
	heap():t(0){}
	bool check(ll x,ll y)
	{
		if(type==0)return x<y;
		else return x>y;
	}
	void up(int x)
	{
		while(x>1&&check(dui[x],dui[x/2]))
		{
			swap(dui[x],dui[x/2]);
			x>>=1;
		}
	}
	void add(ll x)
	{
		dui[++t]=x;
		up(t);
	}
	void down(int x)
	{
		if(x>t/2)return;
		int ans=x;
		if(check(dui[x*2],dui[x]))ans=x*2;
		if(x*2+1<=t&&check(dui[x*2+1],dui[ans]))ans=x*2+1;
		if(ans!=x)
		{
			swap(dui[x],dui[ans]);
			down(ans);
		}
	}
	ll top(){return dui[1];}
	void pop(){dui[1]=dui[t--];down(1);}
	bool empty(){return t==0;}
}dui1,dui2;
int ans=999999999;

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%lld %lld",&a[i].x,&a[i].y);
	sort(a+1,a+n+1,cmp);
	dui1.type=0;dui2.type=1;
	ll S=0,now;
	for(int i=1;i<=n;i++)
	{
		now=a[i].x+a[i].y;
		dui1.add(a[i].x);S+=a[i].x;
		while(S-dui1.top()>=now)S-=dui1.top(),dui2.add(dui1.top()),dui1.pop();
		while(S<now&&!dui2.empty())S+=dui2.top(),dui1.add(dui2.top()),dui2.pop();
		if(S>=now)ans=min(ans,dui1.t);
	}
	if(ans==999999999)printf("-1");
	else printf("%d",ans);
}

说出来你可能不信,第三题我在考场上忘记判无解输出 1 -1 的情况,居然直接被它的 s u b t a s k subtask 卡成 0 0 分?

发布了234 篇原创文章 · 获赞 100 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/102757964