省选集训 16 - 杂题
[CF1442D] Sum
由于序列单增,所以最后至多有一个数组不被选满。
证明简单,假设有两个数组 \(x\),\(y\) 分别选到第 \(i\) 和 \(j\) 项并没有选满。
如果 \(x_{i+1}\leq y_{j+1}\),那么有 \(x_{i}\leq y_{j+1}\),将 \(x_{i}\) 替换为 \(y_{j+1}\) 显然不劣。
然后只需要知道去掉每个位置后的背包,枚举当前位置选多少个就行了。
套路地进行分治,即将 \([l,mid]\) 丢进背包后递归 \((mid,r]\),另一边同理。
这样递归到叶子就是去掉当前位置的背包了,在叶子统计答案即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=3005,INF=0x3f3f3f3f3f3f3f3f;
vector<int> v[N];
int n,k,ans=-INF,t[N],dp[N],sum[N];
void solve(int l,int r){
int mid=(l+r)>>1,now=0;
if(l==r){
for(int i=1;i<=min(k,t[l]);i++)
ans=max(ans,(now+=v[l][i])+dp[k-i]);
return ans=max(ans,dp[k]),void();
}
vector<int> tmp(dp,dp+k+1);
for(int i=l;i<=mid;i++)
for(int j=k;j>=t[i];j--)
dp[j]=max(dp[j],dp[j-t[i]]+sum[i]);
solve(mid+1,r),copy(tmp.begin(),tmp.end(),dp);
for(int i=mid+1;i<=r;i++)
for(int j=k;j>=t[i];j--)
dp[j]=max(dp[j],dp[j-t[i]]+sum[i]);
solve(l,mid),copy(tmp.begin(),tmp.end(),dp);
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
cin>>n>>k,fill(dp+1,dp+k+1,-INF);
for(int i=1;i<=n;i++){
cin>>t[i],v[i].resize(t[i]+1,0);
for(int j=1;j<=t[i];j++)
cin>>v[i][j],sum[i]+=v[i][j];
}
solve(1,n),cout<<ans<<"\n";
}
[Luogu P13552] 鱼类考古学
先观察数据范围,\(10^6\) 还是位运算题目,大致得出复杂度在 \(O(n\log V)\) 左右。
但是拆位后,如果先加后按位与,答案会受进位影响,那有没有办法先与后加呢?
惊奇地发现 \((a\&b)+c\geq c \geq (a+b)\&c\),所以遇到 \((a+b)\&c\) 的情况一定能进行替换。
那题目就被简化为:将 \(n\) 个数划分为 \(k\) 个不交集合,使得各集合按位与后的求和最大。
于是我们就可以进行拆位,每次让当前位的答案最大就可以了,设有 \(cnt\) 个数当前位为 \(1\)。
当 \(cnt<k\) 时,显然应当让当前位为 \(1\) 的全部独立划分为一个集合。
所以可以直接让答案加上这些数,并把这些数删去,变成集合和 \(k\) 都更小的子问题。
当 \(cnt\geq k\) 时,如果当前位全为 \(1\),那不管怎么划分这一位答案都相同,直接加上就行了。
否则,我们一定要将这一位的所有 \(0\) 并在一起才最优,这一位就只能贡献 \(k-1\) 个 \(1\)。
然后用 vector 模拟实现上述过程即可。
注意在 \(cnt\geq k\) 的时候要将当前位的 \(1\) 抹去,否则会在后续 \(cnt<k\) 时重复统计贡献。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
long long t,n,k,ans;
void solve(){
cin>>n>>k,ans=0;vector<int> v(n,0);
for(auto &x:v) cin>>x;
for(int i=29;i>=0;i--){
vector<int> tmp(0,0);
int cnt=0,now=(1<<30)-1;
for(auto x:v) cnt+=(x>>i&1);
if(cnt<k){
for(auto x:v){
if(x>>i&1) ans+=x,k--;
else tmp.push_back(x);
}
v=tmp;
}
else{
for(auto x:v){
if(!(x>>i&1)) now&=x;
else tmp.push_back(x^(1<<i));
}
v=tmp,ans+=(1<<i)*(k-1);
if(now==(1<<30)-1) ans+=(1<<i);
else v.push_back(now);
}
}
return cout<<ans<<"\n",void();
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
cin>>t;while(t--) solve();
}
[AGC054D] (ox)
首先发现字符 o 无论放在哪里都不会对括号序列的合法性产生影响。
而字符 x 除了多一个不能放在两个合法括号序列中间的要求和 o 是一样的。
所以我们可以先把 o 和 x 去掉,只考虑原序列中的括号。
对于普通的括号序列一般令 ( 的值为 \(1\),) 的值为 \(-1\)。
当前缀和 \(<0\) 时将后面最靠近的 ( 提到当前位置一定最优。
而因为 o 和 x 的位置不会改变括号序列合法性,所以操作完后括号的相对顺序就已确定。
既然括号序列的相对顺序已经固定了,那有没有办法让 o 和 x 的相对顺序固定下来呢?
发现当 x 的左边第一个括号为 ( 时一定满足条件。
当 x 的左边第一个括号为 ) 时,将 x 移动到 ) 前和将 ) 移动到 x 后是等价的。
对 x 右边括号的分析同理,所以只移动括号就能达成最小代价,即可将 o 和 x 相对顺序固定。
既然括号和字母的相对顺序都固定下来了,就容易进行 DP 了。
令 \(f_{i,j}\) 表示使用了前 \(i\) 个括号和前 \(j\) 个字母的最小代价。
\(su1_{i,j}\) 表示前 \(j\) 个字母中原本顺序在第 \(i\) 个括号前的数量,前缀和处理。
\(su2_{i,j}\) 表示前 \(i\) 个括号中原本顺序在第 \(j\) 个字母前的数量,前缀和处理。
显然有 \(f_{i,j}=\min(f_{i-1,j}+su1_{i,j},f_{i,j-1}+su2_{i,j})\)。
注意当第 \(j\) 个字母为 x 时需要前 \(i\) 个括号不构成合法序列才能取到后面一项。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=8005,INF=0x3f3f3f3f;
char ch[N],br[N];
int dp[N][N],su1[N][N],su2[N][N];
int n,cb,co,ans,pb[N],po[N],sum[N];
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
cin>>ch,n=strlen(ch),copy(ch,ch+n,ch+1),ch[0]='o';
for(int i=1;i<=n;i++){
if(ch[i]=='('||ch[i]==')') pb[++cb]=i;
if(ch[i]=='o'||ch[i]=='x') po[++co]=i;
}
for(int i=1,j,k;i<=cb;i++){
if(ch[pb[i]]=='('){sum[i]=sum[i-1]+1;continue;}
if((sum[i]=sum[i-1]-1)>=0) continue;
for(j=i,sum[i]+=2;ch[pb[j]]==')';j++);
for(k=j;k>i;k--) swap(pb[k],pb[k-1]),ans++;
}
for(int i=1;i<=cb;i++){
for(int j=1;j<=co;j++){
su1[i][j]=su1[i][j-1]+(pb[i]<po[j]);
su2[i][j]=su2[i-1][j]+(po[j]<pb[i]);
}
}
for(int j=0;j<=co;j++){
if(ch[po[j]]=='o') continue;
fill(dp[0]+j,dp[0]+N,INF);break;
}
for(int i=1;i<=cb;i++){
for(int j=1;j<=co;j++){
dp[i][j]=dp[i-1][j]+su1[i][j];
if(!sum[i]&&ch[po[j]]=='x') continue;
dp[i][j]=min(dp[i][j],dp[i][j-1]+su2[i][j]);
}
}
cout<<ans+dp[cb][co]<<"\n";
}

浙公网安备 33010602011771号