P11990 [JOIST 2025] 大会 / Conference 题解

题目链接:P11990 [JOIST 2025] 大会 / Conference#11408. Conference

考虑将原字符串拆分成若干个被已填充字符包裹的连续问号串 d??...??e,有以下两种状态:

  1. $ d=e $,不妨设 $ d=A $,则如果这一段连续问号串中可以填入的只有 $ A $,则代价为 $ 0 $,如果连续问号串中有 $ AB $ 或 $ AC $,则代价为 $ 2 $,如果 $ ABC $ 均包含,则代价为 $ 3 $。

  2. $ d\not = e $,不妨设 $ d=A, e=B $,如果这一段连续问号串中可以填入的只有 $ AB $(这里包括两个端点,1 中也是),则代价为 $ 1 $,如果 $ ABC $ 均包含,则代价为 $ 2 $。

假如已经决定每段允许的字符集合,设 $ low_x $ 代表可以填入的只有 $ x $ 的段问号数量之和,$ up_x $ 代表可以填入 $ x $ 的段问号数量之和(即包括 $ x, xy, xz, xyz $,其中 $ y, z $ 为除了 $ x $ 的剩下两个字符),$ tar_x $ 代表给定的查询中要求的 $ x $ 的数量,则存在合法填法当且仅当对于三个字符都有 $ low_x \le tar_x \le up_x $,这个是根据 Hall 定理得到的(Hall 定理的学习待补 /yun)。

我们设 $ S_A, S_B, S_C $ 为三个两端相等的连续问号串的长度总和,$ P_{AB}, P_{BC}, P_{CA} $ 为三个两端不等的连续问号串的长度总和。设 $ T_x=S_x+P_{xy}+P_{xz} $,则 $ S_x $ 是字符 $ x $ 的初始下界(即 $ low_x $ ),$ T_x $ 是字符 $ x $ 的初始上界(即 $ up_x $ )。如果 $ tar_x > T_x $,记为 big,如果 $ tar_x < S_x $,记为 small。最多只会有两个 big 字符,正确性显然。

在没有 big 字符的时候,显然只用考虑将一部分单字符区间变成双字符区间以减少尚不满足要求的 $ low_x $,显然对于当前还不满足的字符选择若干个长度最大的区间是不劣的(因为代价都为 2,而区间越大对应的 $ S_x $ 减少越多),进行前缀和和二分即可。

在有 1 个 big 字符的时候,不妨假设 $ A $ 为 big,此时要先满足 $ B, C $ 的下界,和没有 big 字符的操作是一样的,假设 $ low_B $ 相对于最初减少了 $ d_B $,总共选了 $ k_B $ 个区间,$ low_C $ 相对于最初减少了 $ d_C $,总共选了 $ k_C $ 个区间,则这一部分的代价为 $ 2k_B + 2k_C $,并且减少的部分都可以填入 $ A $,还差 $ need = tar_A - T_A - d_B - d_C $,如果 $ need $ 已经小于等于 $ 0 $,则已经满足,否则要将若干个不能填 $ A $ 的区间增加填 $ A $,然后要考虑最小代价。

显然在仅有 $ B $ 或者仅有 $ C $ 的区间添加 $ A $ 代价为 $ 2 $,有 $ BC $ 的区间添加 $ A $ 的代价为 $ 1 $,首先将这三种的区间长度都降序排序,考虑选奇数个 $ BC $ 区间还是偶数个 $ BC $ 区间,如果奇数个则取最长的一个,然后将第 $ 2i $ 个和 $ 2i + 1 $ 个区间长度加起来,这样也变成了代价为 $ 2 $ 的选项,偶数个则是第 $ 2i - 1 $ 和 $ 2i $ 个区间长度加起来。对于给定的三个降序序列求出最小的 $ v $,使得所有大于等于 $ v $ 的值都被取走,然后要让取走的数的和至少达到 $ need $,很显然可以预处理加二分 $ v $ 解决。

