qbxt Day 1

Day 1

考试题解

T1 打扑克

唯一切掉的题,终于签到成功了。本题其实非常简单,打斗地主打多了的人真的是一眼秒。一看数据1000位,看都不用看就是找规律。再看分成奇数偶数两组,就想到通过奇偶性来分类讨论一波。手玩几组小数据,很容易发现规律:一共就四种情况:1.共奇数张牌,奇数先出。2.共奇数张牌,偶数先出。3.共偶数张牌,偶数先出。4.共偶数张牌,奇数先出。自己推导一下就能发现,只有总数是奇数并且奇数先出奇数才能赢,其他都会输,所以直接判字符串末位的奇偶性即可。但是有一种特殊情况,就是\(n\)\(2\)的时候是谁先出谁赢,本题共有80分有这个点。。。。。所以说很多人没有考虑到就挂了\(80\),还是要注意细节。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int T,op;
char n[1010];
int main()
{
	T=read();
	while(T--){
		cin>>n;
		op=read();
		if(strlen(n)==1&&n[0]=='2'){
			if(op==0){
				printf("0\n");
				continue;
			}
			else{
				printf("1\n");
				continue;
			}
		}
		if((n[strlen(n)-1]-'0')%2==1){//奇数张牌 
			if(op==0){//
				printf("0\n");
			}
			else{
				printf("1\n");
			}
		}
		else{
			if(op==0){
				printf("1\n");
			}
			else{
				printf("1\n");
			}
		}
	}
	return 0;
}

T2 粉刷匠

考试时觉得是道可做题,推容斥能够推出来。先写了个暴力\(O(k(m+n))\),拿到\(30\)。但结果就是我越推越乱越推越乱,从没跟暴力拍上过,做了大约一个小时有点自闭,然后去做\(T3\)了。打完\(T3\)暴力,看了眼\(T4\),觉得不可做就又回来做\(T2\)。然后一直推到最后遗憾离场,\(30pts\)滚粗。中午回去巨神\(CYC\)告诉可以倒着做,因为倒着做就不用再考虑会不会被覆盖了。我恍然大悟,然后回来切掉了这道题。

