S2考前综合刷题营Day1

打扑克

题目描述

皮蛋为了讨好黑妞,想要跟她打扑克。

他们打的扑克是这样一种规则:有面值大小从 \(1\)\(n\) 的扑克各一张。其中奇数牌在皮蛋手中,偶数牌在黑妞手中。每人每次只能出一张牌,先出完者获胜(遵循最基本的扑克规则:当对手出牌后,可以选择出一张比他大的牌,或者不管,让他再任意出一张牌)。假设皮蛋和黑妞都是足够聪明的人,都想让自己获胜。现在给定 \(n\) 和谁先出牌,那么谁会获胜呢?

输入格式

\(1\)\(1\) 个正整数 \(T\),表示数据组数。

接下来 \(T\) 行,每行 \(2\) 个正整数 \(n,op\),表示打一局牌。其中 \(n\) 如题所示,保证 \(op∈{0,1}\)\(op=0\) 表示皮蛋先出牌,\(op=1\) 表示黑妞先出牌。

输出格式

\(T\) 行每行 \(1\) 个数表示打一局牌的答案。\(0\) 表示皮蛋获胜,\(1\) 表示黑妞获胜。

样例数据

input

2
5 0
10 1

output

0
1

数据规模与约定

对于 \(40\%\) 的数据,\(n≤10\)

对于 \(70\%\) 的数据,\(n≤10000\)

对于 \(100\%\) 的数据,\(2≤n≤101000,T≤100\)

时间限制:\(1s\)
空间限制:\(512MB\)

Solution

分奇偶,分先后,简单模拟找规律即可。
发现只有当牌数为奇数且皮蛋先手时,皮蛋才能赢,否则都是黑妞赢。
要注意特判 \(n=2\) 的情况。

粉刷匠

题目描述

皮蛋喜欢黑妞,想为她粉刷一面网格墙。

墙上有 \(n\)\(m\) 列共 \(n∗m\) 个网格。初始时,整面墙都是红色的。皮蛋只有红、蓝两种颜色的油漆,而且皮蛋很懒,他每次刷墙只会把某一整行或某一整列刷成红色或蓝色。皮蛋一共粉刷了 \(k\) 次墙。现在给出皮蛋的粉刷方案,请你求出最后这面墙有多少个格子是蓝色的。

输入格式

\(1\)\(3\) 个正整数 \(n,m,k\)

接下来 \(k\) 行,每行 3$ 个整数 \(x,y,z\) 表示一次刷墙。保证 \(x,z∈{0,1}\) x,\(x=0\) 表示皮蛋粉刷第 \(y\) 行,否则表示粉刷第 \(y\) 列;\(z=0\) 则表示将该行(列)的格子都粉刷成红色,否则表示都粉刷成蓝色。保证操作合法。

输出格式

\(1\)\(1\) 个数表示答案。

样例数据

input

2 2 2
0 1 1
1 2 1

output

3

数据规模与约定

对于 \(30\%\) 的数据,\(1≤n,m,k≤1000\)

对于另外 \(30\%\) 的数据,\(n≤10,1≤m,k≤100000\)

对于 \(100\%\) 的数据,\(1≤n,m,k≤1000000\)

时间限制:\(2s\)
空间限制:\(512MB\)

Solution

考虑到对于每一个格子,只有最后一次的行或列的修改对它有效,所以我们可以倒着考虑
这样,每个格子就只被修改了一次,那么对于被修改的某一行或某一列,以后不会再考虑了,所以我们可以把它删掉。
对于行的修改,如果是将这一行改为蓝色,那么我们将 \(ans+=m(m\)为列数\()\),同时 \(n--(n\)为行数);修改列同理。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define ll long long
using namespace std;
int read()
{
	char ch=getchar();
	int a=0,x=1;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') x=-x;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		a=(a<<1)+(a<<3)+(ch^48);
		ch=getchar();
	}
	return a*x;
}
const int N=1e6+5;
int n,m,k;
int opt[N],x[N],y[N];
bool vis[2][N];
ll ans;
int main()
{
	n=read();m=read();k=read();
	for(int i=1;i<=k;i++)
	{
		opt[i]=read();
		x[i]=read();
		y[i]=read();
	}
	for(int i=k;i>=1;i--)
	{
		if(vis[opt[i]][x[i]]) continue;
		vis[opt[i]][x[i]]=1;
		if(opt[i]==0) ans+=m*y[i],n--;
		else ans+=n*y[i],m--;
	}
	printf("%lld\n",ans);
	return 0;
}

