20161019的考试】签到题,逆序对,二分+two pointers 线段树 优化dp

80 篇文章 0 订阅
20 篇文章 0 订阅

T1

题意:数列递推式f[i]=f[i-1]+f[i-2],给定f[0]=1,f[a]=x,问是否有满足条件的f[1]。如果没有,输出"-1";如果有,输出f[b]的值。a,b<=20,保证满足条件的f[1]<=1e6

思路:差点以为要写高精度233333,推一下公式发现f[i]=f[0]*fib[i-2]+f[1]*fib[i-1](fib[i]表示斐波那契数列的第i项),预处理一下fib数组,直接O(1)算出f[1],判断是否有解,有解输出f[b],反正都是O(数据组数)的

代码:

#include<bits/stdc++.h>
using namespace std;	long long k,a,b;
const long long fib[21]={1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946};
int main(){
	freopen("kela.in","r",stdin);
	freopen("kela.out","w",stdout);
	long long x;
	while(~scanf("%lld%lld%lld",&a,&k,&b)){
		if(a>1){
			x=(k-fib[a-2])/fib[a-1];
			if(x<0||x*fib[a-1]!=k-fib[a-2]){
				puts("-1");
				continue;
			}
		}
		else	x=k;
		printf("%lld\n",fib[b-1]*x+fib[b-2]);
	}
	return 0;
}

T2

题意:给定1~n的排列,每次将所有严格递减的区间翻转,每翻转一个区间算一个操作(4231→2413算两个操作),保证初始排列中减区间的长度都是偶数。问进行多少次操作后能使该序列变为升序。 数据范围:nlogn能过

思路:在第一次进行翻转的时候,会将每个严格递减的区间变为递增,此后的每次操作都只能去掉一个逆序对,于是答案就是【两个数不在同一个递减区间内的】逆序对个数加上初始减区间的个数

代码:

#include<cstdio>
#define MAXN 100005
using namespace std;	int n;
int a[MAXN];
long long tree_array[MAXN];
long long ans=0;

int main(){
	freopen("rest.in","r",stdin);
	freopen("rest.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;++i){
		scanf("%d",a+i);
		for(int j=a[i];j;j-=j&(-j))	ans+=tree_array[j];
		if(a[i]==1)	continue;
		for(int j=1;j<=n;j+=j&(-j))	++tree_array[j];
		for(int j=a[i];j<=n;j+=j&(-j))	--tree_array[j];
	}
//	printf("ni = %lld\n",ans);
	for(int i=1,j=2;i<=n;i=j,++j){
		for(;a[j]<a[j-1]&&j<=n;++j);
		if(j>i+1)	ans-=1ll*(j-i)*(j-i-1)/2,++ans;
//		printf("L = %d   R = %d\n",i,j-1);
	}
	printf("%lld",ans);
	return 0;
}


T3…………怎么似曾相识23333

题意:

  HYSBZ开学了!今年HYSBZn个男生来上学,学号为1…n,每个学生都必须参加军训。在这种比较堕落的学校里,每个男生都会有Gi个女朋友,而且每个人都会有一个欠扁值Hi。学校为了保证军训时教官不会因为学生们都是人生赢家或者是太欠扁而发生打架事故,所以要把学生们分班,并做出了如下要求:

    1.分班必须按照学号顺序来,即不能在一个班上出现学号不连续的情况。

    2.每个学生必须要被分到某个班上。

    3.每个班的欠扁值定义为该班中欠扁值最高的那名同学的欠扁值。所有班的欠扁值之和不得超过Limit

    4.每个班的女友指数定义为该班中所有同学的女友数量之和。在满足条件123的情况下,分班应使得女友指数最高的那个班的女友指数最小。

    请你帮HYSBZ的教务处完成分班工作,并输出女友指数最高的班级的女友指数。

    输入数据保证题目有解。

思路:这玩意儿怎么似曾相识啊x 二分答案check一下就好了x

