CF2125游记

前言

第一次在 \(CodeForces\) 上参加比赛。这次看错时间,晚了 \(15\) 分钟进入比赛,只做出 \(T1-T4\)

CF2125 难度 \(Div.2\)

A.Difficult Contest

题面

  • 称一个字符串是好的,当且仅当其没有子串 \(FFT\)\(NTT\)

\(t\) 组数据,每组数据:
给定 \(1\) 个字符串,需要交换其中任意个字符,使得其为好的字符串。

思路

可以发现,非好的子串有个共同点:字符 \(T\) 在后面。
那么我们只需要把 \(T\) 全部移到字符串前面即可。

实现

#include<iostream>
using namespace std;
int n,p;
string s;
int main(){
	cin>>n;
	while(n--){
		cin>>s;
		p=0;
		for(int i=0;i<s.length();++i){
			if(s[i]=='T'){
				swap(s[i],s[p]);
				++p;
			}
		}
		cout<<s<<endl;
	}
}

B.Left and Down

题面

\(t\) 组数据,每组数据:
给定三个数 \(m,n,k\):
你在点 \((n,m)\)
每一步可以从 \((x,y)\) ,走到 \((x-dx,y-dy)\) ,其中 \(dx,dy\in[1,k]\)
其代价为:

  • \(1\) ,如果你没选过 \((x,y)\)
  • \(0\) ,如果你选过 \((x,y)\)

求走到点 \((0,0)\) 的代价。

思路

首先我们发现若 \(n=m\) ,则代价为 \(1\) ,只需要选 \(n\)\((1,1)\)
那么不难想到若 \(n\ne m\),我们只需要走到 \((n,n)\)\((m,m)\) ,不难发现,代价至多为 \(2\)
那我们只需要找到在何种情况下,代价为 \(1\) 即可。
那么显然,只要 \((n,m)\)\(d\) 倍的 \((x,y)\in[1,k]^2\) 代价就为 \(1\)
同除 \(gcd\) 即可。

实现

#include<iostream>
using namespace std;
#define ll long long
int n;
ll a,b,k,d,ans;
ll gcd(ll x,ll y){
	if(x==y)return x;
	if(x==1||y==1)return 1;
	if(x<y)x^=y^=x^=y;
	if(x%y==0)return y;
	return gcd(y,x%y);
}
int main(){
	cin>>n;
	while(n--){
		cin>>a>>b>>k;
		if(a==b){cout<<"1\n";continue;}
		d=gcd(a,b);
		a=max(a,b);
		a/=d;
		if(a<=k)cout<<"1\n";
		else cout<<"2\n";
	}
	return 0;
}

其实我没特判 \(n=m\) 速度还是一样。

C.Count Good Numbers

题面

  • 如果一个数素因数分解后,其所有素因数均至少 \(2\) 位,则称其为好的数。
    \(t\) 组数据,每组数据:
    给定 \(2\) 个数 \(l,r\)
    需要求出 \(l,r\)好的数的个数

思路

好的数的定义翻译一下:

  • 不含因数 \(2,3,5,7\) 的数即为好的数。
    那么我们很容易得出 \([1,i]\) 中好的数的个数 \(f_i\) ,容斥原理即可。
    那么答案就是 \(f_r-f_{l-1}\)

实现

#include<iostream>
using namespace std;
#define ll long long
int n;
ll l,r;
ll get(ll x){
	ll ret=x-1;
	ret-=x/2;ret-=x/3;ret-=x/5;ret-=x/7;
	ret+=x/6;ret+=x/10;ret+=x/14;ret+=x/15;ret+=x/21;ret+=x/35;
	ret-=x/30;ret-=x/42;ret-=x/70;ret-=x/105;
	ret+=x/210;
	return ret;
}
int main(){
	cin>>n;
	while(n--){
		cin>>l>>r;
		cout<<get(r)-get(l-1)<<endl;
	}
    return 0;
}

D.Segments Covering

题面

有一个条带,长度为 \(m\) ,上面可能有 \(n\) 条线段,覆盖 \(l-r\) 的格子,且有 \(p_i=\frac{a}{b}\) 的概率存在。
求这个条带的每一格均正好被覆盖一次的概率。

思路

很显然的 \(Dp\)
先将线段按左端点排序
\(f_i\) 为恰好覆盖了 \([1,i]\) 的概率。
那么有 \(\displaystyle f_r=\sum_{i:r_i=r}\left(f_{l_i}\times\prod_{j:r\le r_j\le r_i\wedge i\ne j} (1-p_j)\times p_i\right)\)
那么最重要的就是中间这一坨怎么求。
观察到中间这一坨只和右端点有关系,我们直接前缀积 \((1-p_i)\) 来求。

实现

