2018年11月2日 提高组&2018.08.18【2018提高组】模拟A组

本文深入解析三道算法竞赛题目,包括字符串操作、积水计算和数位DP问题,提供了详细的解题思路与代码实现,适合算法学习者和竞赛选手参考。

前言

咕咕了快一年了呀


JZOJ 5829 string

题目

给定一个由小写字母组成的字符串 s s s。有 m m m次操作,每次操作给定3个参数 l , r , x l,r,x l,r,x。如果 x = 1 x=1 x=1,将 s [ l ] ∼ s [ r ] s[l]\sim s[r] s[l]s[r]升序排序;如果 x = 0 x=0 x=0,将 s [ l ] ∼ s [ r ] s[l]\sim s[r] s[l]s[r]降序排序。你需要求出最终序列。


分析

建线段树维护,标记永久化,叶子节点表示某位置的小写字母是啥,更改时如果区间不完全重合,要给子节点下传标记,对于每次询问,需要先求出区间各字母的次数,然后按照升序或降序依次插入,时间复杂度 O ( m l o g n ∗ 26 ) O(mlogn*26) O(mlogn26)


代码

#include <cstdio>
#include <cctype>
#include <cstring>
#define rr register
using namespace std;
int w[400001],ans[31],n,m;
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
inline void build(int k,int l,int r){
    if (l==r){w[k]=getchar()^96; return;}
    rr int mid=(l+r)>>1;
	build(k<<1,l,mid),build(k<<1|1,mid+1,r);
	if (w[k<<1]==w[k<<1|1]) w[k]=w[k<<1]; 
}
inline void query(int k,int l,int r,int x,int y){
	if (l==x&&r==y&&w[k]){ans[w[k]]+=r-l+1; return;}
	if (w[k]) w[k<<1]=w[k<<1|1]=w[k];
	rr int mid=(l+r)>>1;
	if (y<=mid) query(k<<1,l,mid,x,y);
	else if (x>mid) query(k<<1|1,mid+1,r,x,y);
	else query(k<<1,l,mid,x,mid),query(k<<1|1,mid+1,r,mid+1,y);
}
inline void update(int k,int l,int r,int x,int y,int z){
	if (l==x&&r==y){w[k]=z; return;}
	if (w[k]) w[k<<1]=w[k<<1|1]=w[k],w[k]=0;
	rr int mid=(l+r)>>1;
	if (y<=mid) update(k<<1,l,mid,x,y,z);
	else if (x>mid) update(k<<1|1,mid+1,r,x,y,z);
	else update(k<<1,l,mid,x,mid,z),update(k<<1|1,mid+1,r,mid+1,y,z);
	if (w[k<<1]==w[k<<1|1]) w[k]=w[k<<1];
}
inline void print(int k,int l,int r){
	if (w[k]){
		for (rr int i=1;i<=r-l+1;++i) putchar(w[k]^96);
		return;
	}
	rr int mid=(l+r)>>1;
	print(k<<1,l,mid),print(k<<1|1,mid+1,r); 
}
signed main(){
	freopen("string.in","r",stdin);
	freopen("string.out","w",stdout);
	n=iut(),m=iut(),getchar(),build(1,1,n);
	while (m--){
		rr int l=iut(),r=iut(),x=iut();
		memset(ans,0,sizeof(ans)); query(1,1,n,l,r);
		if (!x){
		for (rr int i=26;i;--i) if (ans[i])
		    update(1,1,n,l,l+ans[i]-1,i),l+=ans[i];
		}else for (rr int i=1;i<27;++i) if (ans[i]) 
			update(1,1,n,l,l+ans[i]-1,i),l+=ans[i];
	}
	print(1,1,n);
	return 0;
} 

JZOJ 5830 water

题目

有一块矩形土地被划分成 n ∗ m n*m nm个正方形小块。这些小块高低不平,每一小块都有自己的高度。水流可以由任意一块地流向周围四个 方向的四块地中,但是不能直接流入对角相连的小块中。 一场大雨后,由于地势高低不同,许多地方都积存了不少降水。 给定每个小块的高度,求每个小块的积水高度。 注意:假设矩形地外围无限大且高度为 0。


分析

把高度扔到小根堆,然后反向暴力,bfs四周第一个比它高的小块,那么小于它的就加上第一个比它高的小块与其高度差,时间复杂度 O ( n m l o g n m ) O(nmlognm) O(nmlognm)


代码