在有 2 个 big 字符的时候,不妨假设 $ B, C $ 是 big,则 $ A $ 一定是 small(有 $ tar_x = S_A + S_B + S_C + P_{AB} + P_{BC} + P_{CA} - tar_B - tar_C < S_A + S_B + S_C + P_{AB} + P_{BC} + P_{CA} - T_B - T_C = S_A - P_{BC} \le S_A $),必须像没有 big 字符时对 small 字符的操作操作 $ A $。设取了 $ k $ 个区间,长度和为 $ D $,代价为 $ 2k $,两个 big 字符缺的容量为 $ tar_B + tar_C - T_B - T_C = S_A - tar_A - P_{BC} \le S_A - tar_A \le D $,因此显然 $ A $ 扩展的 $ k $ 个区间已经足够。

设分给 $ B $ 的长度为 $ d_B $,则有分给 $ C $ 的长度为 $ D - d_B $。如果 $ A $ 取的前 $ k $ 个区间的长度无法凑出一个 $ x $ 满足 $ x \le d_B $ 且 $ D - x \le d_C $,即 $ x \in [D - d_C, d_B] $,则显然是需要将那个一个区间变成 $ ABC $,代价 $ +1 $(为什么只需要变一个显然,因为大的那方加上自己选择的最大区间就大了,不加上就小了,将这个区间变为 $ ABC $ 显然可行,如果前 $ k $ 个区间不能构造出合法的 $ x $,将前 $ k $ 个区间中的若干个替换为更小的区间只会让 $ x $ 和 $ D - x $ 中的一个变小一个不变(假设将第 $ a $ 个区间换成第 $ b $ 个区间,那么显然是一个变小一个不变,要想让一个增大只会让另一个不符合条件,原因和只需要变一个的原因一样)),即代价为 $ 2k+1 $。

对于一个不升的序列 $ a_1\le a_2 \le \cdots \le a_n $,定义 $ dp[s] $ 为恰好子集和组成 $ s $ 所需的 $ a $ 的最短前缀长度,则上面的 $ x $ 的区间 $ [L, R] $ 是否可满足只需查询 $ \min_{L\le i \le R} dp[i] $ 是否小于 $ k $ 即可,求出前 $ k $ 个 $ a $ 中的数组成的 dp 数组然后就是 RMQ 问题了。

直接进行背包 dp 是 $ O(n^2) $ 的,显然过不了(如果采用 bitset 优化成 $ O(\frac{n^2}{w}) $ 卡卡常数可以过),考虑将一组重量(对应区间的长度)相同的数同时处理,假设有 $ c $ 个,每个重量为 $ w $,在序列中的编号为 $ l, l+1, \cdots, l+c-1 $,假设处理前某个和 $ s_0 $ 已经可以背包出,则处理完当前重量的数后,对于 $ s = s_0 + tw, 1\le t \le c $ 的 $ s $ 均能凑出。则只需要枚举模 $ w $ 的余数,将每个 $ s $ 写成 $ s = r + tw $ 的形式,如果之前有 $ s_0 = r + t_0w $ 可达,且 $ 1\le t - t_0 \le c $,则对于凑出 $ s $ 的一个可能前缀长度为 $ l + t - t_0 - 1 $,记录当前满足 $ dp[s] < l $ 的最大 $ s $ 即可。

对于复杂度,有对于不同长度的数的数量 $ 1 + 2 + \cdots x \le n $ 有 $ x \le \sqrt n $,dp 的复杂度为 $ O(n\sqrt n + \sum_{i=1}^x \frac{n}{i}) = O(n\sqrt n) $,前面的查询部分是 $ O(qlogn) $ 的,总复杂度为 $ O(n\sqrt n + qlogn) $。

代码实现比较恶心。

代码:

