A. Absolute Maximization
题意:
给你一个长度大于等于3的数组a,可以进行任意次操作,一次操作中可以任选两个数,交换两个数二进制下的一个位。
题解:
简单思维题。数组大小大于等于3,可以通过任意次数把每个位的1或0放到一个数上,所以最大值就是所有数的或值(将数位上有1的移到同一个数上),最小值为所有数的按位与值(尽可能使数位能为0就为0)
参考代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
#define pii pair<int, int>
int n, m, a[N];
void solve()
{
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
int mx = 0, mn = ~0;
for (int i = 1; i <= n; i++)
mx |= a[i], mn &= a[i];
cout << mx - mn << "\n";
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int T;
cin >> T;
while (T--)
solve();
}
B. Incinerate
题意:
有n个怪兽,每个怪兽有一个健康值h,一个能量值p。每次攻击可以对每个怪兽造成k点伤害,健康值-k,每次攻击结束后,攻击值k将减去还没死的怪兽(h>0)中最小的p值。问最后能否击败所有怪兽
题解:
直接模拟的复杂度实际上是O(nk)的,写题还是得注意复杂度,不要写假了。。
将怪兽按p值从小到大排序,记录总的攻击值,记录当前还活着的最小p值的怪兽,当生命值小于总的攻击值时,跳过该怪兽。最后判断k<=0时是否还有存活的怪兽即可。
复杂度大致为O(max(n,k))。
参考代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
#define pii pair<int, int>
int n, k, h[N], p[N];
struct node
{
int h, p;
bool operator<(const node x) const
{
return p < x.p;
}
} a[N];
void solve()
{
cin >> n >> k;
for (int i = 1; i <= n; i++)
cin >> a[i].h;
for (int j = 1; j <= n; j++)
cin >> a[j].p;
sort(a + 1, a + n + 1);
int cnt = 0, u = 1; // cnt为当前总的攻击值,u为当前p最小的怪兽下标
while (k > 0)
{
cnt += k;
int mn = 1e9;
while (u <= n && a[u].h - cnt <= 0) // h值已经小于0
u++;
if (u == n + 1) // 所有的怪兽的h都小于了0
{
puts("YES");
return;
}
mn = a[u].p;
// 以下注释为复杂度O(nk)代码(直接模拟)
// for (int i = 1; i <= n; i++)
// h[i] -= k;
// for (int i = 1; i <= n; i++)
// if (h[i] - cnt > 0 && p[i] < mn)
// mn = p[i];
k -= mn;
}
for (int i = 1; i <= n; i++)
{
if (a[i].h - cnt > 0) // 当还有怪兽h值大于0则不能击败所有
{
puts("NO");
return;
}
}
puts("YES");
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int T;
cin >> T;
while (T--)
solve();
}
C. Another Array Problem
题意:
给你一个数组a,在一次操作中可以选择一个区间[l,r]将a[l]~a[r]全部变成a[l]-a[r]的绝对值,操作可以进行任意次。问最后数组和的最大值可以为多少。
题解:
容易想到,我们尽可能将数组变成差值最大的数。显然,最小值是一定能变成0的(将区间长度为2进行两次操作就能变为0)
n>=4时,显然我们能得到最小值0,也能得到这个数组的最大值,并最终将这个数组所有数变成这个最大值(我们可以选数组前两个数或后两个数变成0,再与最大的数进行一次操作,再重复类似上述操作可将数组变成全为最大值)
n == 3时,最大值如果在1或3位置按上述方法是一定能取到的,所以只需要再讨论下最大值在2位置的情况,很显然数组每个数的最大值只能是相邻差的最大值
n==2时,显然数组每个数的最大值也只能是相邻差的最大值
参考代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
#define pii pair<int, int>
int n, m, a[N];
void solve()
{
int ans = 0;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i], ans += a[i]; // 进行0次操作的值
if (n == 2) // 等于2时的特殊情况
ans = max(ans, n * abs(a[1] - a[2]));
else if (n == 3) // 等于3时的特殊情况
ans = max(ans, n * max(abs(a[1] - a[2]), abs(a[2] - a[3])));
int mx = 0;
for (int i = 3; i <= n; i++) // 选a[1],a[2]变为0
mx = max(mx, a[i]);
ans = max(ans, mx * n);
mx = 0;
for (int i = 1; i <= n - 2; i++) // 选后两个数为最大值
mx = max(mx, a[i]);
ans = max(ans, mx * n);
cout << ans << "\n";
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int T;
cin >> T;
while (T--)
solve();
}
D. Valid Bitonic Permutations
题意:
给出5个数n,i,j,x,y分别代表排列的大小、a[i]=x,a[j]=y。问满足条件的严格先单调增再单调减的排列的个数。
题解:
这里提供组合数学解法。
为了方便我们把x,y经过等价转换变成x<y的情况。因为只有一次增和一次减,那么最大值n一定是分界点,所以我们只需要考虑n的位置。
情况一:
(图有点丑,凑活下吧。蓝色的线代表增减性)
最大值在x、y的右边,很显然根据单调性可以分成四个段记为a,b,c,d。考虑其中三个段能选的数的方案数,因为每一段都要满足一个单调性,所以数确定了那么位置也是确定的。
a段:在x-1个数中需要选u-1个即C(x - 1, u - 1)
b段:在y-x-1个数中需要选出v-u-1个即C(y - x - 1, v - u - 1)
c段:在n-y-1个数中需要选出i-v-1(i为n的位置下标)个即C(n - y - 1, i - v - 1)
方案数为:
情况二:
最大值在x、y的中间。同情况一进行讨论
a段:在x-1个数中取u-1个即C(x - 1, u - 1)
b、c段的讨论有点不同,有个小细节。我们记 it = (n - y - 1) - (v - i - 1),该数的含义是能放在c段的数减掉放在了c段的数剩下的数的个数,因为这些数大于y不能放在d段,所以只能强制放在b段(因为也只有b能够放了)
当it > (i - u - 1)时(必须放在b段的数比b段能容纳的数还要多):没有满足条件的方案,方案数为0
当it <= (i - u - 1)时:
c段:在n-y-1个数中取v-i-1个即C(n - y - 1, v - i - 1)
b段:在n - x - (v - i + 1) - it个数中取i - u - 1 - it即C(n - x - (v - i + 1) - it, i - u - 1 - it)
方案数为:
[it<=i-u-1]表达式为真则为1,为假为0
参考代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
#define pii pair<int, int>
int n, u, v, x, y, a[N], c[110][110];
int C(int n, int m)
{
if (m > n || m < 0 || n < 0) // 避免不合法情况
return 0;
return c[n][m];
}
void solve()
{
cin >> n >> u >> v >> x >> y;
int ans = 0;
if (x > y)
{
swap(x, y), swap(u, v), u = n - u + 1, v = n - v + 1;
}
if (y == n) // 如果y就是最大值那就不需要再考虑n的位置了
{
if (n != v)
ans += (v != u ? C(n - x - 1, v - u - 1) : 1) * C(x - 1, u - 1) % mod;
ans %= mod;
}
else
{
// 情况一
for (int i = v + 1; i < n; i++) // 小细节:不能在n位置,因为必须有单调减的段
{
ans += C(x - 1, u - 1) * C(y - x - 1, v - u - 1) % mod * C(n - y - 1, i - v - 1) % mod;
ans %= mod;
}
// 情况二
for (int i = u + 1; i <= v - 1; i++)
{
int it = n - y - 1 - (v - i - 1); // 能放在c段的数减掉放在了c段的数剩下的数的个数
if (it > i - u - 1) // 不存在方案
continue;
ans += C(x - 1, u - 1) * C(n - x - (v - i + 1) - it, i - u - 1 - it) % mod * C(n - y - 1, v - i - 1) % mod;
ans %= mod;
}
}
cout << ans % mod << "\n";
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
c[0][0] = 1;
for (int i = 1; i <= 100; i++) // 预处理组合数
{
c[i][0] = 1;
for (int j = 1; j <= i; j++)
{
c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
c[i][j] %= mod;
}
}
int T;
cin >> T;
while (T--)
solve();
}
upd: dp方法
dp[i][j]:构造区间[ i , j]的合法方案数。
我们考虑不断扩展[i,j]的左边和右边来进行状态转移,实际上我们可以从n到1进行填数扩展,来求得所有的合法方案数。
转移方程:
当前的最大数能填在i的左边(不与所给条件矛盾)
当前的最大数能填在j的右边
初始化:区间长度为1能填入n的方案数为1
详见代码:
int n, u, v, x, y, a[N], dp[110][110];
bool check(int id, int val)
{
if (id == u && val != x)
return false;
if (id == v && val != y)
return false;
return true;
}
void solve()
{
memset(dp, 0, sizeof dp);
cin >> n >> u >> v >> x >> y;
for (int i = 2; i < n; i++)
if (check(i, n))
dp[i][i] = 1;
for (int i = n; i >= 1; i--)
{
for (int j = i; j <= n; j++)
{
int val = n - (j - i + 1);
if (check(i - 1, val))
dp[i - 1][j] = (dp[i - 1][j] + dp[i][j]) % mod;
if (check(j + 1, val))
dp[i][j + 1] = (dp[i][j + 1] + dp[i][j]) % mod;
}
}
cout << dp[1][n] << endl;
}