直线竞速

题目描述

一年一度的繁荣山庄直线竞速比赛要开始了!

本次比赛共有 \(n\) 位选手,第 \(i\) 位选手的起点在 \(a_i\) 位置,速度是 \(v_i\),速度方向是正方向。

之后有 \(Q\) 个询问,每次询问经过 \(t\) 秒后,排名在第 \(k\) 位的选手的编号(在相同位置时,编号小的排名靠前)。

输入格式

\(1\)\(1\) 个正整数 \(n\)

接下来 \(n\) 行,每行 \(2\) 个正整数 \(v_i,a_i\)

接下来 \(1\)\(1\) 个整数 \(Q\)

接下来 \(Q\) 行,每行 \(2\) 个正整数 \(t,k\),表示一个询问。

输出格式

\(Q\) 行,每行一个整数表示询问的答案。

样例数据

input

4
2 100
3 50
4 60
5 1
4    
1 1
50 2
60 4
100 1

output

1
4
1
4

数据规模与约定

对于 \(30\%\) 的数据,\(1≤n,Q≤1000\)

对于另外 \(40\%\) 的数据,\(k=1\)

对于 \(100\%\) 的数据,\(1≤n,Q≤7000\)\(1≤t≤10^9\)\(1≤v_i,a\)i≤10^5$,\(1≤k≤n\)

时间限制:\(2s\)
空间限制:\(512MB\)

Solution

40pts

对于每次询问,\(O(n)\) 求出此刻每个人的位置,然后从大到小排序,输出第 \(k\) 大的人编号。
时间复杂度 \(O(Qn\log{n})\)

70pts

\(40pts\) 的基础上,对于 \(k=1\) 的部分,我们求出每个人位置的同时维护最大位置和这个人的编号,然后输出即可。
时间复杂度 \(O(Qn)\)

100pts

我的做法:考虑到当时间 \(t\) 逐渐增大时,决定人和人之间的位置关系的主要是他们的速度,所以我们可以求一个时间 \(T\),表示在 \(T\) 时刻之后他们的相对位置不再变化,可以求得
\(T\) 的最大范围是 \(10^5\),所以如果 \(t>=T\),我们直接输出速度排名第 \(k\) 的人的编号;否则 \(O(On\log{n})\) 暴力求得第 \(k\) 小的人的编号。
时间复杂度 \(O(Qn\log{n}-\)玄学\()\)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define ll long long
using namespace std;
int read()
{
	char ch=getchar();
	int a=0,x=1;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') x=-x;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		a=(a<<1)+(a<<3)+(ch^48);
		ch=getchar();
	}
	return a*x;
}
const int N=7005;
struct node
{
	int x,v,Id;
	ll now;
}a[N],b[N];
int n,q;
double dp[N][N],T;
bool cmp(node x,node y)
{
	if(x.v==y.v) return x.x>y.x;
	return x.v>y.v;
}
bool Cmp(node x,node y)
{
	if(x.now==y.now) return x.Id<y.Id;
	return x.now>y.now;
}
double max(double a,double b)
{
	if(a>b) return a;
	return b;
}
int main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		a[i].v=read();
		a[i].x=read();
		a[i].Id=i;
	}
	sort(a+1,a+1+n,cmp);
	memset(dp,0x3fff,sizeof(dp));
	for(int i=1;i<n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			if(a[i].x>=a[j].x) dp[i][j]=0;
			else 
			{
				dp[i][j]=1.0*(a[j].x-a[i].x)/(a[i].v-a[j].v);
				T=max(T,dp[i][j]);
			}
		}
	}
	q=read();
	for(int i=1;i<=q;i++)
	{
		int t=read();
		int k=read();
		if(1.0*t>=T) printf("%d\n",a[k].Id);
		else
		{
			if(k==1)
			{
				ll ans=-1e15,ansid;
				for(int i=1;i<=n;i++)
				{
					a[i].now=a[i].x+t*a[i].v;
					if(a[i].now>ans)
					{
						ans=a[i].now;
						ansid=a[i].Id;
					}
				}
				printf("%d\n",ansid);
			}
			else
			{
				for(int i=1;i<=n;i++)
				{
					b[i].now=a[i].x+t*a[i].v;
					b[i].Id=a[i].Id;
				}
				sort(b+1,b+1+n,Cmp);
				printf("%d\n",b[k].Id);
			}
		}
	}
	return 0;
}

