打扑克
题目描述
皮蛋为了讨好黑妞,想要跟她打扑克。
他们打的扑克是这样一种规则:有面值大小从 (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
",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(Qnlog{n}))。
70pts
在 (40pts) 的基础上,对于 (k=1) 的部分,我们求出每个人位置的同时维护最大位置和这个人的编号,然后输出即可。
时间复杂度 (O(Qn))。
100pts
我的做法:考虑到当时间 (t) 逐渐增大时,决定人和人之间的位置关系的主要是他们的速度,所以我们可以求一个时间 (T),表示在 (T) 时刻之后他们的相对位置不再变化,可以求得
(T) 的最大范围是 (10^5),所以如果 (t>=T),我们直接输出速度排名第 (k) 的人的编号;否则 (O(Onlog{n})) 暴力求得第 (k) 小的人的编号。
时间复杂度 (O(Qnlog{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
",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
",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
",b[k].Id);
}
}
}
return 0;
}
正解做法:
显然,任意两位选手的相对位置最多只会变化一次,所有选手在整个比赛期间的两两相对位置的交换次数最多是 (frac{n(n-1)}{2}) 。
于是我们把询问按时间排序,维护比赛期间选手的排名,对于每次询问,像冒泡排序一样从前往后把每位选手向前冒泡,总的交换次数是 (O(n^2)) 的。
时间复杂度 (O(n^2+nQ+Qlog{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
",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
",f[n][m]);
return 0;
}