#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
const int N=2e5+5;
const ll mod=998244353;
struct node1{//左端点排序
	int l,r;ll v;
	bool operator < (const node1 t)const{
		return (l^t.l)?l<t.l:r<t.r;
	}
}a[N];
struct node2{//右端点排序
	int l,r;ll v;
	node2(){}
	node2(int r):r(r){}
	bool operator < (const node2 t)const{
		return (r^t.r)?r<t.r:l<t.l;
	}
}b[N];
int n,m;
ll p[N];
ll qpow(ll a,ll b){
	ll ret=1;
	a%=mod;
	while(b){
		if(b&1)ret=ret*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ret;
}
ll mul(int l,int r){
	ll ret=b[lower_bound(b+1,b+1+n,node2(r+1))-b-1].v;
	ret*=qpow(b[lower_bound(b+1,b+1+n,node2(l))-b-1].v,mod-2);
	ret%=mod;
	return ret;
}
int main(){ll x,y;
	cin>>n>>m;
	b[0].v=1;
	for(int i=1;i<=n;++i){
		cin>>a[i].l>>a[i].r>>x>>y;
		a[i].v=x*qpow(y,mod-2)%mod;
		b[i].l=a[i].l;
		b[i].r=a[i].r;
		b[i].v=1+mod-a[i].v;
	}
	sort(a+1,a+1+n);
	sort(b+1,b+1+n);
	for(int i=1;i<=n;++i){
		b[i].v*=b[i-1].v;
		b[i].v%=mod;
	}
	p[0]=1;
	for(int i=1,j=1;i<=m;++i){
		while(a[j].l==i){
			p[a[j].r]+=p[i-1]*mul(i,a[j].r)%mod*a[j].v%mod*qpow(1+mod-a[j].v,mod-2)%mod;
			p[a[j].r]%=mod;
			++j;
		}
	}
	cout<<p[m]<<endl;
	return 0;
}

E.Sets of Complementary Sums

题面

对于一个集合 \(Q\) ,如果其可以通过以下规则生成,则称其为互补和集

  • 选择一个由 \(m\) 个正整数生成的数组 \(a\)
  • \(Q={s-a_i}\) ,其中 \(s=\sum a_i\)

\(t\) 组数据,每组数据:
给定两个数 \(n,x\) ,你需要计算出恰好包含 \(n\) 个元素,每个元素都属于 \(1-x\)互补和集 \(U\) 的数量。

思路

初步分析-一一对应

不妨假设 \(a\) 单调不降。
显然计数 \(U\) 很难,我们考虑计数对应的 \(a\)
但是单纯计数 \(a\) 会重,所以我们需要找到一个 \(U\)\(a\) 的一一对应方式。
首先,可以发现:

  1. \(U\) 元素个数为 \(a\) 中不同元素个数;
  2. \(U\) 可以由若干个 \(1\) 与若干个不同的数字组成的数组生成;

证明
a. 若 \(a_1=1\) ,则对于任意 \(i\ne j,a_i=a_j\) ,将其中一个替换成 \(a_i\)\(1\)
b. 若 \(a_1\ne 1\) ,则把所有 \(a_i\) 都减去 \(a_1-1\) ,并加上 \((m-1)(a_1-1)\)\(1\)

于是我们将每一个 \(U\) 与一个对应的 \(a\) 一一对应,只需想办法计数对应的 \(a\) 即可。
3. \(\frac{m(m+1)}{2}-1\le x\)

证明
由性质2.最小的 \(a\)\([1,2,...,m]\) ,因此 \(s=\frac{m(m+1)}{2}\) ,于是 \(s-a_i\le s-a_1=s-1\le x\)
\(n\le m\) 于是 \(\frac{n(n+1)}{2}-1\le x\)

\(O(n)=O(\sqrt{x})\)

更进一步-计数DP

然后就是考虑怎么解决。
首先,我们可以不管前面的 \(1\) ,最后再加上。
\(f_{i,j}\) 表示有 \(i\) 个数组,和为 \(j\) 的满足上述性质的 \(a\) 的个数。
\(\displaystyle ans=\sum^{x+1}_{i=1}(x-i+2)\times f_{n,i}\)
考虑转移:
如果枚举第 \(i\) 位的值,难以保证 \(a\) 的递增,那么考虑枚举差分。
有: \(\displaystyle f_{i,j}=\sum_{k=1}f_{i-1,j-k(n-i+1)}\)

实现

#include<iostream>
using namespace std;
#define ll long long
const int N=2e5+5;
const ll mod=998244353;
int t;
ll n,x,f[2][N];
int main(){
	cin>>t;
	while(t--){
		cin>>n>>x;
		if(n==1)cout<<x<<endl;
		else if(x+1<n*(n+1)/2)cout<<"0\n";
		else{
			ll lim=x-n+1,ans=0;
			for(int i=1;i<=lim;++i)f[0][i]=0;
			f[0][0]=1;
			for(int i=1;i<n;++i){
				for(int j=0;j<=lim;++j)f[i&1][j]=0;
				for(int j=i;j<=lim;++j)f[i&1][j]=(f[i&1][j-i]+f[i-1&1][j-i])%mod;
			}
			for(int s=n-1;s<=lim;++s)ans=(ans+(x-s-n+2)*f[n-1&1][s])%mod;
			cout<<ans<<endl;
		}
	}
	return 0;
}

F.Timofey and Docker

题面

给定一个字符串 \(s\) ,以及 \(n\) 个限制 \((l_i,r_i)\)
对于一个限制 \(i\) ,若字符串 \(t\) 中的子串 \(docker\) 数量 \(x\in[l_i,r_i]\) 则称 \(t\) 满足满足该限制。
现在你需要修改字符串 \(s\) 中的若干个字符,使得 \(s\) 满足最多的限制。
求最小修改次数 \(v\)

思路

初步分析-朴素做法

首先,我们对于字符串的操作分为 \(3\) 种:

  1. 添加,将没有 \(docker\) 前后缀的子串改为 \(docker\)
  2. 补全,将有 \(docker\) 前后缀的子串补全为 \(docker\)
  3. 删除,将完整的 \(docker\) 删除其中若干字符。
    根据简单的贪心:
    我们在增加 \(docker\) 个数的时候,肯定不会执行删除操作,
    我们在减少 \(docker\) 个数的时候,肯定不会执行补全添加操作。
    \(1.\)\(2.\) 可以简化为1个操作:
    寻找一个位置 \(i\) ,将其前面的 \(6\) 个字符修改为 \(docker\)
    \(c_i\) 代表将 \(s_{i-5}-s_{i}\) 的字符串修改为 \(docker\) 的代价,可以 \(O(n)\) 预处理之。
    可以得出朴素的 \(Dp\)
    \(f_{i,j}\) 代表前 \(i\) 位恰好有 \(j\)\(docker\) 子串的所需最小修改数。
    转移: \(f_{i,j}=min{f_{i-1,j},f_{i-6,j-1}+c_i}\)
    但这是 \(O(n^2)\) 的,太慢了。

步步为营-DP优化

首先,我们先对 \(l_i,r_i\) 下手:
显然,字符串 \(s\) ,中至多有 \(\left\lfloor\frac{|s|}{6}\right\rfloor\)\(docker\)
那么我们将 \(l_i\) 中大于其的去掉, \(r_i\) 中等于其的修改为其,通过差分可以得出满足最多限制的 \(docker\) 的个数 \(k\) (们)。
\(p\)\(s\) 中的 \(docker\) 的数量。
那么接下来需要确定 \(j\) 的上限。
我们不难发现:最优的 \(k\) 一定是 \(p\) 两侧的 \(k\)

先考虑小于 \(p\)\(k\) 们:
\(k_1=k_2-1\)\(k_1\) 一定是由 \(k_2\) 中的某一个 \(docker\) 替换掉一个字母得来的那么 \(v_1=v_2-1\)
于是其中最大的 \(k\) 一定是最优的。

再考虑大于 \(p\)\(k\) 们:
\(k_1>k_2>p\) ,设 \(k_1-k_2=t\)\(v_2\le v_1-t\)
于是其中最小的 \(k\) 一定是最优的。

于是我们找到了 DP 中 \(j\) 的上限。

拨云见日-最终算法

如果没有 \(j\) 的维度,那么这就是一维 DP ,非常好处理。
那么我们脑海中就会蹦出来一个算法: WQS二分

实现

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int N=5e5+5;
string s;
vector<int>vec;
int cnt[N],c[N],n,k,ans;
double f[N];
double calc(double mid){
	for(int i=6;i<=n;++i)f[i]=min(f[i-1],f[i-6]+c[i]-mid);
	return f[n]+k*mid;
}
int main(){int m,mx,p;double lm,rm;
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int t;
	cin>>t;
	while(t--){
		cin>>s;
		n=s.length();
		s=" "+s;
		cin>>m;
		mx=-1;
		p=0;
		ans=k=1e9;
		for(int i=1;i<=n;++i)cnt[i]=0;
		for(int i=1;i<=m;++i){
			int l,r;
			cin>>l>>r;
			if(l>n/6)continue;
			r=min(r,n/6);
			++cnt[l];--cnt[r+1];
		}
		for(int i=1;i<=n/6;++i)cnt[i]+=cnt[i-1];
		for(int i=0;i<=n/6;++i){
			if(cnt[i]>mx){
				mx=cnt[i];
				vec.clear();
				vec.push_back(i);
			}
			else if(cnt[i]==mx){
				vec.push_back(i);
			}
		}
		for(int i=1;i+5<=n;++i)if(s.substr(i,6)=="docker")++p;
		for(int i=6;i<=n;++i)c[i]=(s[i-5]!='d')+(s[i-4]!='o')+(s[i-3]!='c')+(s[i-2]!='k')+(s[i-1]!='e')+(s[i]!='r');
		for(auto v:vec)if(v<=p)ans=min(ans,p-v);
		for(auto v:vec)if(v>p)k=min(k,v);
		if(k==1e9)cout<<ans<<endl;
		else{
			double l,r;// 二分的是斜率,所以需要使用double
			l=-n-10,r=n+10;int tot=80;
			while(tot--){
				lm=(l+l+r)/3;rm=(l+r+r)/3;
				if(calc(lm)<calc(rm))l=lm;
				else r=rm;
			}
			cout<<min(int(calc(l)+0.1),ans)<<endl;
		}
	}
	return 0;
}

另注

CF 上的题解给的是四边形不等式优化。

posted @ 2025-08-07 17:14  Xie2Yue  阅读(12)  评论(0)    收藏  举报