正解做法:
显然,任意两位选手的相对位置最多只会变化一次,所有选手在整个比赛期间的两两相对位置的交换次数最多是 \(\frac{n(n-1)}{2}\)
于是我们把询问按时间排序,维护比赛期间选手的排名,对于每次询问,像冒泡排序一样从前往后把每位选手向前冒泡,总的交换次数是 \(O(n^2)\) 的。
时间复杂度 \(O(n^2+nQ+Q\log{Q})\)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<cmath>
#include<set>
#include<map>
#include<cstdlib>
#include<bitset>
#include<stack>
#include<ctime>
#include<fstream>
#define dd double
#define ll long long
#define mp make_pair
#define pb push_back
#define N 7010
#define M 1010
using namespace std;
int n,Q;
struct ma
{
	int v,a,num;
}w[N];
bool cmp1(ma x,ma y)
{
	return x.a>y.a;
}
struct am
{
	int t,k,ans,num;
}q[N];
bool cmp2(am x,am y)
{
	return x.t<y.t;
}
bool cmp3(am x,am y)
{
	return x.num<y.num;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&w[i].v,&w[i].a);
		w[i].num=i;
	}
	cin>>Q;
	for(int i=1;i<=Q;i++)
	{
		scanf("%d%d",&q[i].t,&q[i].k);
		q[i].num=i;
	}
	sort(w+1,w+n+1,cmp1);
	sort(q+1,q+Q+1,cmp2);
	for(int i=0;i<=Q;i++)
	{
		for(int j=1;j<=n;j++)
		{
			for(int k=j;k>1;k--)
			{
				ll x=w[k].a+(ll)w[k].v*q[i].t;
				ll y=w[k-1].a+(ll)w[k-1].v*q[i].t;
				if(y<x||y==x&&w[k-1].num>w[k].num) swap(w[k-1],w[k]);
				else break;
			}
		}
		q[i].ans=w[q[i].k].num;
	}
	sort(q+1,q+Q+1,cmp3);
	for(int i=1;i<=Q;i++) printf("%d\n",q[i].ans);
}

游戏

题目描述

有一个游戏:给定两个正整数序列 \(A,B\),长度分别为 \(n,m\)。你可以做如下操作:删掉 \(A\) 的最后 \(x(x≥1)\) 个数并得到它们的和 \(S_1\),同时删掉 \(B\) 的最后 \(y(y≥1)\) 个数并得到它们的和 \(S_2\),这次操作的花费是 \((S_1–x)(S_2–y)\)。你需要一直操作直到 \(A,B\) 都为空(不能做完一次操作后使得其中一个为空而另一个非空)。本次游戏的总花费是每次花费的和。求最小的总花费。

输入格式

\(1\)\(2\) 个正整数 \(n,m\)

\(2\)\(n\) 个正整数,表示序列 \(A\)

\(3\)\(m\) 个正整数,表示序列 \(B\)

