CF/AT做题记录

我发现三级标题有点小。

CF1905C

考虑先找到原串中的字典序最大串,这个直接单调栈求出。然后我们对这个字串 t 执行操作,t 是不上升的所以肯定能排好序,我们在找 t 的时候顺便记录下 t 在原串中的位置,然后把排好序的 t 放回去判断,如果是不下降的则输出排好 t 所需的次数,正确性手玩得到。

CF1905D

先求出原排列的前缀 mex 然后考虑循环移位后产生的影响以及怎么维护这个序列。手模,向左移位时将所有大于 ai 的前缀 mex 变成 ai,注意最后一个 mex 一定是 n,操作过程珂以用一个 deque 维护,但是直接维护是 O(n2) 的,观察到在移位过程中会有很多连续相同的 mex,考虑记录下每个 mex 的个数把他们缩在一起,具体而言在 deque 里存一个二元组 (x,y) 表示有 yx,每次弹出掉队首,对于队尾 >ai 的元素暴力全部弹出,并插入一个 (ai,x) 表示 xai,再插入一个 n,复杂度均摊 O(n)。注意实现细节。

点击查看代码
for(int i=1;i<=n;++i){
		xx x=(xx){a[i],0};
		sum-=q.front().val,q.front().cnt--;
		if(!q.front().cnt) q.pop_front();
		while(!q.empty()&&q.back().val>=a[i]){
			sum-=q.back().val*q.back().cnt;
			x.cnt+=q.back().cnt;
			q.pop_back();
		}
		q.push_back(x);
		q.push_back((xx){n,1});
		sum+=x.val*x.cnt+n;
		ans=max(ans,sum);
	}

CF1913D

VP做不出来,有一个奇怪的 O(n) 的模拟+组合数学想法,不会实现,结果是dp。看到求方案数并且元素之间的答案好像能转移,考虑dp,套路地设 fi 表示只看前 i 个数并保留 ai 时的方案数。考虑找到 i 前面第一个小于 ai 的数,要满足 ji 的所有元素都大于 aiaj,即区间 [j,i] 的最小值是 aiajfi=fj+sumsum 表示 [j,i1] 的区间和。直接枚举找 jO(n2),考虑寻找 j 的一步用单调栈优化,并且记录前缀和,复杂度 O(n)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=3*114514,M=1919810,mod=998244353,inf=1145141919810;
ll T;
ll n,a[N],dp[N],sum[N],s[N];
//前i个不删i时的方案数 
void solve(){
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i],dp[i]=sum[i]=s[i]=0;
	dp[0]=1,sum[1]=1; a[0]=inf;
	ll t=0,minn=inf,res=0,ans=0;
	for(int i=1;i<=n;++i){
		while(t&&a[s[t]]>a[i]) res=(res-dp[s[t]]+mod)%mod,--t;
		dp[i]=(sum[i-1]-sum[s[t]]+mod)%mod;
		if(!t) ++dp[i],dp[i]%=mod;
		dp[i]+=res,dp[i]%=mod;
		res+=dp[i],res%=mod;
		s[++t]=i; sum[i]=(sum[i-1]+dp[i])%mod;
	}
	for(int i=n;i>=1;--i)
		if(a[i]<minn)
			minn=a[i],ans=(ans+dp[i])%mod;
	cout<<ans<<'\n';
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>T;
	while(T--) solve();
	return 0;
} 

CF1909B

这么简单做法都想到了还没做出来,烦。你手玩一下就能发现答案只会是 2 的幂次,直接模拟再用个 set 就没了,一开始还在想什么奇偶性。

CF1909C

一个简单的贪心思路,给我点时间也想得出来。先对 l 从大到小排序,然后把 r 放到 set 里,依次对每个 l 找到大于等于它的第一个 r,配起来放进 vector 里然后从 set 里删掉那个 r,再把得到的值从小到大排序,c 从大到小排,就做完了。

CF1909D

ABC128D

挺简单一个题,但是脑子抽了。你直接枚举从左右开始能取到的地方,然后把取到的数排个序,在剩余次数内尽量把负数减掉就没了。从右向左的循环别写成 ++i

ABC130E

dp,初始把所有空串赋为 1,然后每个 dpi,j 都能由 dpi1,j,dpi,j1 转移来,注意重复算了一个 dpi1,j1 要减掉。然后如果 ai=bj 那么前面的每一个子序列都能再加一,也就是总共再加一个 dpi1,j1

ABC132D

组合数,把 k 个球分成 i 段就是个插板法,方案数 C(i1k1),然后将这 i 段插入到长度 nk 的序列中,有 nk+1 个位置,方案数为 C(ink+1),注意取模乘逆元。