#include<bits/stdc++.h>
#define time(null) chrono::steady_clock::now().time_since_epoch().count()
#define uint unsigned long long
#define debug() cout<<"come here\n"
#define INF 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
#define pb push_back
#define Code return
#define by 0
#define MCYYDS ;
using namespace std;
int qpow(int a,int b,int p=INF){int ret=1;while(b){if(b&1)ret=(ret*a)%p;a=(a*a)%p;b>>=1;}return ret;}
inline int read(){int ret=0,f=1;char ch=getchar();while(ch<'0'||ch>'9')f=(ch=='-'?-1:f),ch=getchar();while(ch>='0'&&ch<='9')ret=(ret<<3)+(ret<<1)+(ch^48),ch=getchar();return ret*f;}
inline void write(int x){if(x<0){putchar('-');write(-x);return ;}if(x>9)write(x/10);putchar((char)(x%10+48));}
inline void writech(int x,char ch){write(x);putchar(ch);}
struct RMQ{
	int sz;
	vector<int> a;
	void init(vector<int> &v)
	{
		sz=1;
		while(sz<v.size())sz<<=1;
		a.assign(sz+sz,INF);
		for(int i=0;i<v.size();i++)
		{
			a[i+sz]=v[i];
		}
		for(int i=sz-1;i>=1;i--)
		{
			a[i]=min(a[i*2],a[i*2+1]);
		}
	}
	int query(int l,int r)
	{
		if(l>r)return INF;
		int ans=INF;
		l+=sz;
		r+=sz+1;
		while(l<r)
		{
			if(l&1)ans=min(ans,a[l++]);
			if(r&1)ans=min(ans,a[--r]);
			l>>=1;
			r>>=1;
		}
		return ans;
	}
};
bool cmp(int x,int y)
{
	return x>y;
}
struct sub0{
	vector<int> a,s,f;
	void init(vector<int> v,int n)
	{
		a=v;
		sort(a.begin(),a.end(),cmp);
		s.assign(a.size()+1,0);
		for(int i=0;i<a.size();i++)
		{
			s[i+1]=s[i]+a[i];
		}
		f.assign(n+2,0);
		for(auto x:a)
		{
			f[x]++;
		}
		for(int i=n;i>=1;i--)
		{
			f[i]+=f[i+1];
		}
	}
	int sum()
	{
		return s.back();
	}
	int get(int x)
	{
		if(x<=0)return 0;
		return lower_bound(s.begin(),s.end(),x)-s.begin();
	}
	pii calc(int cnt,int low=0)
	{
		if(cnt>=f.size())return {0,0};
		if(f[cnt]<=low)return {0,0};
		return {f[cnt]-low,s[f[cnt]]-s[low]};
	}
};
struct sub1{
	vector<int> a;
	sub0 ev,od;
	void init(vector<int> v,int n)
	{
		a=v;
		sort(a.begin(),a.end(),cmp);
		vector<int> e,o;
		for(int i=0;i+1<a.size();i+=2)
		{
			e.pb(a[i]+a[i+1]);
		}
		for(int i=1;i+1<a.size();i+=2)
		{
			o.pb(a[i]+a[i+1]);
		}
		ev.init(e,n);
		od.init(o,n);
	}
};
struct sub2{
	int n;
	RMQ st;
	void init(vector<int> &a)
	{
		n=0;
		for(auto x:a)
		{
			n+=x;
		}
		vector<int> dp(n+1,INF);
		dp[0]=0;
		for(int l=0,r;l<a.size();l=r)
		{
			r=l+1;
			while(r<a.size()&&a[r]==a[l])r++;
			for(int i=0;i<a[l]&&i<=n;i++)
			{
				int last=-INF;
				for(int j=i,k=0;j<=n;j+=a[l],k++)
				{
					bool flag=(dp[j]<l+1);
					if(last>=k-r+l)
					{
						dp[j]=min(dp[j],l+k-last);
					}
					if(flag)last=k;
				}
			}
		}
		st.init(dp);
	}
	bool query(int len,int l,int r)
	{
		l=max(l,0);
		r=min(r,n);
		return (l<=r&&st.query(l,r)<=len);
	}
};
struct node{
	int x,y,z;
};
pii get(sub0 &a,int lowa,sub0 &b,int lowb,sub0 &c,int val)
{
	pii x=a.calc(val,lowa),y=b.calc(val,lowb),z=c.calc(val);
	return {x.first+y.first+z.first,x.second+y.second+z.second};
}
int calc(sub0 &a,int lowa,sub0 &b,int lowb,sub0 &c,int val,int n)
{
	if(val<=0)return 0;
	int tot=a.sum()-a.s[lowa]+b.sum()-b.s[lowb]+c.sum();
	if(tot<val)return INF;
	int l=1,r=n,pos=1;
	while(l<=r)
	{
		int mid=l+r>>1;
		if(get(a,lowa,b,lowb,c,mid).second>=val)l=mid+1,pos=mid;
		else r=mid-1;
	}
	pii ret=get(a,lowa,b,lowb,c,pos+1);
	return ret.first+(val-ret.second-1)/pos+1;
}
int solvesub1(int id,vector<int> tar,vector<int> low,vector<int> up,vector<sub0> &S,vector<sub1> &T,int n)
{
	int fir=(id+1)%3,sec=(id+2)%3;
	int lowf=S[fir].get(low[fir]-tar[fir]),lows=S[sec].get(low[sec]-tar[sec]);
	int val=tar[id]-up[id]-S[fir].s[lowf]-S[sec].s[lows];
	int mn=2*calc(S[fir],lowf,S[sec],lows,T[id].ev,val,n);
	if(T[id].a.size())mn=min(mn,1+2*calc(S[fir],lowf,S[sec],lows,T[id].od,val-T[id].a[0],n));
	return (lowf+lows)*2+mn;
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
//	cout.tie(0);
	int n=read();
	string s;
	cin>>s;
	vector<vector<int> > sv(3,vector<int>()),tv(3,vector<int>());
	int last=0,base=0;
	for(int i=1;i<n;i++)
	{
		if(s[i]=='?')continue;
		int len=i-last-1,l=s[last]-'A',r=s[i]-'A';
		if(l==r)
		{
			if(len)sv[l].pb(len);
		}
		else
		{
			base++;
			if(len)tv[3-l-r].pb(len);
		}
		last=i;
	}
	vector<sub0> S(3);
	vector<sub1> T(3);
	vector<sub2> P(3);
	vector<int> low(3),up(3),nw(3);
	for(int i=0;i<3;i++)
	{
		S[i].init(sv[i],n);
		T[i].init(tv[i],n);
		P[i].init(S[i].a);
		low[i]=S[i].sum();
		int sum=0;
		for(auto x:tv[i])
		{
			sum+=x;
		}
		nw[i]=sum;
	}
	for(int i=0;i<3;i++)
	{
		up[i]=low[i]+nw[0]+nw[1]+nw[2]-nw[i];
	}
	int q=read();
	while(q--)
	{
		vector<int> tar(3);
		tar[0]=read();
		tar[1]=read();
		tar[2]=read();
		vector<int> tmp;
		for(int i=0;i<3;i++)
		{
			if(tar[i]>up[i])tmp.pb(i);
		}
		int ans=0;
		if(tmp.size()==0)
		{
			for(int i=0;i<3;i++)
			{
				ans+=2*S[i].get(low[i]-tar[i]);
			}
		}
		else if(tmp.size()==1)ans=solvesub1(tmp[0],tar,low,up,S,T,n);
		else
		{
			int lft=3-tmp[0]-tmp[1];
			ans=2*S[lft].get(low[lft]-tar[lft]);
			if(!P[lft].query(ans/2,tar[tmp[0]]-up[tmp[0]],S[lft].s[ans/2]-tar[tmp[1]]+up[tmp[1]]))ans++;
		}
		writech(base+ans,'\n');
	}
	Code by MCYYDS
}

posted @ 2026-07-20 10:32  MCYYDS  阅读(7)  评论(0)    收藏  举报