"蔚来杯"2022牛客暑期多校训练营4
N Particle Arts
题目大意
有n个粒子,每个粒子有一个能量ai。两两随机碰撞,每碰撞一次两粒子的能量变为a&b,a|b。求所有粒子能量稳定后的方差。
题解
首先观察&和|运算,0和0得00,0和1得01,1和0得01,1和1得11,可知运算以后0和1的数量没有变,更进一步可知,所有n个数字每一位上的总的0和1数量没有变,即n个数字的和不变。
同时根据题意可以得到,要想a&b和a|b以后稳定,结果一定为a和b。根据上面的&和|的结果可知,假如a和b的某一位(二进制)为0和1,那么1一定都在其中一个数字里,0一定都在另一个数字里,这样才能保证&和|以后结果为a和b。
所以可以统计n个数字里每一位有多少个1,然后重新将每一位的1分配给n个数字。分配规则:当计算第i个数字时,若某一位还有1则分配1,若没有则分配0。
最后是计算方差的公式:D(x)=E(x2)-E(x)2。
代码
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
ull n, x, ans1, ans2, temp, gc;
ull cnt[20];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> x;
ans1 += x;
for (int j = 0, p = 1; j <= 15; j++, p <<= 1)
{
if (x & p)
cnt[j]++;
}
}
for (int i = 1; i <= n; i++)
{
temp = 0;
for (int j = 0, p = 1; j <= 15; j++, p <<= 1)
{
if (cnt[j])
{
cnt[j]--;
temp += p;
}
}
ans2 += temp * temp;
}
ans2 = n * ans2 - ans1 * ans1;
n = n * n;
gc = __gcd(n, ans2);
cout << ans2 / gc << "/" << n / gc << endl;
return 0;
}
K NIO’s Sword
题目大意
玩家初始有一把攻击力为0的剑,需要依次击杀n个敌人,仅当攻击力模n与i同余才能击杀第i个敌人。玩家可以升级剑,每次升级相当于在当前攻击力后面添加一个数字,问最少需要几次升级。
题解
记 Ai为击杀第i个怪物时的攻击力。
设为了击杀第 i 只怪物进行了 k 次升级,则有Ai-1 * 10k + x = Ai, 0 ≤ x < 10k ,即(i - 1) * 10k + x ≡ i mod n 。
所以对于每个 i 值,从小到大枚举 k 的取值,并计算 x 可取的最小非负值,直到找到一个满足条件的解即可。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, res;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for (ll i = 1; i <= n; i++)
{
for (ll j = 0; j <= 6; j++)
{
if ((ll(i - (i - 1) * pow(10, j)) % n + n) % n < pow(10, j))
{
res += j;
break;
}
}
}
cout << res << endl;
return 0;
}
H Wall Builder II
题目大意
给n个1 * 1的矩形,n-1个1 * 2的矩形,n-2个1 * 3的矩形,……,1个1 * n的矩形,把这些矩形拼成一个大矩形,
求大矩形的最小周长以及可行的方案。
题解
首先可以求出大矩形面积S=n * (n+1) * (n+2) / 6,然后可以得出周长C=2 * (a + S / a),由对勾函数性质可得当a≤sqrt(S)(下取整),a尽量大时周长最小。
摆放的时候可以贪心,把所有小矩形从长到短放入大矩形,从上到下一行一行试着放,遇到某一行能放就
放。对于1到100中的所有n,这个贪心都可以构造出理论上的最优解。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 6e3 + 5;
int t, n, S, a, b;
int cnt[maxn];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> t;
while (t--)
{
cin >> n;
for (int i = 1; i <= n; i++)
cnt[i] = n - i + 1;
S = n * (n + 1) * (n + 2) / 6;
a = sqrt(S);
while (S % a != 0)
{
a--;
}
b = S / a;
cout << (a + b) * 2 << endl;
if (a < b)
swap(a, b);
for (int i = 0; i < b; i++)
{
int temp = a;
for (int j = n; j >= 1 && temp; j--)
{
while (cnt[j] && j <= temp)
{
cout << a - temp << " " << i << " " << a - temp + j << " " << i + 1 << endl;
cnt[j]--;
temp -= j;
}
}
}
}
return 0;
}
D Jobs (Easy Version)
题目大意
有 n 个公司,第 i 个公司有 mi 个工作,每个工作对三个能力分别有数值要求,必须三个能力都达标才能胜任这份工作。一个人只要能胜任一个公司的任意一份工作就可以去这个公司工作。询问 q 次,每次询问一个三元组,代表一个人三个能力的数值,求这个人可以去多少个公司工作。
题解
使用bitset存储,并处理前缀,单点查询即可,a[i] [iq] [eq] [aq]表示第i家公司,是否有分别小于等于iq,eq,aq的工作。
代码
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const int maxn = 4e2 + 5;
const int mod = 998244353;
ll n, q, seed, m, x, y, z, res;
bitset<maxn> a[15][maxn][maxn];
int solve(int iq, int eq, int aq)
{
int ans = 0;
for (int i = 1; i <= n; i++)
{
if (a[i][iq][eq][aq])
ans++;
}
return ans;
}
ll qpow(ll a, ll n, ll mod)
{
a %= mod;
ll res = 1;
while (n)
{
if (n & 1)
res = (res * a) % mod;
a = (a * a) % mod;
n >>= 1;
}
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> q;
for (int i = 1; i <= n; i++)
{
cin >> m;
while (m--)
{
cin >> x >> y >> z;
a[i][x][y][z] = 1;
}
}
for (int c = 1; c <= n; c++)
{
for (int i = 0; i <= 400; i++)
{
for (int j = 0; j <= 400; j++)
{
for (int k = 0; k <= 400; k++)
{
if (a[c][i][j][k])
{
a[c][i + 1][j][k] = 1;
a[c][i][j + 1][k] = 1;
a[c][i][j][k + 1] = 1;
}
}
}
}
}
cin >> seed;
std::mt19937 rng(seed);
std::uniform_int_distribution<> u(1, 400);
int lastans = 0;
for (int i = 1; i <= q; i++)
{
int IQ = (u(rng) ^ lastans) % 400 + 1;
int EQ = (u(rng) ^ lastans) % 400 + 1;
int AQ = (u(rng) ^ lastans) % 400 + 1;
lastans = solve(IQ, EQ, AQ);
res = (res + lastans * qpow(seed, q - i, mod)) % mod;
}
cout << res << endl;
return 0;
}
A Task Computing
题目大意
从n个任务中选m个(a1, a2,…… , am)出来并任意排序,收益是
,求最大收益。
题解
[大佬链接](2022牛客暑期多校训练营4 个人题解 更新至5题 - 知乎 (zhihu.com))
观察收益式子可得,如果已经选了一些物品,其权值为x,现在需要往前面添加一个(w,p)的物品。那么权值会变成w+p*x。例如,现在有三件物品标号为1,2,3,收益为w1+w2p1+w3p1p2,若现在在前面添加一个物品标号为0,则收益为w0+w1p0+w2p0p1+w3p0p1p2,即w0+p0(w1+w2p1+w3p1p2)。
现在有两个物品 i , j ,如果i放到j前面获得的权值要大一点,则需要满足wi+pi(wj+pjx)>wj+pj(wi+pix),
化简一下得到,wi+piwj>wj+pjwi,wi(1-pj)>wj(1-pi),再除一下,可以发现大小结果只和自己有关。
所以可以排序,使得排在前面的数,更适合放到前面。
然后直接背包即可,定义f[i] [j] 为[i,n]中选择j个物品的最大值。
代码
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const int maxn = 1e5 + 5;
const int Max = 0x3f3f3f3f;
struct Node
{
double w, p;
bool operator<(const Node &a) const
{
return w * (1 - a.p) > a.w * (1 - p);
}
} a[maxn];
int n, m;
double dp[maxn][25];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i].w;
for (int i = 1; i <= n; i++)
cin >> a[i].p, a[i].p /= 10000;
sort(a + 1, a + 1 + n);
memset(dp, -Max, sizeof dp);
dp[n + 1][0] = 0;
for (int i = n; i >= 1; i--)
{
dp[i][0] = 0;
for (int j = 0; j <= m; j++)
dp[i][j + 1] = max(dp[i + 1][j + 1], a[i].w + a[i].p * dp[i + 1][j]);
}
cout << fixed << setprecision(10) << dp[1][m] << endl;
return 0;
}