浙江农林大学2022年新生杯程序设计竞赛(同步赛)
1,D,jbgg爆金币咯
思想:dp优化
两个人,设为a,b,想知道x血量a能否获胜,只需要知道被a所以技能砍后的血量状态b能否获胜(不能,那a在x血量可以赢),由此递推,会推到x最后小于0的情况(答案)
我们容易想到dfs遍历,返回1,表示当前人可以在该血量获胜,0则不行,当然,如果x小于0,直接返回0(必败,boss已经死了,没你表现的机会)
为了方便,设a为0,b为1,方便两人在数组用异或转化(因为是回合制,轮流来)
剪枝优化:为防止重复遍历,我们对更新过的dp数组访问时直接返回
#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
const int INF = 0x3f3f3f3f;
const int N = 2e3 + 100;
int a[5][10];//存储两人的技能数量及其伤害
int dp[5][N];
bool dfs(int x, bool f)
{
if (x <= 0)return 0;//必败
if (dp[f][x] != -1)return dp[f][x];//已经更新过
for (int i = 1; i <= a[f][0]; ++i)
{
if (!dfs(x - a[f][i], f ^ 1) )return (dp[f][x] = 1);//如果我这一到下去,对手返回0,说明他赢不了,那么我就是赢了,更新dp并返回
}//注意,dfs的f每次都有异或——更换对手--f ^ 1
return (dp[f][x] = 0);//所以技能都用过,对手还是赢,那么我认输行了吧
}
int main()
{
int t;
cin >> t;
while (t--)
{
int x;
cin >> a[0][0] >> a[1][0] >> x;//0位置存储技能数量
for (int i = 1; i <= a[0][0]; ++i)cin >> a[0][i];
for (int i = 1; i <= a[1][0]; ++i)cin >> a[1][i];
memset(dp, -1, sizeof(dp));//重置dp,为-1表示没有访问
cout << (dfs(x, 0) ? "AsindE" : "slwang") << endl;//先手是a,所以返回1,a赢
}
return 0;
}
2,I,异或和
思想:二进制转化
求解异或和很简单,重点是怎么任意组合
观察到:异或是每一位单独进行,那么我们和是不是也可以一位一位处理呢——这样我们就可以对每一位使用高中的组合公式
我们统计数组中每一位1的个数,我们知道集合n个元素有2^n的组合,所以对于每一位,0与1的组合有(2^x(1的个数-1)*2*x(0的个数))1的个数减1是因为我们1的组合总会出现一半组合是偶数个1(包括0)的情况,即该异或和为0,无意义,所以减,减在1上面,使一个1也没有时值为0.
对于每一位的值,还要*1<<i,表示其二进制位置的值
#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
const int INF = 0x3f3f3f3f;
const int N = 1e5 + 100;
int mod = 998244353;
ll pows[N];//pows开的空间是N,因为不是简单的代表2的几次方,而是1的个数
ll cnt[30];
int main()
{
ll n, x;
cin >> n;
pows[0] = 1;
for (int i = 1; i <= n; ++i)pows[i] = pows[i - 1] * 2 % mod;//后面值很大,一定要取模
for (int i = 1; i <= n; ++i)
{
cin >> x;
for (int j = 22; j >= 0; --j)if ((x >> j) & 1)cnt[j]++;//记录每一位二进制
}
ll ans = 0;
for (int i = 0; i <= 22; ++i)ans = (ans + ((pows[cnt[i] - 1] * pows[n - cnt[i]]) % mod) * (1<<i)%mod) % mod;
cout << ans << endl;
return 0;
}
3,L,jbgg想要n
思路:加的时候发现每次只需要保留余数部分就会,暴力遍历加标记剪枝
#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
const int INF = 0x3f3f3f3f;
const int N = 1e6 + 100;
int main()
{
ll n, m, p;
bitset<N>vis;
cin >> n >> m >> p;
ll k = 1;
while (n / k)k *= 10;
n%=p;//n或者k都可能比p大,直接加可能会错,先取余,不然n,k极大时乘起来直接爆
k%=p;
ll t = 0, ans =n;
for (int i = 1; i <= m; ++i)
{
t = (t * k%p + n) % p;
if (vis[t])break;
vis[t] = 1;
ans = max(ans, t);
}
cout << ans;
return 0;
}