Atcoder 249
Atcode249
E.RLE(DP)
Problem
给定一个整数\(N\),要求满足以下要求的字符串的数量:该字符串连续的相同的字符可以合并,假设这一段长度为\(L\),那么合并后这一段可以表示为\(\alpha f(L)\),其中\(f(L)\)为\(L\)的位数,比如\(aaaaabbbccc\)合并后变为\(a5b3c3\),如果一个字符串合并后的长度小于它原来本身,该字符串就是合法的。要求长度为\(N\)的合法字符串的数量。\(1\le N\le 3000\)
Sol
各个长度对应合并后的长度如下
所以考虑\(dp[i][j]\)表示长度为\(i\)合并后长度为\(j\)的字符串的数量。于是有
暴力转移的话时间复杂度是\(O(n^3\)的,但注意这些都是连续和的形式,不妨记\(sum[i][j]\)为长度为所有小于\(i\)并且合并后长度为\(j\)的\(dp\)的值,然后转移的时候\(dp[i][j]=dp[i][j]+25*(sum[i-1][j-2]-sum[i-10][j-2])\),其他类似,注意这里乘的是\(25\),因为要保证和前一段连续的字符不一样。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,P;
cin>>n>>P;
vector<vector<ll>>dp(n+1,vector<ll>(n+1));
vector<vector<ll>>sum(n+1,vector<ll>(n+1));
auto cal=[&](int i)->int{
if(i<10) return 2;
if(i<100) return 3;
if(i<1000) return 4;
return 5;
};
for(int i=1;i<=n;i++)
{
int k=cal(i);
if(k<n) dp[i][k]=(dp[i][k]+26)%P;
for(int j=2;j<n;j++)
{
dp[i][j]=(dp[i][j]+25*(sum[max(0,i-1)][max(0,j-2)]-sum[max(0,i-10)][max(0,j-2)])%P)%P;
dp[i][j]=(dp[i][j]+25*(sum[max(0,i-10)][max(0,j-3)]-sum[max(0,i-100)][max(0,j-3)])%P)%P;
dp[i][j]=(dp[i][j]+25*(sum[max(0,i-100)][max(0,j-4)]-sum[max(0,i-1000)][max(0,j-4)])%P)%P;
dp[i][j]=(dp[i][j]+25*(sum[max(0,i-1000)][max(0,j-5)]-sum[max(0,i-10000)][max(0,j-5)])%P)%P;
sum[i][j]=(sum[i-1][j]+dp[i][j])%P;
}
}
ll ans=0;
for(int i=1;i<n;i++)ans=(ans+dp[n][i])%P;
if(ans<0) ans+=P;
cout<<ans<<'\n';
return 0;
}
F. - Ignore Operations(贪心)
Problem
有一个初始数\(x=0\),现在可以进行\(N\)次操作并且可以跳过其中\(K\)步,操作类型分为\(2\)种:
1.y x=y
给定\(y\),让\(x=y\)
2.y x=x+y
给定\(y\),让\(x=x+y\)
问最后可以获得的\(x\)的最大值
Sol
注意到如果在某一步中使用了操作\(1\),那么改步前面的所有操作都是无效的,在前面使用跳过次数显然不理智。所以我们可以从后往前枚举操作,如果是操作 1,考虑这次操作 1 不跳过,并且把这次操作后面的所有操作 1 跳过,然后贪心特跳过操作 2,就是把最小的操作 2 能跳过跳过,然后更新答案,这样可以保证跳过次数不浪费,因为肯定不会给前面的操作使用。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,k;
cin>>n>>k;
vector<pair<int,int>>op(n+1);
for(int i=1;i<=n;i++) cin>>op[i].first>>op[i].second;
priority_queue<int>q;
op[0]={1,0};
ll sum=0,ans=-1e18,sum_=0;
//sum存没有反悔的操作2,sum_存反悔的操作2
int cnt=0;
for(int i=n;i>=0;i--)
{
if(op[i].first==1)
{
ans=max(ans,op[i].second+sum-sum_);
if(k==0) break;
k--;
if(q.size()>k){
sum_-=q.top();
q.pop();
}
}else
{
//如果小于0并且当前可以跳过,就跳过
if(op[i].second<0)
if(q.size()<k)//如果小于0并且当前可以跳过,就跳过
{
sum_+=op[i].second;
q.push(op[i].second);
}else if(q.size()&&q.top()>op[i].second)//如果不能跳过,把之前负的最大的取消反悔
{
sum_-=q.top();
q.pop();
q.push(op[i].second);
sum_+=op[i].second;
}
sum+=op[i].second;
}
}
cout<<ans<<'\n';
return 0;
}
G.- Xor Cards(线性基)
Problem
给定\(N\)张卡牌,每张卡牌正面写着\(A_i\),背面写着\(B_i\)。要求选出几张卡牌,满足这些卡牌正面的数异或和不大于\(K\),并且背面的异或和最大,输出这个最大异或和.
Sol
对题解进行翻译,不会
考虑将\(A_i,B_i\)同时插入到线性基\(a,b\)中,如果\(A_i,B_i\)不能插入线性基中,那么就把\(B_i\)插入另一个线性基\(L\)中。构造完后,考虑先满足第一个条件即正面异或和不大于\(K\),从高位开始检查,如果\(K\)的第\(i\)位是\(0\),那么跳过,因为这一位选了线性基中的数,肯定不满足比\(K\)小,否则,在满足了比\(K\)小的前提下,考虑把高于\(i\)位的线性基\(a,b\)中的数加入答案,然后把低于\(i\)位的线性基\(b\)中的数加入线性基\(L\)中,最后再从高位检查到低位更新答案即可。
#include <bits/stdc++.h>
using namespace std;
const int N=33;
int a[N],b[N];
struct LinearBasis{
int bit[N];
void init()
{
memset(bit,0,sizeof bit);
}
bool insert(int x)
{
for(int i=29;i>=0;i--){
if(!(x>>i)&1) continue;
if(bit[i]){
x^=bit[i];
continue;
}
bit[i]=x;
return true;
}
return false;
}
}L2;
bool Insert(int &x,int &y)
{
for(int i=29;i>=0;i--)
{
if(!(x>>i)&1) continue;
if(a[i]){ x^=a[i],y^=b[i];continue;}
for(int j=0;j<i;j++)
if((x>>j)&1) x^=a[j],y^=b[j];
for(int j=29;j>i;j--) if((a[j]>>i)&1) a[j]^=x,b[j]^=y;
a[i]=x,b[i]=y;
return true;
}
return false;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,k;
cin>>n>>k;
k++;
bool flag=true;
for(int i=1;i<=n;i++)
{
int a,b;
cin>>a>>b;
if(!Insert(a,b))
{
L2.insert(b);
flag=false;
}
}
for(int i=0;i<=29;i++)
{
if(a[i]==0) continue;
if(a[i]>=k&&flag){
cout<<"-1\n";
return 0;
}
break;
}
int ans=-1;
for(int i=30;i>=0;i--)
{
if(!((k>>i)&1))continue;
int p=0,q=0;
for(int j=29;j>i;j--)
if((k>>j)&1) p^=a[j],q^=b[j];
if(((p>>i)&1)||p>=k) continue;
LinearBasis L=L2;
for(int j=0;j<i;j++) L.insert(b[j]);
for(int j=29;j>=0;j--)if((q^L.bit[j])>q) q^=L.bit[j];
if(q>ans) ans=q;
}
cout<<ans<<'\n';
return 0;
}
线性基
概念
线性基是由一个集合构造出来的另一个集合
性质
- 原集合的元素可以通过线性基中的某些数异或得到,原集合的元素的所有相互异或得到的值也可以通过线性基中的元素异或得到。(基底性)
- 线性基是满足上述条件的最小的集合
- 线性基没有异或和为0的子集(线性无关性)
- 线性基中每个元素的异或方案唯一,也就是说,线性基中不同的异或组合异或出来的数都是不一样的(线性基)
- 线性基中每个元素的二进制最高位互不相同
线性代数角度
- 假设原集合为\(S\),线性基为\(V\),那么\(|V|\le |S|\),假设集合里最大的数为\(N\)。把线性基每个数以二进制表示,那么线性基就可以看做是一个基底,线性基里面的数呈线性无关。并且线性基中元素个数最多为\(log_2N\)。
构造
假设\(S=\{01001101,01101001\}\),首先插入\(y=01001101\),则\(p[6]=01001101\),然后插入\(x=01101001\),因为\(p[6]\)已经存在了,说明一定可以\(x\)的第\(6\)位应经可以由\(y=01001101\)表出,把\(x\)和\(y=01001101\)异或,考虑\(x\)的第\(i\)位(\(i\lt 6\)),若\(x_i=1\)并且\(y_i=1\),说明第\(i\)位可以被\(y\)表出,不用考虑,异或后第\(i\)位为\(0\),不用管;若\(x_i=1\)并且\(y_i=0\),异或后第\(i\)位为\(1\),说明第\(i\)位不能由\(y\)表出,所以继续去找可以表出第\(i\)位的\(p[i]\),若存在,继续想之前那样操作,反之,说明目前线性基中没有可以表出\(x\)的方案,所以\(x\)就要加入线性基中。
inline void insert(long long x) {
for (int i = 55; i + 1; i--) {
if (!(x >> i)) // x的第i位是0
continue;
if (!p[i]) {
p[i] = x;
break;
}
x ^= p[i];
}
}