ABC141E

字符串,直接枚举 O(n3),考虑二分答案优化,我们记录每个区间长度对应的 base 并且做一个哈希值的前缀和,求哈希值就 a[r]-a[l-1]*base[r-l+1],其中 l 是区间左端点 r 是长度,还有哈希都用双哈希,不写要遭。

CF1918D

二分答案是能想到的,check的单调队列维护dp就不会了。我们设 dpi 为以 i 为一段数的结尾时的总值。
他妈的不想写了,下次还填不简单。

ABC339G

强制在线,能想到用树套树或分块解决,但我不会树套树,于是考虑分块。散块直接暴力枚举一遍就完了。对于整块,我们考虑先分别对每个块内的元素排序,在每个块中记录前缀和,这样查询时对每个整块就可以先二分找到 x 在排序后的块所在的位置,然后就可以直接加前缀和了,时间复杂度 O(nnlogn),在 3.50s 时限下调下块长就能过了。

ABC341E

有时候区间的特定形态是可以用线段树维护的。对于每个区间考虑维护左端点值、右端点值、区间是否合法的标志 fl 以及取反懒标记 tag。build 时把所有长度为一的区间标为合法,然后 pushup 时判断左儿子的右端点 和 右儿子的左端点是否不同,还要满足左右儿子区间都合法这个父亲区间才合法。修改的话就按套路对 tag,lv,rv 取个反就行了。查询的话我们考虑用一个结构体记录合并到的区间的信息,包括左右端点值和是否合法的标记。然后在递归过程中一直判断就行了,看下代码吧。
这个题挺简单的,但赛时没过,显示出一些问题吧…… 首先想到一个做法一定不能草率地就直接开始写,要一步一步往下把每一步具体该怎么实现想出来,如果实现不了就假了,以及一定要想清楚正确性。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mk make_pair
#define ls now<<1
#define rs now<<1|1
const ll N=5*114514,M=1919810;
ll n,m,a[N];
struct tree{
	ll l,r,fl,len;
	ll lv,rv,tag;
}t[4*N];
void pushup(ll now){
	t[now].fl=(t[ls].rv!=t[rs].lv)&&(t[ls].fl&&t[rs].fl);
	t[now].lv=t[ls].lv,t[now].rv=t[rs].rv;
}
void pushdown(ll now){
	if(!t[now].tag) return;
	t[ls].tag^=1,t[rs].tag^=1;
	t[ls].lv^=1,t[ls].rv^=1;
	t[rs].lv^=1,t[rs].rv^=1;
	t[now].tag=0;
	return;
}
void build(ll now,ll l,ll r){
	t[now].l=l,t[now].r=r,t[now].len=r-l+1;
	t[now].lv=a[l],t[now].rv=a[r];
	if(l==r){
		t[now].fl=1;
		return;
	}
	ll mid=l+r>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	pushup(now);
}
void update(ll now,ll x,ll y){
	if(t[now].l>=x&&t[now].r<=y){
		t[now].lv^=1,t[now].rv^=1;
		t[now].tag^=1;
		return;
	}
	pushdown(now);
	ll mid=t[now].l+t[now].r>>1;
	if(x<=mid) update(ls,x,y);
	if(y>mid) update(rs,x,y);
	pushup(now);
}
struct xx{
	ll lv,rv,fl;
};
xx query(ll now,ll x,ll y){
	if(t[now].l>=x&&t[now].r<=y) return (xx){t[now].lv,t[now].rv,t[now].fl};
	ll mid=t[now].l+t[now].r>>1;
	xx ans=(xx){-1,-1,-1},u;
	pushdown(now);
	if(x<=mid) ans=query(ls,x,y);
	if(y>mid){
		u=query(rs,x,y);
		if(x<=mid) ans.fl=(ans.rv!=u.lv)&&(ans.fl&&u.fl),ans.rv=u.rv;
		else ans=u;
	}
	pushup(now);
	return ans;
}
void debug(ll now){
	cout<<"QWQ: "<<t[now].l<<" "<<t[now].r<<" "<<t[now].lv<<" "<<t[now].rv<<" "<<t[now].fl<<'\n';
	if(t[ls].len) debug(ls);
	if(t[rs].len) debug(rs);
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;++i){
		char ch; cin>>ch;
		a[i]=int(ch-'0');
	}
	build(1,1,n);
	for(int i=1;i<=m;++i){
		ll opt,l,r;
		cin>>opt>>l>>r;
		if(opt==1) update(1,l,r);
		else{
			xx sum=query(1,l,r);
			if(sum.fl) cout<<"Yes\n";
			else cout<<"No\n";
		}
		//debug(1);
	}
	return 0;
}

