OI 笑传 #32
今天是 bct Day2,赛时 \(40+60+10+0=110\),rk 70。
挂分原因是被 vector 卡常了/fn。然后 T4 捆绑 Sbt#1 T 了一个于是又没了 20pts。
评价是 ok 场,练习了对拍的使用。
发现 hm2ns 总是会随口否掉一些他看起来很错实际上对完了的性质,今天的 T2 是第三次出现这种情况,上一次是 CSPS T2,上上次忘了。
T1
算贡献即可,不需要用 vector 存质因数。
code
Show me the code
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef __int128 i128;
typedef unsigned long long u64;
const int N=1e6+88;
i64 a[N],b[N];
i64 ta[N],tb[N];
vector<pair<int,int> > k[N];
int main(){
int n;cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];ta[a[i]]++;
}
for(int i=1;i<=n;i++){
cin>>b[i];tb[b[i]]++;
}
for(int i=1;i<=n;i++){
for(int j=1;i*j<=n&&j<=i;j++){
k[i*j].push_back(make_pair(i,j));
}
}
i64 ans=0;
for(int i=1;i<=n;i++){
for(pair<int,int> as:k[i]){
if(as.first==as.second){
ans+=ta[as.first]*tb[as.second]*a[i];
}
else{
ans+=ta[as.first]*tb[as.second]*a[i];
ans+=tb[as.first]*ta[as.second]*a[i];
}
}
}
cout<<ans;
return 0;
}
T2
平衡树题,实际上是线段树题。
hm2ns 很快的否掉了对值域区间操作相对大小不变这个性质。但实际上这是对的。
然后在排完序的数组里做区间操作就是对值域操作,在段里记录对应值的左右端点。
需要不少标记。感谢 sheep 的对拍。
code
Show me the code
#define rd read()
#define mkp make_pair
#define ls p<<1
#define rs p<<1|1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef unsigned long long u64;
typedef unsigned int u32;
typedef __int128 i128;
i64 read(){
i64 x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=5e5+5;
int n,q;
int a[N];
struct seg{
int l;
int r;
int vl;
int vr;
int p1;
int p0;
bool rst;
bool rid;
int cnt;
int cglg;
}t[N<<2];
int maxbp;
void b(int p,int l,int r){
t[p].l=l;t[p].r=r;
t[p].p1=t[p].p0=t[p].rst=t[p].rid=0;
t[p].cglg=-1;
maxbp=max(maxbp,p);
if(l==r){
t[p].vl=a[l];
t[p].vr=a[r];
t[p].p0=(a[l]%2==0)?1:0;
t[p].p1=(a[l]%2==1)?1:0;
return ;
}
int mid=l+r>>1;
b(ls,l,mid);b(rs,mid+1,r);
t[p].vl=t[ls].vl;
t[p].vr=t[rs].vr;
t[p].p1=t[ls].p1+t[rs].p1;
t[p].p0=t[ls].p0+t[rs].p0;
return ;
}
void pushdown(int p){
if(!t[p].rst)return ;
t[ls].rst=t[p].rst;
t[rs].rst=t[p].rst;
t[ls].rid=t[p].rid;
t[rs].rid=t[p].rid;
if(t[p].cglg!=-1){
if(t[ls].vl%2==t[p].cglg)t[ls].vl--;
if(t[ls].vr%2==t[p].cglg)t[ls].vr--;
if(t[rs].vl%2==t[p].cglg)t[rs].vl--;
if(t[rs].vr%2==t[p].cglg)t[rs].vr--;
t[ls].cglg=t[p].cglg;
t[rs].cglg=t[p].cglg;
t[p].cglg=-1;
}
t[ls].vl-=t[p].cnt;
t[ls].vr-=t[p].cnt;
t[rs].vl-=t[p].cnt;
t[rs].vr-=t[p].cnt;
t[ls].cnt+=t[p].cnt;
t[rs].cnt+=t[p].cnt;
if(t[ls].rid==1){
t[ls].p1=t[ls].p1+t[ls].p0;
t[ls].p0=0;
}
else{
t[ls].p0=t[ls].p1+t[ls].p0;
t[ls].p1=0;
}
if(t[rs].rid==1){
t[rs].p1=t[rs].p1+t[rs].p0;
t[rs].p0=0;
}
else{
t[rs].p0=t[rs].p1+t[rs].p0;
t[rs].p1=0;
}
t[p].cnt=0;
t[p].rst=0;
t[p].rid=0;
return ;
}
int modi(int p,int l,int r,int c){
if(t[p].r>n||p==0||p>maxbp)return 0;
if(l<=t[p].vl&&t[p].vr<=r){
if(t[p].rst==1){
if(t[p].rid==c){
t[p].rid^=1;
t[p].vl--;t[p].vr--;
swap(t[p].p0,t[p].p1);
t[p].cnt++;
return t[p].r-t[p].l+1;
}
else{
return 0;
}
}
if(c==0){
int pt=t[p].p0;
t[p].p1+=t[p].p0;
t[p].p0=0;
t[p].rst=1;
t[p].rid=1;
if(t[p].vl%2==0)t[p].vl--;
if(t[p].vr%2==0)t[p].vr--;
t[p].cglg=c;
// t[p].cnt++;
return pt;
}
if(c==1){
int pt=t[p].p1;
t[p].p0+=t[p].p1;
t[p].p1=0;
t[p].rst=1;
t[p].rid=0;
if(t[p].vl%2==1)t[p].vl--;
if(t[p].vr%2==1)t[p].vr--;
t[p].cglg=c;
// t[p].cnt++;
return pt;
}
}
pushdown(p);
i64 sub=0;
if(l<=t[ls].vr)sub+=modi(ls,l,r,c);
if(t[rs].vl<=r)sub+=modi(rs,l,r,c);
if(t[p].l!=t[p].r){
t[p].vl=t[ls].vl;
t[p].vr=t[rs].vr;
t[p].p1=t[ls].p1+t[rs].p1;
t[p].p0=t[ls].p0+t[rs].p0;
}
return sub;
}
int main(){
cin>>n>>q;
i64 ans=0;
for(int i=1;i<=n;i++){
a[i]=rd;
ans+=a[i];
}
sort(a+1,a+1+n);
b(1,1,n);
for(int i=1;i<=q;i++){
int l,r,c;
cin>>l>>r>>c;
ans-=modi(1,l,r,c);
cout<<ans<<'\n';
}
return 0;
}
赛时写了个简单易用的 check 程序,这是好的:
Show me the code
#define rd read()
#include<bits/stdc++.h>
#include<windows.h>
using namespace std;
typedef long long ll;
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int main(){
string s;cin>>s;
if(s=="1"){
system("a.exe < a.in > a.out");
}
else if(s=="bk"){
double beg=clock();
system("a.exe < a.in > a.out");
double ed=clock();
system("brute.exe < a.in > a.ans");
system("fc a.out a.ans");
cout<<"program used "<<ed-beg<<' '<<" ms";
}
else if(s=="mul"){
int k;cin>>k;
while(k--){
system("gen.exe > a.in");
double beg=clock();
system("a.exe < a.in > a.out");
double ed=clock();
system("brute.exe < a.in > a.ans");
if(system("fc a.out a.ans")){
cout<<"wa"<<'\n';
Sleep(19923134);
}
cout<<"program used "<<ed-beg<<' '<<" ms";
}
}
return 0;
}
T3
首先当然要去想二分,但是这东西怎么二分啊,最大子段和最小?
于是赛时对着前一些部分分搞了一个阴间的没边的 DP,结果只过了最小的 subtask。
code
Show me the code
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef __int128 i128;
typedef unsigned long long u64;
const int N=3e3;
i64 dp[N][N];
i64 raw[N][N];
i64 a[N],b[N];
i64 c[N];
i64 dm[N];
int main(){
int n,km;
cin>>n>>km;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)cin>>b[i];
if(n<=16){
i64 ans=1e18;
int op=0;
for(int i=0;i<(1<<n);i++){
int k=0;
for(int j=0;j<n;j++){
if((i>>j)&1){
c[j+1]=a[j+1];
k++;
}
else{
c[j+1]=b[j+1];
}
dm[j]=0;
}
if(k!=km)continue;
i64 dans=0;
for(int j=1;j<=n;j++){
dm[j]=max(dm[j-1]+c[j],c[j]);
dm[j]=max(dm[j],0ll);
dans=max(dans,dm[j]);
}
if(ans>dans){
op=i;
ans=dans;
}
}
cout<<ans<<'\n';
for(int i=0;i<n;i++){
if((op>>i)&1){
cout<<'A';
}
else{
cout<<'B';
}
}
return 0;
}
if(n>16){
i64 ans=0;
int wp=0;
for(int i=1;i<=n;i++){
for(int k=0;k<=min(km,i);k++){
if(k==0){
dp[i][0]=dp[i-1][0]+b[i];
raw[i][0]=raw[i-1][0]+b[i];
continue;
}
if(i==k){
dp[i][k]=dp[i-1][k-1]+a[i];
raw[i][k]=raw[i-1][k-1]+a[i];
}
else{
dp[i][k]=min(dp[i-1][k-1]+a[i],dp[i-1][k]+b[i]);
if(dp[i][k]==dp[i-1][k]+b[i]){
raw[i][k]=raw[i-1][k]+b[i];
}
else{
raw[i][k]=raw[i-1][k-1]+a[i];
}
}
dp[i][k]=max(dp[i][k],0ll);
}
}
vector<char> vc;
int dk=km;
for(int i=n;i>=1;i--){
if(dk==0){
vc.push_back('B');
continue;
}
if(raw[i][dk]==raw[i-1][dk]+b[i]&&raw[i][dk]==raw[i-1][dk-1]+a[i]){
vc.push_back('A');
dk--;
}
else if(raw[i][dk]==raw[i-1][dk-1]+a[i]){
vc.push_back('A');
dk--;
}
else{
vc.push_back('B');
}
}
cout<<dp[n][km]<<'\n';
reverse(vc.begin(),vc.end());
for(char c:vc){
cout<<c;
}
}
return 0;
}
这东西离 50pts 的解法挺接近了(吗?),状态是没问题的,转移有个边界没判掉导致我以为这东西不用二分,脑子不知道在干嘛。
首先我们最大子段和是维护的前 \(i\) 个数用了 \(j\) 个 \(A\) 的数时,最大后缀和是什么。
对于所有的 \(i,j\),如果我们钦定了最大子段和,那么所有的 \(dp_{i,j}\) 都应该小于等于这个值。
然后我套上二分,判上这个条件就有 \(50\) 了,我在干啥啊。
正解是对这个 DP 的斜率优化,跳了。
T4
赛时写了状压+DFS,结果就 TLE 了 subtask 1 的 一个点,评价是不会剪枝+卡时判无解导致的。
结果真是神秘搜索题,跳了。
数据结构选讲
Luogu P9527
sheep 一眼了这题,首先想想 \(d=1\) 的时候怎么做。
又是一个经典的技巧是我们维护去维护这个点的父亲,也就是我们把这个点操作统一给了父亲,注意父亲自己也要挂上这个 tag。
然后我们查一个点的时候就把自己和它父亲的 tag 合起来就是了。
回到这里发现 \(d\) 很小,然后想想能不能也把这个距离不超过 \(d\) 的点拆成上面的样子。
我们设一个集合 \(S_{u,i}\) 表示已 \(u\) 为根的子树中深度为 \(i\) 的点(\(u\) 深度是 \(0\))。
然后对于一个点 \(i\),距离它不超过 \(d\) 的点集合是这么拆,我们依然往上找祖先,相当于一层层的 cover 掉。
具体的,我们设 \(u_i\) 是 \(u\) 的第 \(i\in [0,d]\) 级祖先,那么这个点集合是:
而且这些 \(S\) 都是不交的。
这个你让我怎么证啊,画画图的事。
如果这个点上面不够 \(d\) 个祖先,那么在根的位置上要把没覆盖的层都覆盖掉。
我们转而统一将操作放在这些集合上,因为 \(d\le 40\),所以每个点只会在 \(d\) 个不同的 \(S\) 里,因为你想想 \(S\) 是怎么出来的,是不是要对每一个 \(u\) 去找祖先然后加进这个祖先对应的位置里面去。
于是所有操作都是 \(O(d)\) 的,做完了。
QOJ 8547
上题的转化相同,处理要离线,但是没怎么听懂。
Luogu P10045
很颠的拆贡献题。
我们这么加这些数还是奇数,都可以被写成 \(2a+1\) 的形式。
于是询问的乘法就是这个东西:
然后是猎奇的,你考虑这个东西的组合意义是什么,不然你没法维护。
你试试把这个东西展开之后,首先乘了 \(20+\) 个 \(2a_i\) 的项肯定是没了。
然后剩下的东西要么乘 \(1\) 要么去乘 \(2a_i\),也就是说它的组合意义其实是一些项选择 \(2a_i\),一些项选择 \(1\),这些选择方式乘出来的数的和。
这个怎么维护?发现乘 \(1\) 很扯,于是我们尝试维护乘 \(2a_i\) 的数量,通过组合数把 \(a_i\) 和 \(1\) 乘进去,当成算方案了。
于是令 \(f_j\) 表示选了 \(j\) 个 \(2a_i\) 状物的答案和。
然后接下来在线段树每个点上挂上这些在加什么的就能维护了。我为什么要会这些,睡觉去了。

浙公网安备 33010602011771号