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]\) 级祖先,那么这个点集合是:

\[\bigcup_{i=0}^{d} S_{u_i,d-i}\cup S_{u_i,d-i-1} \]

而且这些 \(S\) 都是不交的。

这个你让我怎么证啊,画画图的事。

如果这个点上面不够 \(d\) 个祖先,那么在根的位置上要把没覆盖的层都覆盖掉。

我们转而统一将操作放在这些集合上,因为 \(d\le 40\),所以每个点只会在 \(d\) 个不同的 \(S\) 里,因为你想想 \(S\) 是怎么出来的,是不是要对每一个 \(u\) 去找祖先然后加进这个祖先对应的位置里面去。

于是所有操作都是 \(O(d)\) 的,做完了。

QOJ 8547

上题的转化相同,处理要离线,但是没怎么听懂。

Luogu P10045

很颠的拆贡献题。

我们这么加这些数还是奇数,都可以被写成 \(2a+1\) 的形式。

于是询问的乘法就是这个东西:

\[\prod_{i=l}^{r} (2a_i+1) \bmod2^{20} \]

然后是猎奇的,你考虑这个东西的组合意义是什么,不然你没法维护。

你试试把这个东西展开之后,首先乘了 \(20+\)\(2a_i\) 的项肯定是没了。

然后剩下的东西要么乘 \(1\) 要么去乘 \(2a_i\),也就是说它的组合意义其实是一些项选择 \(2a_i\),一些项选择 \(1\),这些选择方式乘出来的数的和。

这个怎么维护?发现乘 \(1\) 很扯,于是我们尝试维护乘 \(2a_i\) 的数量,通过组合数把 \(a_i\)\(1\) 乘进去,当成算方案了。

于是令 \(f_j\) 表示选了 \(j\)\(2a_i\) 状物的答案和。

然后接下来在线段树每个点上挂上这些在加什么的就能维护了。我为什么要会这些,睡觉去了。

posted @ 2025-11-21 21:39  hm2ns  阅读(28)  评论(0)    收藏  举报