ARC058D

首先要知道:从 (x,y)(x2,y2),只能向下/右走,根据 Schroder 数,可得路径数为 C(x2xx2x+y2y)

把要走的区域划分为两个矩形,然后把他们相邻的点的方案数乘起来求和就行了,画个图就直接看出来了,把题解的放一下:

注意组合数要判断方案数为 1 的情况。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=3*114514,M=1919810,mod=1e9+7;
ll n,m,a,b;
ll p[N],q[N];
ll qpow(ll a,ll b){
	ll ans=1;
	while(b){
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}
ll C(ll n,ll m){
	if(n==m||m==0) return 1;
	return p[n]*q[m]%mod*q[n-m]%mod;
}
ll calc(ll x,ll y,ll n,ll m){
	return C(n-x+m-y,n-x);
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m>>a>>b;
	p[1]=1;
	for(int i=2;i<=2e5;++i) p[i]=p[i-1]*i%mod;
	for(int i=1;i<=2e5;++i) q[i]=qpow(p[i],mod-2);
	ll ans=0;
	for(int i=1;i<=n-a;++i){
		ll x=calc(1,1,i,b),y=calc(i,b+1,n,m);
		ans=(ans+x*y%mod)%mod;
	}
	cout<<ans;
	return 0;
}//数据水了 

ARC060E

就直接正着反着倍增跳就行了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll int
const ll N=114514,M=1919810;
ll n,a[N],L,Q,lg[N];
ll pos[N],sop[N];
ll f[N][24],g[N][24];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i];
	cin>>L>>Q;
	for(int i=1;i<=n;++i) pos[i]=upper_bound(a+1,a+n+1,a[i]+L)-a-1;
	for(int i=n;i>=1;--i){
		ll l=1,r=i-1,res=0;
		while(l<=r){
			ll mid=(l+r)>>1;
			if(a[i]-a[mid]<=L) res=mid,r=mid-1;
			else l=mid+1;
		}
		sop[i]=res;
	}
	for(int i=n-1;i>=1;--i){
		f[i][0]=pos[i];
		for(int j=1;j<=18;++j){
			f[i][j]=f[f[i][j-1]][j-1];
			if(!f[i][j]) break;
		}
	}
	for(int i=2;i<=n;++i){
		g[i][0]=sop[i];
		for(int j=1;j<=18;++j){
			g[i][j]=g[g[i][j-1]][j-1];
			if(!g[i][j]) break;
		}
	}
	while(Q--){
		ll x,y;
		cin>>x>>y;
		ll u,ans=0;
		if(x<y){
			u=x;
			for(int i=18;i>=0;--i){
				if(f[u][i]>y||!f[u][i]) continue;
				u=f[u][i],ans+=pow(2,i);
				if(u==y) break;
			}
			if(u<y) ++ans;
			cout<<ans<<'\n';
		}
		else{
			u=x;
			for(int i=18;i>=0;--i){
				if(g[u][i]<y||!g[u][i]) continue;
				u=g[u][i],ans+=pow(2,i);
				if(u==y) break;
			}
			if(u>y) ++ans;
			cout<<ans<<'\n';
		}
	}
	return 0;
} 

ARC059E

dp[i][j] 表示 dp 到第 i 个人,总共已经发了 j 个糖时的总和。转移的话枚举一个 k,表示第 i 个人分到了 k 个糖,于是 dp[i1] 的答案都要乘上一个 x=AiBixk,转移式就是:dp[i][j]=k=0j(dp[i1][jk]×x=AiBixk),珂以用前缀和预处理幂次,预处理 O(n2),转移 O(n3)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=514,M=1919810,mod=1e9+7;
ll n,c,a[N],b[N],ans;
ll po[N][N],sum[N][N],dp[N][N];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>c;
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<=n;++i) cin>>b[i];
	//预处理幂次
	for(int i=1;i<=400;++i){
		po[i][0]=1;
		sum[i][0]=i;
		for(int j=1;j<=400;++j)
			po[i][j]=po[i][j-1]*i%mod,
			sum[i][j]=(sum[i-1][j]+po[i][j])%mod;
	}
	dp[0][0]=1;
	for(int i=1;i<=n;++i)
		for(int j=0;j<=c;++j)
			for(int k=0;k<=c-j;++k)
				dp[i][j+k]=(dp[i][j+k]+dp[i-1][k]*(sum[b[i]][j]-sum[a[i]-1][j]+mod)%mod)%mod;
	cout<<dp[n][c];
	return 0;
} 

ARC063E

