装饰大楼,备用钥匙,IOIOI卡片占卜总结

T1装饰大楼

Description

国际信息学奥林匹克竞赛将要在日本召开了。为了欢迎全世界的选手们,委员会决定将从机场到宿舍沿路的大楼装饰起来。根据某著名设计师的设计,做装饰的大楼从机场到宿舍的方向必须高度严格递增。也就是说,如果做装饰的大楼从机场开始高度顺次为h1,h2,h3,…,那么必须满足h1<h2<h3<…。
为了使尽量多的装饰品发挥光泽,做装饰的大楼希望越多越好。担任挑选被装饰的大楼的工作的JOI君,考虑到了大楼的主人可能会有“希望自己的大楼被装饰起来,而且,为了让大楼很显眼,希望这栋大楼是所有装饰起来的大楼中离宿舍最近的一栋”这种无理要求。
从机场到宿舍沿路共有N栋大楼,从机场开始的第i栋大楼称作大楼i,N栋大楼的高度彼此不同。JOI君为了满足各种各样的要求,决定事先计算出“如果装饰大楼i,并且让大楼i是所有装饰起来的大楼中离宿舍最近的一栋,那么选出的大楼最多有Ai个”这样的东西。JOI君计算出了整数列A1,A2,…,AN,然后发给了日本信息学奥林匹克竞赛委员会的K理事长。
然而,K理事长收到的信息只有一个长度为N-1的整数列B1,B2,…,B[N-1]。K理事长不知道大楼高度的情报,因此没有办法计算出Ai。
K理事长认为,JOI君一定是漏写了一个数。考虑到以A1,A2,…,AN为A数组的大楼的高度大小关系可能有很多种,K理事长想知道,删掉一个数后能得到B1,B2,…,B[N-1]的合法的A数组一共有多少种?
然而实际上,JOI君有可能并没有漏写一个数而是出现了其他的书写事故,因此无解也是有可能的。

Input

第一行一个正整数N,表示从机场到宿舍沿路的大楼数量。
接下来N-1行,第i行(1<=i<=N-1)为Bi,表示K理事长收到的第i个数的值。

Output

输出一行一个正整数,表示可能的A数组的数量

Sample Input

4
1
1
2

Sample Output

5
【HINT】
合法的A数组共有以下5种:
1,2,1,2,此时的高度序列为2413或3412
1,1,2,3,此时的高度序列为2134
1,1,2,1,此时的高度序列为3241
1,1,2,2,此时的高度序列为2143
1,1,1,2,此时的高度序列为3214

Data Constraint

对于10%的数据,N<=8
对于40%的数据,N<=300
对于100%的数据:
2<=N<=10^6
1<=Bi<=N (1<=i<=N-1)

码上解释吧