其实这道题在某些方面跟去年江西\(CSP-S\)\(T3\)网格图中间求和的那部分非常非常相似,如果一行被染成了蓝色,那么就看看有几列已经被确定了,减去这几列即可。染成红色的话只需要去掉对答案的贡献这一行代码即可。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll; 
inline ll read(){
	ll x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
ll n,m,k,ans,lsum,hsum;
ll x[1000005],y[1000005],z[1000005];
bool hang[1000005],lie[1000005];
int main()
{
	scanf("%lld%lld%lld",&n,&m,&k);
	for(ll i=1;i<=k;i++){
		scanf("%lld%lld%lld",&x[i],&y[i],&z[i]);
	}
	for(ll i=k;i>=1;i--){
		if(z[i]==1){
			if(x[i]==0){
				if(!hang[y[i]]){
					hang[y[i]]=1;
					ans+=(m-lsum);
					hsum++;
				}
			}
			else{
				if(!lie[y[i]]){
					lie[y[i]]=1;
					ans+=(n-hsum);
					lsum++;
				}
				
			}
		}
		else{
			if(x[i]==0){
				if(!hang[y[i]]){
					hang[y[i]]=1;
					hsum++;
				}
			}
			else{
				if(!lie[y[i]]){
					lie[y[i]]=1;
					lsum++;
				}
			}
		}
	}
	printf("%lld\n",ans);
	return 0;
}
//  不开longlong见祖宗 

拓展:疯狂的馒头

T3 直线竞速

首先一眼看出了\(70pts\)的做法,只需要暴力算出查询的每一秒的时候每一个人的位置然后排序即可,时间复杂度是\(O(Qnlogn)\),这样能得到\(30pts\)。但发现还有\(40pts\)是只求第一名,那么在算每一个人的位置时直接在循环中更新最大值即可,时间复杂度是\(O(Qn)\)。就得到了\(70pts\)

但是令我万万没想到的是,我离正解只差了一个\(STL\)函数!!!在神奇的\(STL\)库里,有一个神奇的函数叫做\(nth_element()\)。那么这个函数的意思是什么呢:在O(n)的时间复杂度内,在无序序列中,找到第\(k\)小的数。

我:???????

然后用了函数之后总时间复杂度就到了\(O(Qn)\),可以切掉这道题。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,Q,t,k,num1;
int v[7010],a[7010];
long long maxx;
struct F{
	int num;
	long long s;
}f[7010];
long long cmp(F A,F B){
	return A.s>B.s;
}
int main()
{
	n=read();
	for(int i=1;i<=n;i++){
		v[i]=read();
		a[i]=read();
	}
	Q=read();
	for(int i=1;i<=Q;i++){
		t=read();
		k=read();
		maxx=0;
		for(int j=1;j<=n;j++){
			f[j].s=a[j]+1ll*v[j]*t;
			f[j].num=j;
			if(f[j].s>maxx){
				maxx=f[j].s;
				num1=f[j].num;
			}
		}
		if(k==1){
			printf("%d\n",num1);
			continue;
		}
		nth_element(f+1,f+k,f+1+n,cmp);//并不知道咋用,瞎搞一波然后对了
		printf("%d\n",f[k].num);
	}
	return 0;
}

但是老师的正解似乎并不是这样。。。(这做法确实nc而且low)

老师承认这题忘记了有这个函数,出水了,如果手写分治拿到满分也可以接受,但是\(STL\)就过分简单了。

下面说一下分治求第\(k\)小元素的原理:随便找一个元素放在中间当作挡板,分成两半,将小于这个数的元素放在左边,反之放在右边。然后看看左边数的个数和右边数的个数。如果左边的数的个数不到\(k\),那么数肯定在右边,就相当于求右边序列中的第\(k-x\)(左边有\(x\)个数)小的数,递归求解即可,复杂度是\(O(n)\)

正解做法2:

在起点处排序,每个选手之间的相对位置不可能跨越交换,只能相邻两人直接交换。这与冒泡排序的思想类似,因为速度快的超过速度慢的之后就不会再被慢的人超过,所以最多交换\(n^2\)次。就相当于每次交换减少一对逆序对。这不就是冒泡排序?!所以做法就是先提前将询问的时间排序,求出每个人那一秒在多远的位置,然后将所有的逆序对交换,然后输出排名。由于交换后不会再换回来,所以下一次询问在当前序列基础上继续操作即可。由于最多产生\(n^2\)个逆序对,所以最多交换\(n^2\)次,总时间复杂度是\(O(n^2+QlogQ+nQ)\)

T4 游戏

?分做法:

\(f_{i,j}\)表示\(A\)的前\(i\)个数和\(B\)的前\(j\)个数做游戏的最小总花费。\(f_{i,j}=min(f_{k,r}+(SA_i-SB_k)*(SA_j-SA_r),0<=k<i,0<=r<j\)(这式子没记全QWQ)。\(SA\)\(SB\)表示\(A\)\(B\)的前缀和。总时间复杂度:\(O(n^2m^2)\)

100分做法:

发现一结论:每次取的两端中至少有一段中只有一个数。假设\(A\)序列我分成两段取,两段的值分别是\(x\),\(y\)\(B\)序列也如此,分为\(a\),\(b\)两段。那么合起来的贡献就是\((x+y)*(a+b)\)要比分开的贡献\((ax+by)\)大,所以显然分开更优。就这样一直分下去,发现至少有一个序列在每一次操作的时候只选一个数最优,那么枚举是哪一段最后包含那一个只有一个数的那一段即可。用\(f_{i_j}\)来记录\(A\)序列选\(i\)个数然后\(B\)序列选\(j\)个数的时候最小贡献是多少。那么考虑转移方程:当两个序列都只选一个数的时候,那么很明显,就是由各自长度-1然后加上当前两个数的乘积。但如果不是的话,则可以看做是此过程重复多次。比如说\(A\)序列取一个数,然后\(B\)序列取了任意多个数。则可以看做\(A\)序列没有删掉最后一个数,然后一直乘下去直到乘完这些数,那么转移就是\(f_{i,j}=f_{i,j-1}+a_i*b_j\)。同理,就可以得到三个不同状态,然后取最小值,即是答案。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long int ll;
int n,m;
int a[2010],b[2010]; 
ll f[2010][2010];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",a+i);
		a[i]--;
	}
	for(int i=1;i<=m;i++){
		scanf("%d",b+i);
		b[i]--;
	}
	memset(f,0x3f,sizeof(f));//初始化为最大值
	f[0][0]=0;//什么都没选贡献当然是0
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			ll x=a[i]*b[j];
			f[i][j]=min(f[i-1][j-1],min(f[i-1][j],f[i][j-1]))+x;//三个不同的状态
		}
	}
	printf("%lld\n",f[n][m]);
	return 0;
}

DFS

NOI 1999 生日蛋糕

常规操作:如果已经大于\(ans\)直接\(return\)

最优性剪枝:\(v=pi*r^2*h,s=2*pi*r*h,s=2*v/r\),所以当体积一定时,r最大的时候最优。然后将半径带入体积求出高度继续搜索。

可行性剪枝:如果余下的蛋糕圆柱体不能满足半径和高都小于当前蛋糕圆柱体(当前圆柱体过小)。如果叠最小的圆柱体都会超出体积,那么也剪掉。

BFS

HAOI 2008 移动玩具

把所有边建出来然后\(BFS\)

拓扑序明显的时候用\(dp\),常数小。

posted @ 2020-10-01 23:12  徐明拯  阅读(164)  评论(2编辑  收藏  举报