两个做法,dp和贪心。
其实也不能严格算dp:先一遍dfs求出每个点的点权取值范围,然后再一遍dfs判断每两个点之间是否有交集,没有的话就不合法。
贪心:每次取点权最小的点然后对它周围没有点权的点赋值为 val[u]+1,用一个优先队列维护,复杂度 O(nlogn)。正确性感性理解一下是对的,严谨证明:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mk make_pair
const ll N=114514,M=1919810,inf=1e18;
struct xx{
	ll next,to;
}e[2*N];
ll head[2*N],cnt;
void add(ll x,ll y){
	e[++cnt].next=head[x];
	e[cnt].to=y;
	head[x]=cnt;
}
bool fl,vis[N];
ll n,k,a[N],rt,dept[N];
typedef pair<ll,ll> pi;
priority_queue<pi,vector<pi>,greater<pi> > q;
void dfs(ll u,ll fa){
	if(fl) return;
	for(int i=head[u];i;i=e[i].next){
		ll v=e[i].to;
		if(v==fa) continue;
		if(abs(a[u]-a[v])!=1){
			fl=1;
			return;
		}
		dfs(v,u);
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n;
	for(int i=1;i<n;++i){
		ll a,b;
		cin>>a>>b;
		add(a,b),add(b,a);
	}
	cin>>k;
	for(int i=1;i<=k;++i){
		ll v; cin>>v;
		cin>>a[v];
		if(!rt) rt=v;
		q.push(mk(a[v],v));
	}
	while(!q.empty()){
		ll u=q.top().second; q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=head[u];i;i=e[i].next){
			ll v=e[i].to;
			if(a[v]||vis[v]) continue;
			a[v]=a[u]+1,q.push(mk(a[v],v));
		}
	}
	dfs(rt,0);
	if(fl) cout<<"No";
	else{
		cout<<"Yes\n";
		for(int i=1;i<=n;++i) cout<<a[i]<<'\n';
	}
	return 0;
}

ABC048D

顶多算个黄。简单手玩能发现只要中间有至少一个初始珂以删除的字符,那么就一定能把原串删到长度为 3 的时候,如果首尾相同就还能删一次,否则不能,然后总共能删奇数次就先手胜,否则后手胜。

ABC049D

开两个并查集,然后用一个二维map,直接对每个点加 m[find1(i)][find2(i)]++ 就好了,查询也就直接查。

ARC064E

顶多算个绿。珂以把起点和终点看作两个半径为零的圆,每对圆之间受到宇宙射线照射的路径长就是圆心距离减去半径和。n1000,我们就可以建一个完全图,把要受射线照射的路径长当作边权然后直接跑最短路就行了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define db long double
#define ll int
#define mk make_pair
const ll N=1145140,M=1919810;
const db inf=1145141919810.0;
struct xx{
	ll next,to;
	db val;
}e[2*N];
ll head[2*N],cnt;
void add(ll x,ll y,db z){
	e[++cnt].next=head[x];
	e[cnt].to=y;
	e[cnt].val=z;
	head[x]=cnt;
}
ll s,t,n;
db xs,ys,xt,yt,x[N],y[N],r[N];
db calc(ll i,ll j){
	return sqrt((x[i]-x[j])*(x[i]-x[j])*1.0+(y[i]-y[j])*(y[i]-y[j])*1.0)-r[i]-r[j];
}
db dis[N];
bool vis[N];
typedef pair<db,ll> pi;
priority_queue<pi,vector<pi>,greater<pi> > q;
void dij(){
	for(int i=0;i<=n+1;++i) dis[i]=inf,vis[i]=0;
	dis[s]=0,q.push(mk(dis[s],s));
	while(!q.empty()){
		ll u=q.top().second; q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=head[u];i;i=e[i].next){
			ll v=e[i].to;
			db w=e[i].val;
			if(dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				q.push(mk(dis[v],v));
			}
		}
	}
}
int main(){
	//ios::sync_with_stdio(0);
	//cin.tie(0); cout.tie(0);
	cin>>xs>>ys>>xt>>yt>>n;
	s=0,t=n+1;
	for(int i=1;i<=n;++i) cin>>x[i]>>y[i]>>r[i];
	x[0]=xs,y[0]=ys,r[0]=0;
	x[n+1]=xt,y[n+1]=yt,r[n+1]=0;
	for(int i=0;i<=n+1;++i)
		for(int j=i+1;j<=n+1;++j){
			db w=calc(i,j);
			w=max(w,(db)(0));
			add(i,j,w),add(j,i,w);
		}
	dij();
	printf("%0.10Lf",dis[n+1]);
	return 0;
}
posted @   和蜀玩  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】

阅读目录(Content)

此页目录为空

点击右上角即可分享
微信分享提示