#include<cstdio>
#include<iostream>
using namespace std;
int n,b[1000007],mx,p,np,bz;//mx是最大值(目前)
long long ans;
int main(){
	freopen("building.in","r",stdin);
	freopen("building.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<n;i++)
		scanf("%d",&b[i]);
	for(int i=1;i<n;i++){
		if(b[i]>mx+2){//这种情况是不合法的,至于为什么自行脑补
			printf("0");
			return 0;
		}
		if(b[i]==mx+2){//这种情况有一个是合法的,有两个是不合法的,至于为什么自行脑补。
			bz++,p=i;//bz判断有几次,p表示发生这种情况的位置,答案后面统计
			if(bz==2){//有两次就不合法
				printf("0");
				return 0;
			}
		}
		if(mx<b[i]) mx=b[i],np=bz==0?i:np;//记录一下最大值
	}
	if(bz){//有一次的话,答案就是发生情况的地方减去最大值(当时)最早出现的地方
		printf("%d",p-np);
		return 0;
	}
	mx=0;
	for(int i=1;i<n;i++){
	//我们发现对于i是否可以选k在于前面1到k-1是否都出现过,由于我们判断了有裂缝的情况,所以到了这一步说明最大值取到k,那么1至k-1必然出现过,那么i可以选的数一共是k+1,也就是mx+1,但是会有重复的部分所以要-1(为什么是减一请自行脑补)
		mx=max(mx,b[i]);
		ans+=mx;
	}
	printf("%lld",ans+1);//在0到1之间还可以插个1
}

T2备用钥匙

Description

你知道Just Odd Inventions社吗?这个公司的业务是“只不过是奇妙的发明(Just Odd Inventions)”。这里简称为JOI社。
JOI社有N名员工,编号从1到N。所有员工的工作时间从时刻0持续到时刻M,时刻0和时刻M的时候,所有员工都必须在公司内。
某天,出于巧合,JOI社的每个员工都要出行恰好一次。员工i(1<=i<=N)在时刻Si离开公司,时刻Ti回到公司。同一时刻不会同时有两名以上的员工离开或回到公司。
JOI社的入口处有一扇巨大的门,员工只能通过这扇门离开或回到公司。门上挂着一把锁,从公司内部可以任意开锁或上锁,但从公司外部只有持有备用钥匙的人才能开锁或者上锁。时刻0时,锁是锁上的。
每个社员在回到公司的时候,都必须能够进入公司。换句话说,对于任意1<=i<=N,要么员工i持有备用钥匙,要么时刻Ti时门是开着的,否则是不被允许的。员工回到公司的时候,或者携带备用钥匙的员工离开公司的时候,可以选择锁门或不锁。没有携带备用钥匙的员工离开公司的时候没有办法锁门。
JOI社的社长决定把备用钥匙交给N个员工中的K个人。为了避免钥匙的丢失,员工之间不允许借用钥匙。此外,JOI社的社长很重视时间效率,因此每个员工在离开或回到公司的时刻以外,不允许开锁或者上锁。
出于安全的考虑,社长希望上锁的时间越长越好。现在他将员工出入公司的信息和准备交给员工的钥匙数量告诉了你,请你求出在能使所有员工回到公司的时候都能进入公司的大门的前提下,上锁的时间最长是多少。

Input

第一行三个空格分隔的整数N,M,K,表示JOI社的员工有N个,工作时间从时刻0到时刻M,备用钥匙有K把。
接下来N行,第i行(1<=i<=N)有两个空格分隔的整数Si,Ti,表示员工i在时刻Si离开公司,时刻Ti回到公司。

Output

输出一行一个正整数,表示上锁时间总和的最大值。

Sample Input

4 20 2
3 11
5 15
6 10
12 18

Sample Output

13
【HINT】
JOI社共有4名员工,工作时间为时刻0~时刻M,共有两把备用钥匙。
将钥匙交予员工2和员工4,一天日程如下:
时刻0,锁是关闭状态
时刻3,员工1离开公司。由于员工1没有备用钥匙,无法锁门。
时刻5,员工2离开公司,锁门。
时刻6,员工3离开公司。由于员工3没有备用钥匙,无法锁门。
时刻10,员工3回到公司,不锁门。
时刻11,员工1回到公司,锁门。
时刻12,员工4离开公司,锁门。
时刻15,员工2回到公司,锁门。
时刻18,员工4回到公司,锁门。
直到时刻20为止,锁都保持关闭状态。上锁的时间段为03,56,11~20,总计13时段,故答案为13。

Data Constraint

对于20%的数据,1<=N<=20,1<=M<=10^6
对于100%的数据:
1<=N<=2000
1<=M<=10^9
1<=K<N
0<Si<Ti<M (1<=i<=N)
对于任意i,j (1<=i<=N,1<=j<=N,i≠j),Si≠Sj,Si≠Tj,Ti≠Tj

正解

这个题是dp,但是直接推比较难,所以要考虑变形一下。
对于时间轴上有进出的相邻的两个点,有4种情况,出为A,进为B

  1. AA
  2. BB
  3. AB
  4. BA
    然后对于第一种情况,若这两个点之间是有贡献的话,那么显然第一个出的(左边)是要有钥匙的
    对于第二种情况,若是要这两个点之间有贡献的话,那么显然右边要有钥匙的(注意我们此刻只考虑这两个点,所以左边的点回来时门是锁的还是没锁都是不用李它的。)
    对于第三种情况,显然要有贡献是两个点都要有钥匙的
    对于第四种情况,显然都不需要有钥匙就能有贡献。
    那么一二的情况我们把贡献记录在有钥匙的那个点的编号。四的情况答案可以直接加,三的情况我们需要判断一下
    若这两个点的编号是一样的(也就是说一个人),那么这个人直接记录一下贡献就好了,否则的话,我们用个nxt[左边(编号)]=右边(编号)然后右边的点记录贡献(这个需要另开数组记录)
    之后我们按照点的顺序给它们个新编号,然后就可以愉快的dp了。
    dp代码见
#include<cstdio>
#include<iostream>
#include<algorithm>
#define N 4007
using namespace std;
int n,m,k,d[N],sum[N],ans,mx,res[N],g[N],f[N][N],nxt[N],o;
bool vis[N];
struct node{int num,w;bool bz;}a[N];
bool cmp(node a,node b){return a.w<b.w;}
void dg(int x){
	if(!x) return;
	vis[x]=1,dg(nxt[x]),d[o--]=x;//先递归到后面回溯时再赋编号
}
int main(){
	freopen("key.in","r",stdin);
	freopen("key.out","w",stdout);
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++){
		scanf("%d%d",&a[i*2-1].w,&a[i*2].w);//时间
		a[i*2-1].num=a[i*2].num=i;//编号是哪个人
		a[i*2-1].bz=0,a[i*2].bz=1;//标记进,出
	}
	sort(a+1,a+2*n+1,cmp);//按时间从小到大排序
	for(int i=1;i<n*2;i++){
		bool x=a[i].bz,y=a[i+1].bz;
		if(!x&&!y) sum[a[i].num]+=a[i+1].w-a[i].w;//情况一
		if(x&&y) sum[a[i+1].num]+=a[i+1].w-a[i].w;//情况二
		if(x&&!y) ans+=a[i+1].w-a[i].w;//情况四
		if(!x&&y){//情况三
			if(a[i].num==a[i+1].num) sum[a[i].num]+=a[i+1].w-a[i].w;//同一个人
			else{//不同一个人
				nxt[a[i].num]=a[i+1].num;
				vis[a[i+1].num]=1;//用来找新编号
				res[a[i+1].num]=a[i+1].w-a[i].w;
			}
		} 
	}
	o=n;
	for(int i=1;i<=n;i++)
		if(!vis[i]) dg(i);//没标记过就递归找新编号
	for(int i=1;i<=n;i++){
		for(int j=1;j<=k;j++){//f[i][j]表示前i个人用j把钥匙并且第i个人并且用过一把的贡献
			if(j>1) f[i][j]=max(g[j-1],f[i-1][j-1]+res[d[i]]);
			f[i][j]+=sum[d[i]];//方程如上
			g[j]=max(g[j],f[i-1][j]);//g[j]指前面用了j把钥匙的最大贡献
			mx=max(mx,f[i][j]);//统计答案用的最大贡献
		}
	}
	ans+=mx+a[1].w+m-a[2*n].w;//0到第一个出的点还有最后一个进的点到m的贡献统计一下
	printf("%d",ans);
}

