第一题 D - Earn or Unlock
问题简述
- n卡牌从上往下放,开局有一次操作次数。(操作类型如下):
- 1.将卡牌的值转化为操作次数
- 2.获取卡牌的值
- 问最多能获取卡牌的值。
思路
-
dp背包
我们可以清楚的知道取了前i个卡牌时获得的所有卡牌的值,并且知道一定消耗了i - 1次操作 所以当取了前面i个时消耗了i - 1次操作机会,获得了前i个卡牌的值之和减(i - 1) 所以直接用背包确定在前i个时是否能精准取到(为什么是精准呢,因为可以贪心的认为取的越多越好)
-
但是单纯的用背包是不可以应用在当前的1e5的复杂度上的,所以可以用二进制bitset来优化背包的复杂度
代码
ini
**include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5 + 5;
const int mod = 1e9 + 7;
int a[N];
bitset<N>dp;
void solve() {
int n;
int sum = 0;
int ans = 0;
cin >> n;
for(int i = 0;i < n;i++) {
cin >> a[i];
}
n *= 2;//其实是n + n
dp[0] = 1; // 初始化dp
for(int i = 0;i < n;i++) {
dp |= (dp << a[i]); // 二进制背包 ,优化复杂度 ,取第i个值用作操作一
sum += a[i];//前缀
if(dp[i] == 1) {//当背包刚好能取i时,有i的值被转化为了
ans = max(ans , sum - i);
dp[i] = 0;//同时因为是刚好取到i,所以当不取时结束
}
}
cout << ans << "\n";
}
signed main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _ = 1;
// cin>>_;
while(_--)
{
solve();
}
}
第二题 F - Divide, XOR, and Conquer
问题简述
给定n个数ai,ai<=2^60
将这n个数进行以下操作。
选择第k个位置1<=k<n,令x=a1^a2^...^ak,y=a[k+1]^a[k+2]^...^a[n]
- 如果x>y,保留a1,a2,...,ak
- 如果x<y,保留a[k+1],a[k+2],...,a[n]
- 如果x==y,保留前面或者后边都可。
问,对于1<=i<=n,能否通过上述操作,保留剩下第i个元素。、
思路
已知对于一段区间异或[l ,r] ,令s为此区间的异或和,则有以下两种情况: 令k , x , y如问题简述所述
1.s = 0 则x == y , 任意x 或 y区间都可以删 2.s != 0 ,则存在x (或y) 的最高位与区间的异或和最高位相同时x > y(y > x)
(请读者细品为什么)
然后就是直接枚举长度从大到小,定义两个数组st[i]为以i为左边界的区间最高位的集合,ed为右区间 (区间dp)
代码
ini
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5 + 5;
const int mod = 1e9 + 7;
const int M = ((int)1 << 61) - 1;
int a[N];
int hb(int x) {//获取最高位的值15 -> 8
if(!x) return M;
else {
return (int)1 << (63 - __builtin_clzll(x));
}
}
void solve() {
int n ;
cin >> n;
vector<int>s(n + 1);//前缀和
for(int i = 1;i <= n;i++) cin >> a[i] , s[i] =(s[i - 1] ^ a[i]);
if(n == 1) {cout << 1 << "\n";return;}
vector<int>st(n + 1) , ed(n + 1);
st[1] = ed[n] = hb(s[n]); //初始化
for(int len = n;len >= 1;len--) {
for(int l = 1;l + len - 1 <= n;l++) {
int r = l + len - 1;
int sum = (s[r] ^ s[l - 1]);//区间异或和
int msk = (sum & st[l]) | (sum & ed[r]);
//记录区间是否合法,用之前说的方法
//如果以l为左边界或以r为右边界且包含了该区间的大区间存在最高位与sum且了那么该区间合法
//说明该区间可以被这个 最高位且sum 的大区间给用操作减去一个小区间获取
int flag = 0;
if(msk or st[l] == M or ed[r] == M) {
st[l] |= hb(sum) , ed[r] |= hb(sum);//更新l , r的最高位集合
flag = 1;
}
if(len == 1) cout << flag;
}
}
cout << "\n";
}
signed main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _ = 1;
cin>>_;
while(_--)
{
solve();
}
}