输出格式

\(1\)\(1\) 个数表示最小的总花费。

样例数据

input

3 2
1 2 3
1 2

output

2

数据规模与约定

数据点 \(1:n=20,m=20\)

数据点 \(2:n=110,m=80\)

数据点 \(3:n=200,m=130\)

数据点 \(4:n=400,m=80\)

数据点 \(5:n=1000,m=333\)

数据点 \(6:n=510,m=910\)

数据点 \(7:n=1200,m=1400\)

数据点 \(8:n=700,m=1800\)

数据点 \(9:n=1998,m=1370\)

数据点 \(10:n=2000,m=1999\)

对于 \(100\%\) 的数据,\(1≤A_i,B_i≤1000\)

时间限制:\(2s\)
空间限制:\(512MB\)

Solution

首先化简一下题目,把每个数 \(--\),问题就变成了区间和直接乘。
\(f[i][j]\) 表示 \(A\) 的前 \(i\) 个数和 \(B\) 的前 \(j\) 个数做游戏的最小总花费。
\(F[i][j]=min(f[k][r]+(S_A[i]-S_A[k])*(S_B[j]-S_B[r])),0<=k<i,0<=r<j\) \(S_A,S_B\) 表示 \(A,B\) 的前缀和。
时间复杂度 \(O(n^2m^2)\)
一个结论:存在一个最优解的每次删数,至少有一段长度是 \(1\)
于是状态转移只需要枚举一维 时间复杂度 \(O(nm(n+m))\)
有了之前的结论,可以重新思考转移的状态:
\(1.\)\(A\)\(B\) 截取的最后一段长度均为 \(1\),则有转移为:\(f[i][j]=\min(f[i][j],f[i-1][j-1]+a[i]*b[j])\)
\(2.\)\(A\) 的最后一段长度为 \(1\)\(B\) 的长度不为 \(1\),则有转移 \(f[i][j]=\min(f[i][j],f[i-1][k]+a[i]*(b[k+1]+b[k+2]+...+b[j]))\)
对照上一种情况的转移方程,换种角度来思考:我们用长度为 \(1\)\(A\) 序列和长度为 \(k\)\(B\) 序列对应,其实就是第一种情况执行了 \(k\) 次,因为无论如何,\(a[i]*b[j]\) 的贡献都会加进去的。
则有转移方程:\(f[i][j]=\min(f[i][j],f[i][j-1]+a[i]*b[j])\)(就是让 \(B\) 序列的后几个依次与 \(a[i]\) 对应)。
\(3.\)\(B\) 的最后一段长度为 \(1\)\(A\) 的长度不为 \(1\),此情况与情况 \(2\) 同理,转移方程为:\(f[i][j]=\min(f[i][j],f[i-1][j]+a[i]*b[i])\)
综上,给出最后的转移方程:\(f[i][j]=\min(f[i-1][j-1],\min(f[i-1][j],f[i][j-1]))+a[i]*b[j]\)
边界条件:\(f[0][0]=0\)
时间复杂度:\(O(nm)\)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define ll long long
using namespace std;
int read()
{
	char ch=getchar();
	int a=0,x=1;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') x=-x;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		a=(a<<1)+(a<<3)+(ch^48);
		ch=getchar();
	}
	return a*x;
}
const int N=2005;
int n,m;
int a[N],b[N];
ll f[N][N];
int main()
{
	n=read();m=read();
	for(int i=1;i<=n;i++) a[i]=read()-1;
	for(int i=1;i<=m;i++) b[i]=read()-1;
	memset(f,127,sizeof(f));
	f[0][0]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			f[i][j]=min(f[i-1][j-1],min(f[i-1][j],f[i][j-1]))+a[i]*b[j];
		}
	}
	printf("%lld\n",f[n][m]);
	return 0;
}
posted @ 2020-10-01 17:31  暗い之殇  阅读(312)  评论(1编辑  收藏  举报