#include <cstdio>
#include <queue>
#define rr register
using namespace std;
struct rec{
	int x,y,h;
	bool operator <(const rec &t)const{
		return h>t.h;
	}
};
const int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0}; 
priority_queue<rec>q; bool v[303][303];
int ans[303][303],a[303][303],n,m;
inline void print(int ans){
	if (ans<0) putchar('-'),ans=-ans;
	if (ans>9) print(ans/10);
	putchar(ans%10+48);
}
inline signed iut(){
	rr int ans=0,f=1; rr char c=getchar();
	while ((c<48||c>57)&&c!='-') c=getchar();
	if (c=='-') c=getchar(),f=-f;
	while (c>47&&c<58) ans=(ans<<3)+(ans<<1)+c-48,c=getchar();
	return ans*f;
}
inline void dfs(rec t,int height){
	v[t.x][t.y]=1;
	if (t.h>=height){
		q.push(t);
		return;
	}
	ans[t.x][t.y]+=height-t.h;
	for (rr int k=0;k<4;++k)
	if (!v[t.x+dx[k]][t.y+dy[k]])
	    dfs((rec){t.x+dx[k],t.y+dy[k],a[t.x+dx[k]][t.y+dy[k]]},height);
}
inline void bfs(){
	while (q.size()){
		rec t=q.top(); q.pop();
		for (rr int k=0;k<4;++k)
		if (t.x+dx[k]&&t.x+dx[k]<=n&&t.y+dy[k]&&t.y+dy[k]<=m&&!v[t.x+dx[k]][t.y+dy[k]])
			dfs((rec){t.x+dx[k],t.y+dy[k],a[t.x+dx[k]][t.y+dy[k]]},a[t.x][t.y]);
	}
}
signed main(){
	freopen("water.in","r",stdin);
	freopen("water.out","w",stdout);
	n=iut(); m=iut();
	for (rr int i=1;i<=n;++i)
	for (rr int j=1;j<=m;++j){
		a[i][j]=iut();
		if (a[i][j]<0) ans[i][j]=-a[i][j],a[i][j]=0;
	}
	for (rr int i=1;i<=n;++i){
		v[i][1]=1; v[i][m]=1;
		q.push((rec){i,1,a[i][1]});
		q.push((rec){i,m,a[i][m]});
	}
	for (rr int i=2;i<m;++i){
		v[1][i]=1; v[n][i]=1;
		q.push((rec){1,i,a[1][i]});
		q.push((rec){n,i,a[n][i]});
	}
	bfs();
	for (rr int i=1;i<=n;++i)
	for (rr int j=1;j<=m;++j) print(ans[i][j]),putchar(j==m?10:32);
	return 0;
} 

JZOJ 5831 number

题目

给定正整数 n , m n,m n,m,问有多少个正整数满足: (1)不含前导 0; (2)是 m m m的倍数; (3)可以通过重排列各个数位得到 n n n


分析

这是一道状压数位dp,设 d p [ s ] [ i ] dp[s][i] dp[s][i]表示当前状态为 s s s,模 m m m i i i时的总数,然而怎样表示状态呢,如果用二进制肯定会爆炸的呀,可以采用变进制状压dp,把0−9每一种数字各用了几次压成一个状态,据证实,最大状态为 60000 60000 60000,那么时间复杂度显然


代码

#include <cstdio>
#include <cctype>
#include <cstring>
#define rr register
using namespace std;
const int mod=998244353;
int minx[10],all[10],cnt[10],w[10],dp[65536][100],n;
inline void iut(){
	rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ++cnt[c^48],c=getchar();
}
inline signed mo(int x,int y){return x+y>=mod?x+y-mod:x+y;}
signed main(){
	freopen("number.in","r",stdin);
	freopen("number.out","w",stdout);
	iut(); scanf("%d",&n);
	minx[0]=1,all[0]=cnt[0];
	for (rr int i=1;i<10;++i) minx[i]=all[i-1]+1,all[i]=all[i-1]+minx[i]*cnt[i];
	for (rr int i=1;i<10;++i) if (cnt[i]) dp[minx[i]][i%n]=1;
	for (rr int i=1;i<all[9];++i){
		rr int t=i;
		for (rr int j=9;~j;--j) w[j]=cnt[j]-t/minx[j],t%=minx[j];
		for (rr int j=0;j<10;++j) if (w[j])
		    for (rr int k=0;k<n;++k)
		        dp[i+minx[j]][(k*10+j)%n]=mo(dp[i+minx[j]][(k*10+j)%n],dp[i][k]); 
	}
	return !printf("%d",dp[all[9]][0]);
} 
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值