长得很像某y姓jq学长搬运过的题(bzoj 4639),先考虑二分答案,于是可以变成那道题了,然后粘过来改一改就过了【x


对于yjq搬运的题啊……可以naive地n^2 dp  枚举每次增加一段的左右端点,八十分啊

正解是用线段树+two pointers 优化一下

如果f[j]能更新f[i],肯定有性质【j+1到i的所有数的和没有超过二分的那个答案】,然后j+1到i中的最大数就是这一段的代价,可以用单调队列维护最大值,然后从………………f[j]+转移代价里面找最小值就是f[i]。

(╯‵□′)╯︵┻━┻我不管了语死早反正就是那个意思……【对未来的flaze】你自己看代码哼

#include<bits/stdc++.h>
#define MAXN 20057
using namespace std;

int n,lim;
int a[MAXN];
int b[MAXN];
long long sum[MAXN];
long long f[MAXN];

long long dt1[MAXN<<2],dt2[MAXN<<2],tag[MAXN<<2];

void pushdown(int now){
	tag[now<<1]=tag[now],tag[now<<1|1]=tag[now];
	dt2[now<<1]=dt1[now<<1]+tag[now],dt2[now<<1|1]=dt1[now<<1|1]+tag[now];
	tag[now]=0;
}

void modify(int now,int l,int r,int L,int R,long long v){
	if(L<=l&&r<=R)	return tag[now]=v,dt2[now]=dt1[now]+v,void();
	int mid=(l+r)>>1;
	if(tag[now]) pushdown(now);
	if(L<=mid)	modify(now<<1,l,mid,L,R,v);
	if(mid<R)	modify(now<<1|1,mid+1,r,L,R,v);
	dt2[now]=min(dt2[now<<1],dt2[now<<1|1]);
}

void modify(int now,int l,int r,int pos,long long v){
	if(l==r)	return dt1[now]=v,void();
	int mid=(l+r)>>1;
	if(tag[now]) pushdown(now);
	if(pos<=mid)	modify(now<<1,l,mid,pos,v);
	else	modify(now<<1|1,mid+1,r,pos,v);
	dt1[now]=min(dt1[now<<1],dt1[now<<1|1]);
}

long long inqry(int now,int l,int r,int L,int R){
	if(L<=l&&r<=R)	return dt2[now];
	int mid=(l+r)>>1;
	if(tag[now]) pushdown(now);
	long long rtn=0x3f3f3f3f3f3f3f3f;
	if(L<=mid)	rtn=inqry(now<<1,l,mid,L,R);
	if(mid<R)	rtn=min(rtn,inqry(now<<1|1,mid+1,r,L,R));
	return rtn;
}

int que[MAXN],head,tail;
bool check(long long limit){
	head=1;
	tail=0;
	f[0]=0;
	
	for(int i=1;i<=n;++i){
		while(sum[i]-sum[head-1]>limit)	
			++head;//,printf("%lld  %lld  %d\n",sum[i],sum[head-1],que[head]-1);
		while(a[i]>a[que[tail]])	--tail;
		modify(1,0,n,que[tail],i-1,a[i]);
		f[i]=inqry(1,0,n,head-1,i-1);
		que[++tail]=i;
		modify(1,0,n,i,f[i]);
	}
	
	return f[n]<=lim;
}


int main(){
	freopen("training.in","r",stdin);
	freopen("training.out","w",stdout);
	
	scanf("%d%d",&n,&lim);
	for(int i=1;i<=n;++i)
		scanf("%d%d",a+i,b+i),sum[i]=sum[i-1]+b[i];
	
	a[0]=0x3f3f3f3f,sum[0]=0;		
	int L=0,R=sum[n];
	while(L^R){
		int mid=(L+R)>>1;
		if(check(mid))	R=mid;
		else	L=mid+1;
	}
	printf("%d",L);
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值