T3IOIOI卡片占卜

我认为这道题还是真的很巧妙的,建议读者先看看能否思考到正解,再看题解。

Description

K理事长很喜欢占卜,经常用各种各样的方式进行占卜。今天,他准备使用正面写着”I”,反面写着”O”的卡片为今年IOI的日本代表队占卜最终的成绩。
占卜的方法如下所示:
首先,选择5个正整数A,B,C,D,E。
将A+B+C+D+E张IOI卡片排成一行,最左侧的A张卡片正面朝上,接下来B张反面朝上,接下来C张卡片正面朝上,接下来D张反面朝上,最后E张正面朝上。如此排列的话,从左侧开始顺次为A张“I”,B张“O”,C张“I”,D张“O”,E张“I”。
在预先决定的N种操作中选出至少1种,然后按照任意顺序执行。(注:同种操作执行多次也是可以的。)这里,第i种操作(1<=i<=N)为【将从左数第Li张卡片到第Ri张卡片全部翻转】。翻转一张卡片需要1秒的时间,因此第i种操作耗时Ri-Li+1秒。
操作结束后,如果所有卡片都是正面朝上则占卜成功。K理事长不想翻多余的牌,因此在实际使用卡片占卜之前会先计算出是否存在占卜成功的可能性。进一步,如果占卜可能成功,他会计算出能使占卜成功所消耗的时间的最小值。
现在给出卡片的排列信息和预先决定的操作信息,请你写一个程序,计算出占卜能否成功,如果能成功,输出消耗时间的最小值。

Input

