CF1435 游记

Posted lsq147

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CF1435 游记相关的知识,希望对你有一定的参考价值。

CF1435 游记

第一次 AK div2 ,我流下了感动的泪水。

Atcoder 掉分没有关系,攒完 RP 之后 CF 就可以上分了。/xyx

技术图片

A Finding Sasuke

题意简述

给定 (n) 个数 (a_1,a_2,dots,a_n) ,其中 (n) 是偶数,请找到一组 (b_1,b_2,dots,b_n) 满足 (sum_{i=1}^na_ib_i=0)(T) 组数据。

(1le Tle 1000,2le nle 100,|a_i|le 100,a_i e 0) ,你需要保证 (|b_i|le 100,b_i e 0)

解题思路

显然, (-a_2,a_1,-a_4,a_3,-a_6,a_5dots,-a_n,a_{n-1}) 就满足条件。

参考代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<‘0‘||c>‘9‘;c=ch())if(c==‘-‘)f=-f;
	for(x=0;c<=‘9‘&&c>=‘0‘;c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc(‘0‘);if(x<0)pc(‘-‘),x=-x;
	while(x)q[cnt++]=x%10+‘0‘,x/=10;
	while(cnt--)pc(q[cnt]);
}
int a[105];
int main(){
	int T;read(T);
	while(T--){
		int n;read(n);
		for(int i=1;i<=n;++i)
			read(a[i]);
		for(int i=1;i<=n;i+=2)
			write(-a[i+1]),pc(‘ ‘),write(a[i]),pc(" 
"[i==n-1]);
	} 
	return 0;
}

B A New Technique

题意简述

有一个 (n imes m) 的矩阵,其中填写的数字为 (1,2,dots,n imes m) ,将每一行打乱顺序和每一列打乱顺序后告诉你,还原原矩阵,保证有解, (t) 组数据。

(1le tle 10^5,1le n,mle 500,sum n imes mle 2.5 imes 10^5)

题目分析

相当于告诉你每个数上面是什么,下面是什么,左边是什么,右边是什么,于是就可以做了。

参考代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<‘0‘||c>‘9‘;c=ch())if(c==‘-‘)f=-f;
	for(x=0;c<=‘9‘&&c>=‘0‘;c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc(‘0‘);if(x<0)pc(‘-‘),x=-x;
	while(x)q[cnt++]=x%10+‘0‘,x/=10;
	while(cnt--)pc(q[cnt]);
}
const int maxn=250005;
int Right[maxn],Left[maxn],Up[maxn],Down[maxn];
int main(){
	int T;read(T);
	while(T--){
		int n,m;
		read(n),read(m);
		for(int i=1;i<=n*m;++i)
			Right[i]=Left[i]=Up[i]=Down[i]=0;
		int tot=0;
		for(int i=1;i<=n;++i){
			int last=0;
			for(int j=1;j<=m;++j){
				int a;read(a);if(j!=1)Right[last]=a,Left[a]=last;last=a;
			}
		}
		for(int j=1;j<=m;++j){
			int last=0;
			for(int i=1;i<=n;++i){
				int a;read(a);if(i!=1)Down[last]=a,Up[a]=last;last=a;
			}
		}
		int rt;
		for(int i=1;i<=n*m;++i){
			if(!Up[i]&&!Left[i]){
				rt=i;break;
			}
		}
		while(rt){
			for(int i=rt;i>=1&&i<=n*m;i=Right[i])
				write(i),pc(" 
"[!Right[i]]);
			rt=Down[rt];
		}
	}
	return 0;
}

C Perform Easily

题意简述

给定六个数 (a_1,a_2,dots,a_6)(n) 个数 (b_1,b_2,dots,b_n) ,请找到一个序列 (c_1,c_2,dots,c_n) ,满足 (1le c_ile 6,a_{c_i}<b_i) ,并最小化 (max(a_{c_i}-b_i)-min(a_{c_i}-b_i))

(1le a_i,b_ile 10^9,1le nle 10^5)

题目分析

先对于任意的 (1le ile n) ,求出满足条件的 (a_{c_i}-b_i) 的所有可能,考虑枚举 (min(a_{c_i}-b_i)) ,这样的话就是要最小化 (a_{c_i}-b_i) ,用一个桶记一下就行了。

参考代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<‘0‘||c>‘9‘;c=ch())if(c==‘-‘)f=-f;
	for(x=0;c<=‘9‘&&c>=‘0‘;c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc(‘0‘);if(x<0)pc(‘-‘),x=-x;
	while(x)q[cnt++]=x%10+‘0‘,x/=10;
	while(cnt--)pc(q[cnt]);
}
const int maxn=100005;
int sum[maxn][6],a[6],cnt[maxn];
struct Temp{
	int id,val;
	Temp(int id=0,int val=0):
		id(id),val(val){}
	bool operator < (const Temp o)const{
		return val<o.val;
	}
}all[maxn*6];
int now[maxn];
int main(){
	for(int i=0;i<6;++i)
		read(a[i]);
	sort(a,a+6);
	int n,ac=0,mx=0;read(n);
	for(int i=0;i<n;++i){
		int b;read(b);int cn=0;
		for(int j=5;j>=0;--j)if(b>a[j]){
			all[ac++]=Temp(i,sum[i][cn++]=b-a[j]);
		}
		cnt[i]=cn;mx=max(mx,sum[i][now[i]=0]);
	}
	sort(all,all+ac);int ans=0x3f3f3f3f;
	for(int l=0,r=0;l<ac;l=r=r+1){
		int val=all[l].val;
		while(r+1<ac&&all[r+1].val==val)++r;
		ans=min(ans,mx-val);int ok=true;
		for(int i=l;i<=r&&ok;++i){
			int id=all[i].id;++now[id];
			if(now[id]>=cnt[id])ok=false;
			else mx=max(mx,sum[id][now[id]]);
		}
		if(!ok)break;
	}
	write(ans),pc(‘
‘);
	return 0;
}

