K.RSP
思维
(我们在这里假定是从1到n共n个数字,第i个数字被第i+1个数字克制,同时克制第i-1个数字,特别的,n被1克制)
即使是双方绝顶聪明的情况下,第一轮的胜负也是纯运气问题,而对于第一轮外的m - 1轮:
因为双方均不能连续出相同的数字,而每一个数字均只被一个数字克制,所以双方每轮均可选择被上一轮对方选过的数字克制的数字,以保证自己选择的数字不被克制。
例如:假定n等于4,第一轮,A选择3, B选择1,那么第二轮,A可以选择被1克制的4,因为B不能选择1,而B可以选择被3克制的2,因为A不能选择3,从而通过保证一定平局确保自己不会输。
综上,在双方均采用最优策略的情况下,游戏获胜的概率即为第一轮获胜的概率,即1 / n 。
#include <iostream>
using namespace std;
int main()
{
int n, m;
scanf("%d %d", &n, &m);
printf("1/%d", n);
return 0;
}
A.疾羽的救赎
模拟
开结构体数组w模拟棋盘,a[1]代表棋盘最上方的位置,a[2]代表棋盘中间的位置,a[3]代表棋盘下方的位置(假定每格刚好可以放三枚棋子)
以1,2,3代表紫色棋子,绿色棋子和黄色棋子(对应输入的1,2,3)
初始化w[2].a[3] = 1, w[3].a[3] = 2, w[4].a[3] = 3 (即将三个棋子置于棋盘第2,3,4格的底层)
对于12张卡牌,我们进行12次读入,每次读入两个数字x,y,遍历棋盘九个格子,寻找x对应棋子所在位置,将它所在棋盘上的位置(第j格,第k层)及需要挪动到的位置(棋盘第j + y格)作为参数传入move函数
move函数实现的功能:从棋盘第x格的第k层开始,将第k层及其上层棋子依次挪动到棋盘第y格,由下至上放置。
#include <iostream>
using namespace std;
struct W{
int a[4];
}w[10];
void move(int x, int y, int k)
{
for(int i = k; i > 0; i --)
{
for(int j = 3; j > 0; j --)
{
if(!w[y].a[j])
{
w[y].a[j] = w[x].a[i];
w[x].a[i] = 0;
}
}
}
}
int main()
{
int t, x, y;
scanf("%d",&t);
while(t --)
{
//初始化棋盘
for(int i = 1; i <= 9; i ++)
{
for(int j = 1; j <= 3; j ++)
{
w[i].a[j] = 0;
}
}
w[2].a[3] = 1;
w[3].a[3] = 2;
w[4].a[3] = 3;
for(int i = 1; i <= 12; i ++)
{
scanf("%d %d",&x, &y);
bool f = 0;
for(int j = 1; j <= 9; j ++)
{
for(int k = 1; k <= 3; k ++)
{
if(w[j].a[k] == x)
{
f = 1;
move(j, j + y, k);
break;
}
}
if(f) break;
}
}
if(w[9].a[1] && w[9].a[2] && w[9].a[3]) puts("Y");
else puts("N");
}
return 0;
}
//输出棋盘每格当前状态
for(int i = 1; i <= 9; i ++)
{
printf("%d %d %d\n", w[i].a[1], w[i].a[2], w[i].a[3]);
}
L. 养成游戏
递归,枚举
二维数组q用于存储每个评委的评分标准,一维数组a用于存储n个属性值。
由于数据范围并不大,我们去枚举所有属性值的所有可能组合,取这些组合最终得分的最大值即可
函数功能:
dfs用于递归求所有可能的组合情况
check函数用于计算当前组合的得分情况,并返回得分
#include <iostream>
using namespace std;
int n, m, k, q[110][10], a[10];
long long ans;
long long check()
{
long long res = 0, w;
for(int i = 1; i <= m; i ++)
{
w = q[i][4] * a[q[i][1]] + q[i][5] * a[q[i][2]];
if(!q[i][3])
{
if(w <= q[i][6]) res += q[i][7];
}
else
{
if(w >= q[i][6]) res += q[i][7];
}
}
return res;
}
void dfs(int u)
{
if(u > n)
{
ans = max(ans, check());
return;
}
for(int i = 0; i <= k; i ++)
{
a[u] = i;
dfs(u + 1);
}
}
int main()
{
scanf("%d %d %d", &n, &m, &k);
for(int i = 1; i <= m; i ++)
{
for(int j = 1; j <= 7; j ++)
{
scanf("%d",&q[i][j]);
}
}
dfs(1);
printf("%lld", ans);
return 0;
}
G.精灵宝可梦对战
模拟
定义a数组与b数组分别存储A的n个宝可梦的属性和B的m个宝可梦的属性
用队列q1存储玩家A的宝可梦队伍顺序,q2存储玩家B的宝可梦队伍顺序
用while循环进行模拟,循环结束条件为一方宝可梦队伍为空或 cnt (进行轮数)等于 k
函数功能:
attack1函数用于计算出玩家A的x号宝可梦对玩家B的y号宝可梦能造成的最大伤害值,并扣除玩家A的x号宝可梦相应的能量值(存储在a[x][0])以及玩家B的y号宝可梦相应的血量(存储在b[y][0])。
attack2函数用于计算出玩家B的x号宝可梦对玩家A的y号宝可梦能造成的最大伤害值,并扣除玩家B的x号宝可梦相应的能量值(存储在b[x][0])以及玩家A的y号宝可梦相应的血量(存储在a[y][0])。
#include <iostream>
#include <queue>
using namespace std;
const int N = 1e5 + 20;
int n, m, k, a[N][10], b[N][10];
queue<int> q1, q2;
void attack1(int x, int y)
{
int w, f, z;
w = max(0, a[x][2] - b[y][4]);
f = max(0, a[x][3] - b[y][5]);
if(a[x][0] >= a[x][6])
{
z = a[x][7];
}
else z = 0;
if(z > w && z > f)
{
a[x][0] -= a[x][6];
b[y][1] -= z;
}
else
{
a[x][0] ++;
b[y][1] -= max(w, f);
}
}
void attack2(int x, int y)
{
int w, f, z;
w = max(0, b[x][2] - a[y][4]);
f = max(0, b[x][3] - a[y][5]);
if(b[x][0] >= b[x][6])
{
z = b[x][7];
}
else z = 0;
if(z > w && z > f)
{
b[x][0] -= b[x][6];
a[y][1] -= z;
}
else
{
b[x][0] ++;
a[y][1] -= max(w, f);
}
}
int main()
{
scanf("%d %d %d", &n, &m, &k);
for(int i = 1; i <= n; i ++)
{
q1.push(i);
for(int j = 1; j <= 7; j ++)
{
scanf("%d",&a[i][j]);
}
}
for(int i = 1; i <= m; i ++)
{
q2.push(i);
for(int j = 1; j <= 7; j ++)
{
scanf("%d",&b[i][j]);
}
}
int x = q1.front(), y = q2.front();
q1.pop(); q2.pop();
while(k --)
{
attack1(x, y);
q1.push(x);
if(b[y][1] <= 0)
{
if(q2.empty()) break;
y = q2.front();
q2.pop();
}
x = q1.front();
q1.pop();
attack2(y, x);
q2.push(y);
if(a[x][1] <= 0)
{
if(q1.empty()) break;
x = q1.front();
q1.pop();
}
y = q2.front();
q2.pop();
}
if(k != -1)
{
if(q1.empty()) puts("Bob");
else puts("Alice");
}
else puts("Draw");
return 0;
}
F. 最长上升子序列
构造(逆向dp)
在写这题之前,先让我们回忆一下动态规划求最长上升子序列的过程:
对于数组 q 和 f,我们用 f[i]表示以第 i 个元素为最后一位(结尾)的最长上升子序列的长度。
则f[i]的计算过程即为q数组中第 i 位元素之前,所有小于 q[i] 的元素(以 x 代替它们的下标)所对应的 f[x]的最大值加一。
即:f[i] = { f[x] | q[x] < q[i] && x < i }的最大值 + 1
可以发现,f[i]每次最多只会比 f[1] ~ f[i - 1] 中的最大值多1。
回到这道题,由上述发现可以知道,当题给的最长上升子序列数组的某位元素减去它之前的所有元素的最大值的结果比1要大时,该排列是不存在的,此时直接输出 -1。
否则,一定存在至少一个1 到 n的排列,使得该排列的最长上升子序列数组与题给数组相同。
不存在的问题解决了,现在让我们来解决存在时的构造问题:
我们接着看动态规划求最长上升子序列的过程。
对于每一个f[i]为1的位置,q[i]一定小于它前面的所有数(1到n的排列不存在等于的情况)。
对于每一个f[i]为2的位置,q[i]一定只大于它前面所有f[x]为1的数。
同理,对于每一个f[i]为x的位置,q[i]一定只大于它前面所有f[i]不大于x的数。
这样看来,我们可以将1到n中最小的w个数(w为 f[i] 为1的位置 i 的个数)逆序放置到f[i]为1的位置上(逆序才能保证每一个f[i]为1的q[i]都不大于它前面最长上升子序列为1的数)。
然后将从w + 1到 w + y(y为 f[i] 为2的位置 i 的个数)逆序放置到f[i]为2的位置上。
……
最终可以得到一个符合题面要求的序列(数组)。
怎么实现呢?
我们用st数组来存储题给数组中每个长度的最长上升子序列出现的次数,对其求前缀和。
如对于题给数组:1 2 2 3 3
st数组的存储结果为:st[] = {1, 2, 2, 0, 0} (此处忽略掉下标为0的元素)。
对st数组求前缀和后:st[] = {1,3,5,5,5}
我们结合 st 原数组与 st 前缀和数组查看,可以发现所有元素恰能从小到大覆盖 1 到 n 这个区间。即原st[1] 对应1;原st[2]对应2,3;原st[3]对应4,5。
根据题给数组中子序列长度出现的次序,我们将子序列长度对应的区间逆序放置,可以得到一个从 1 到 n 的排列 :1 3 2 5 4
这个排列的最长上升子序列数组恰与题给数组相同。
代码实现如下:
#include <iostream>
using namespace std;
const int N = 1e6 + 20;
int n, q[N], st[N], mx = 0;
int main()
{
scanf("%d",&n);
bool f = true;
for(int i=1;i<=n;i++)
{
scanf("%d",&q[i]);
st[q[i]] ++;
if(q[i] > mx + 1) f = false;
else if(q[i] > mx) mx = q[i];
}
if(!f)
{
puts("-1");
}
else
{
for(int i = 1; i < N; i ++)
{
st[i] += st[i - 1];
}
for(int i = 1; i <= n; i ++)
{
q[i] = st[q[i]] --;
}
for(int i = 1; i <= n; i ++) printf("%d ", q[i]);
}
return 0;
}