第一行5个空格分隔的整数A,B,C,D,E,表示占卜初始时,从最左端开始依次是A枚正面朝上,接下来B枚背面朝上,接下来C枚正面朝上,接下来D枚背面朝上,最后E枚正面朝上。
接下来一行一个正整数N,表示预先决定的操作种类数。
接下来N行,第i行(1<=i<=N)两个空格分隔的正整数Li,Ri,表示第i种操作为【将从左数第Li张卡片到第Ri张卡片全部翻转】。

Output

如果占卜能够成功,输出消耗时间的最小值,否则输出-1。

Sample Input

1 2 3 4 5
3
2 3
2 6
4 10

Sample Output

12
【HINT】
初始的卡片序列为IOOIIIOOOOIIIII。
执行第2种操作后得到IIIOOOOOOOIIIII,耗时5秒。
接下来执行第3中操作,得到IIIIIIIIIIIIIII,占卜成功,耗时7秒。
耗时12秒完成占卜,这是耗时的最小值,故输出12。

## Data Constraint
对于15%的数据,N<=10
对于另外50%的数据,1<=A,B,C,D,E<=50
对于100%的数据:
1<=A,B,C,D,E,N<=10^5
1<=Li<=Ri<=A+B+C+D+E (1<=i<=N)

考试时暴力20分滚粗

正解

实际上是个最短路????(小问号,你是不是有很多朋友
眼切的大佬不要太介意,这是身为蒟蒻的本能反应(非条件反射,又称简单反射)
具体是这样的,相邻两个数异或一下,由于不是1,就是0,然后可以发现场上就剩下4个1了,位置分别是a,a+b,a+b+c,a+b+c+d(e:我不配拥有姓名?)
然后对于每次翻转,实际上是l-1,r这两个位置翻转(是不是很妙)
然后我们将l-1,r连上边,边权是r-l+1,也就是它们的贡献。
然后我们考虑每次删去两个1,这样4个1就是分成两组。那么现在假设我们要翻转l,r;r,x,实际上就是翻转l,x对吧,所以对于每组,我们显然可以找到这两个点的最短路,最短路的长度就是翻这两个点所需要的贡献了,然后我们将所有可能的匹配搞一搞找一下最大值就行了。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
#define N 1000007
using namespace std;
int n,head[N],cnt;
long long ans,dis[N];
bool bz[N];
struct node{
	int to,nxt,w;
}e[N<<1];
void add(int u,int v,int w){
	e[++cnt].to=v;
	e[cnt].w=w;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
long long find(int x,int y){//spfa还是香啊
	queue<int>q;
	memset(bz,1,sizeof(bz));
	memset(dis,60,sizeof(dis));
	q.push(x);
	bz[x]=0;
	dis[x]=0;
	while(!q.empty()){
		int u=q.front();
		q.pop();bz[u]=1;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(dis[v]>dis[u]+e[i].w){
				dis[v]=dis[u]+e[i].w;
				if(bz[v])
					q.push(v),bz[v]=0;
			}
		}
	}
	return dis[y];
}
int main(){
	freopen("card.in","r",stdin);
	freopen("card.out","w",stdout);
	int a,b,c,d,e;
	scanf("%d%d%d%d%d",&a,&b,&c,&d,&e);
	int u[5];
	u[1]=a,u[2]=a+b,u[3]=a+b+c,u[4]=a+b+c+d;//4个点
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x-1,y,y-x+1);
		add(y,x-1,y-x+1);//建边
	}
	memset(dis,60,sizeof(dis));//dis赋无穷大,这的60就够了。
	ans=dis[1];//刚开始被ans和dis的的初始化化搞死了,所以就这样了
	ans=min(ans,find(u[1],u[2])+find(u[3],u[4]));
	ans=min(ans,find(u[1],u[3])+find(u[2],u[4]));
	ans=min(ans,find(u[1],u[4])+find(u[2],u[3]));//三种组合情况
	if(ans>=1e12) ans=-1;//原来就是答案比int大,然后wa了,后来搞longlong又搞错很烦
	printf("%lld",ans);
}

总结

今天的题真的是挺妙(对于某些大佬来说挺淼的)考试时只能暴力,赛后看到思路后觉得……嗯,是我想不到的题。都挺好淼的。

猜你喜欢

转载自blog.csdn.net/jay_zai/article/details/107699489