D Shurikens

题意简述

给定 (n) ,有一个长度为 (n)排列 (a) ,有 (2n) 次操作和一个初始为空的小根堆和一个初始为 (0) 的数字 (c) ,每次操作要么是令 (cgets c+1) ,然后将 (a_c) 插入堆中,要么是删除小根堆的堆顶,现在给出操作序列和每次删除掉的数字,询问是否存在一个满足条件的排列 (a) ,如果存在需要构造出来。

(1le nle 10^5)

题目分析

每次找到被删掉的最小值,然后找到它前面第一个操作,如果是插入操作就删除这个插入操作和这个删除操作,否则就无解,证明显然。

参考代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<‘0‘||c>‘9‘;c=ch())if(c==‘-‘)f=-f;
	for(x=0;c<=‘9‘&&c>=‘0‘;c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc(‘0‘);if(x<0)pc(‘-‘),x=-x;
	while(x)q[cnt++]=x%10+‘0‘,x/=10;
	while(cnt--)pc(q[cnt]);
}
const int maxn=100005,saxn=maxn*2;
struct Temp{
	int id,val;
	Temp(int id=0,int val=0):
		id(id),val(val){}
	bool operator < (const Temp o)const{
		return val<o.val;
	}
}sum[maxn];
int id[saxn],pa[saxn];
int ac(int x){
	return pa[x]==x?x:pa[x]=ac(pa[x]);
}
int ans[maxn];
int main(){
	int n;read(n);int n2=n*2,cnt=0,an=0;
	for(int i=1;i<=n2;++i){
		char c=ch();
		while(c!=‘+‘&&c!=‘-‘)c=ch();
		if(c==‘-‘){
			int a;read(a);
			sum[++an]=Temp(i,a);
		}
		else{
			id[i]=++cnt;
		}
		pa[i]=i;
	}
	pa[0]=0;
	sort(sum+1,sum+an+1);
	for(int i=1;i<=an;++i){
		int no=sum[i].id,val=sum[i].val;
		int to=ac(no-1);
		if(!id[to])return puts("NO"),0;
		ans[id[to]]=val;
		pa[no]=pa[to]=to-1;
	}
	puts("YES");
	for(int i=1;i<=n;++i)
		write(ans[i]),pc(" 
"[i==n]);
	return 0;
}

E Solo mid Oracle

题意简述

给定 (a,b,c,d) ,有一个敌人,你可以对他发动技能来伤害他,每次发动技能时该敌人会瞬间失去 (a) 滴血,然后在接下来的 (c) 秒内,他每秒会恢复 (b) 滴血,恢复血量可以叠加,如果你在第 (i,j(i<j)) 秒都发动了技能,那么需要满足 (jge i+d) ,当这个敌人在某一秒的时候的血量小于等于 0 ,那么他就死了,询问你可以杀死的血量最高的敌人的血量是多少,如果可以杀死任意血量的敌人,输出 -1(t) 组询问。

(1le tle 10^5,1le a,b,c,dle 10^6)

题目分析

感觉细节挺多的一道题目。

首先贪心地想,技能肯定是能放就放,所以放技能大概就是:

[egin{matrix} -a & +b & dots & +b & +b & +b & dots & +b & & -a & +b & dots & +b & dots & +b & +b & +b & & & & -a & +b & dots & +b & +b & +b & +b & +b & & & & & & dots \\end{matrix} ]

现在要求的就是前缀和的最小值。

先考虑判 -1 ,随着秒数的增加,一直到第一个技能的治愈效果消失的时候,每次伤害就变成固定的了,此时上面的矩阵大概长这样:

[egin{matrix} +b & +b & +b & dots & +b +b & +b & +b & dots & +b & +b & dots & +b & +b & +b & dots\\vdots & vdots & vdots & vdots & vdots & vdots & vdots & vdots & vdots & vdots & vdots +b & +b & +b & dots & +b & +b & dots & +b & +b & +b & dots-a & +b & +b & dots & +b & +b & dots & +b & +b & +b & dots& & & & & & & -a & +b & +b & dots \\end{matrix} ]

可以把这些数分成若干块,使得每一块的值都相等:

技术图片

(这张图中每一块代表的就是最左边的矩形和中间的矩形的并)

如果每一块的值都是负数,那么显然 -1 ,其他情况不可能 -1

上图中最左边的 -a 是第 (lfloorfrac{c}{d} floor)-a ,第 (i)-a 的位置是 ((i-1) imes d+1) ,所以这个 -a 的位置就是 (s=(lfloorfrac{c}{d} floor-1) imes d+1) ,第一个 -a 的治愈效果会一直执行到 (c+1) 这个位置,由此可以得出,最左边的矩形的权值和就是 (((c+1-s+1) imes lfloorfrac{c}{d} floor-1) imes b-a) ;最左边的 -a 的右边的第一个 -a 的位置就是 (s+d) ,所以中间的矩形的权值和就是 ((s+d-(c+2)) imes (lfloorfrac{c}{d} floor-1) imes b) 。所以如果下面这个式子的值为负数,那么答案就是 -1

[((c+1-s+1) imes lfloorfrac{c}{d} floor-1) imes b+(s+d-(c+2)) imes (lfloorfrac{c}{d} floor-1) imes b-a ]

如果不是负数,说明这个总的矩阵的前缀和的最小值一定在第一个 -a 到第 (lfloorfrac{c}{d} floor)-a 之间,并且这一列必然是一次放技能的开始,因为往后面走不会更优了,确定了选择区间之后,我们发现第一个 -a 的治愈效果一定会到达当前选择的位置,所以考虑问题就更加简单了。

(x)-a 造成的共伤害是多少?枚举前面 (x-1)-a 造成的治愈效果,总伤害应该是 (-a imes x+sum_{y=1}^{x-1}((x-y) imes d imes b)) ,即 (-a imes x+frac{x(x-1)}{2} imes d imes b) ,是个二次函数,可以直接求对称轴,也可以三分,我用的方法是三分。

参考代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<‘0‘||c>‘9‘;c=ch())if(c==‘-‘)f=-f;
	for(x=0;c<=‘9‘&&c>=‘0‘;c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc(‘0‘);if(x<0)pc(‘-‘),x=-x;
	while(x)q[cnt++]=x%10+‘0‘,x/=10;
	while(cnt--)pc(q[cnt]);
}
long long a,b,c,d;
long long f(long long x){
	return x*(x-1)/2*d*b-a*x;
}
int main(){
	int T;read(T);
	while(T--){
		read(a),read(b),read(c),read(d);
		long long e=c/d+1,pos=(e-1)*d+1;
		long long eb=(e*(c+1-pos+1)-1)*b+(pos+d-(c+2))*(e-1)*b;
		if(eb<a)puts("-1");
		else{
			long long l=1,r=e,ans=0;
			while(l+3<r){
				long long ml=(l+r)>>1,mr=ml+1;
				if(f(ml)>f(mr))ans=min(ans,f(l=ml));
				else ans=min(ans,f(r=mr));
			}
			while(l<=r)ans=min(ans,f(l++));
			write(-ans),pc(‘
‘);
		}
	}
	return 0;
}

总结

这次比赛状态还不错, E 这种神仙细节题目也没有挂,希望下次也要保持这样的状态。

以上是关于CF1435 游记的主要内容,如果未能解决你的问题,请参考以下文章

PKUSC2018游记

csp-j/s2019游记(爆炸记)

如何从后台弹出片段

1435 位数阶乘

51nod 1435 位数阶乘

1435 位数阶乘