24.8.18(线段树维护字符串哈希,警钟长鸣,百度之星打铁)

星期一:

补24河南萌新 四 G                                                   牛客传送门

线段树维护字符串哈希,不错,值得一写

思路:可以想到1,2询问其实是一样的,正着反着循环节的长度都不会改变

如何快速找出循环节呢,首先如果在 l-r区间检查 len是否为循环节,一节一节查哈希值是明显不可行的,可查 [ l, r-len ]和 [ l+len,r ]的哈希值是否相等,就能大大减少查询次数

然后是如何枚举 len,如果从小到大枚举因子,复杂度为根号级别,此题中是行不通的。有一个方法是从大到小枚举 len,如果 len不行,那么 len的所有因子肯定也不是循环节,把所有因子标记上,以后枚举到时就可跳过,这样会比根号快很多

以上算是两个技巧,可以记着

代码如下:

const int N=2e5+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=998244353;
ll n;
const int ba=131;
ull hsh_tb[N][27],qp[N];
vector<int>fact[N];
void init(){
	qp[0]=1;
	for(int i=1;i<N;i++){
		qp[i]=qp[i-1]*ba;
		for(int j=1;j<=26;j++) hsh_tb[i][j]=hsh_tb[i-1][j]*ba+j;
	}                          //预处理哈希值和快速幂
	for(int i=1;i<N;i++)
		for(int j=i*2;j<N;j+=i) fact[j].push_back(i);  //预处理因子
}
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
	struct nod{
		ll l,r;
		ull hsh1;
		int tag;
		ll len;
	}t[N<<2];
	ll ql,qr,qv;
	nod merge(nod a,nod b){
		nod res;
		res.l=a.l,res.r=b.r;
		res.hsh1=a.hsh1*qp[b.len]+b.hsh1;
		res.tag=0;
		res.len=a.len+b.len;
		return res;
	}
	void pushup(int p){t[p]=merge(t[lc],t[rc]);}
	void bd(int p,int l,int r){
		t[p]={l,r,0,0,1};
		if(l==r){
			char c; cin >> c;
			t[p].hsh1=c-'a'+1;
			return ;
		}
		int mid=l+r>>1;
		bd(lc,l,mid);
		bd(rc,mid+1,r);
		pushup(p);
	}
	void pushdn(int p){
		if(!t[p].tag) return ;
		t[lc].hsh1=hsh_tb[t[lc].len][t[p].tag];
		t[lc].tag=t[p].tag;
		t[rc].hsh1=hsh_tb[t[rc].len][t[p].tag];
		t[rc].tag=t[p].tag;
		t[p].tag=0;
	}
	void update(int p){
		if(ql<=t[p].l && qr>=t[p].r){
			t[p].hsh1=hsh_tb[t[p].len][qv];
			t[p].tag=qv;
			return ;
		}
		int mid=t[p].l+t[p].r>>1;
		pushdn(p);
		if(ql<=mid) update(lc);
		if(qr>mid) update(rc);
		pushup(p);
	}
	void updt(int l,int r,char c){
		ql=l,qr=r;
		qv=c-'a'+1;
		update(1);
	}
	nod query(int p){
		if(ql<=t[p].l && qr>=t[p].r) return t[p];
		int mid=t[p].l+t[p].r>>1;
		pushdn(p);
		if(ql>mid) return query(rc);
		if(qr<=mid) return query(lc);
		return merge(query(lc),query(rc));
	}
	ull ask_hsh1(int l,int r){
		ql=l,qr=r;
		return query(1).hsh1;
	}
}tr;
bool check1(int l,int r,int len){
	return tr.ask_hsh1(l,r-len)==tr.ask_hsh1(l+len,r);
}
void solve(){
	init();
	int q; cin >> n >> q;
	tr.bd(1,1,n);
	while(q--){
		int op,l,r; cin >> op >> l >> r;
		if(!op){
			char c; cin >> c;
			tr.updt(l,r,c);
		}else if(op==1){
//			__gnu_pbds::gp_hash_table<int,int>st;  //和set用法差不多的容器
			set<int>st;
			int len=r-l+1,ans=len;
//			for(int i:fact[len]) if(check1(l,r,i)){ans=i; break;} //根号复杂度,不可接受
			for(int i=fact[len].size()-1;i>=0;i--){
				if(st.find(fact[len][i])!=st.end()) continue;
				if(check1(l,r,fact[len][i])) ans=fact[len][i];
				else for(int ff:fact[fact[len][i]]) st.insert(ff);
			}
			cout << ans << "\n";
		}else{
//			__gnu_pbds::gp_hash_table<int,int>st;
			set<int>st;
			int len=r-l+1,ans=len;
//			for(int i:fact[len]) if(check2(l,r,i)){ans=i; break;}
			for(int i=fact[len].size()-1;i>=0;i--){
				if(st.find(fact[len][i])!=st.end()) continue;
				if(check1(l,r,fact[len][i])) ans=fact[len][i];
				else for(int ff:fact[fact[len][i]]) st.insert(ff);
			}
			cout << ans << "\n";
		}
	}
}

ABC365 F的官方题解真抽象,atcoder专用头文件里封装的线段树,配上全英文题解,看一晚上加一早上看不明白,择日再战

星期二:

重做 24牛客多校1  A                                        牛客传送门

组合数好题,题解在8.4里

板刷easy数据结构                                              牛客传送门

题意:一排列,从2到n给出n-1个数,代表在 p【i】前有多少数比 p【i】小,求原排列

思路:注意数据范围,想到应该在 n^2左右。注意到最后一个数是可以最先得知的,即 a【i】+1,这题样例很好造,多造俩样例再观察下规律,就发现可以给已经填过的数加上1的权值,p【i】   不能一下得到答案,需要枚举check,若枚举到 j,查询在 p【i】后填过多少比 j小的数 (树状数组,a【i】代表在 p【i】前有多少小于等于 j的数 (经过++处理,若加起来后等于 j即满足,那么此数一定就是 j,复杂度感觉有点极限

代码如下:

ll n;
int t[N],a[N];
int ans[N];
bool vi[N];
int lowbit(int x){return x&-x;}
void add(int x){
	for(int i=x;i<=8000;i+=lowbit(i)) t[i]++;
}
int ask(int x){
	int res=0;
	for(int i=x;i;i-=lowbit(i)) res+=t[i];
	return res;
}
void solve(){
	cin >> n;
	for(int i=2;i<=n;i++){
		cin >> a[i];
		a[i]++;
	}
	for(int i=n;i>1;i--){
		for(int j=1;j<=n;j++)
			if(ask(j)+a[i]==j){ans[i]=j; break;}
		add(ans[i]);
		vi[ans[i]]=1;
	}
	for(int i=1;i<=n;i++) if(!vi[i]){ans[1]=i; break;}
	for(int i=1;i<=n;i++) cout << ans[i] << "\n";
}

晚 cf round 966 div3, 又是警钟长鸣的一场

贴 F                                                                   cf传送门

思路:贪了两次,一次wa2,一次过不了样例,转写dp

dp[ i ][ j ]表示考虑到第 i个矩形, 拿 j分的最小操作数, 很朴素的转移就 ok

贴这题的原因是dp转移时, 又忘记和自己取 min了,上次还说事不过三...没想到没多久又被拿下了,并付出了 cf上蓝的代价。。。多么痛的领悟

由此, 警钟长鸣

! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! 

代码如下:

ll n;
PII a[1010];
int b[1010][210];    //表示第 i个矩形拿 j分的最小操作数
int dp[1010][210];
ll k;
inline void op(int i,int n,int m,ll ne,ll sc){
	if(sc>k) return ;
	if(n>m) swap(n,m);
	if(ne<=0) return ;
	if(n==1 && m==1){
//		return op(n,m-1,ne-2,sc+2)+1;
		b[i][sc+2]=b[i][sc]+1;
		b[i][sc+1]=1e9;
		op(i,n,m-1,ne-2,sc+2);
	}else{
//		return op(n,m-1,ne-1,sc+1)+n;
		b[i][sc+1]=b[i][sc]+n;
		op(i,n,m-1,ne-1,sc+1);
	}
}
void solve(){
	cin >> n >> k;
	ll sum=0;
	for(int i=1;i<=n;i++){
		cin >> a[i].first >> a[i].second;
		sum+=a[i].first+a[i].second;
		if(a[i].first>a[i].second) swap(a[i].first,a[i].second);
	}
	if(sum<k){cout << "-1\n"; return ;}
	for(int i=1;i<=n;i++){
		memset(b[i],0x3f,sizeof b[i]);
		b[i][0]=0;
		op(i,a[i].first,a[i].second,a[i].first+a[i].second,0);
	}
	memset(dp,0x3f,sizeof dp);
	dp[0][0]=0;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=k+1;j++){
			for(int y=0;y<=j;y++){
				dp[i][j]=min({dp[i-1][j],dp[i-1][j-y]+b[i][y],dp[i][j]});
                //一定要和自己取min口牙 ! ! ! !

//				if(i==2 && j==4 && y==2){
//					cout << dp[i-1][j-y] << "\n";
//					cout << b[i][y] << "\n";
//					cout << dp[i-1][j] << "\n";
//					cout << dp[i][j] << "\n";
//				}
			}
		}
	}
	cout << min(dp[n][k],dp[n][k+1]) << "\n";
//	cout << dp[2][4];
}

星期三:

下午 24河南萌新 五 题目总的来说比较水

贴道暴搜博弈                                                  牛客传送门

因为题意过典,产出大量O(1)判断的规律大神,是谁我不说(

思路:第一次用 dfs写博弈,效果不错,主要是这题对于日期的处理比较繁琐

如果是A操作,那么所有策略中有一个必胜就是必胜,res取 |,如果是B那么所有策略都必须输,res取 &,再加个记忆化是好习惯

代码如下:

bool ifr(int y){                 //判断是否闰年
	y+=2000;
	return (y%400==0)||(y%4==0 && y%100);
}
int md[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
bool ifok(int y,int m,int d){       //判断日期是否合法
	if(y>24) return 0;
	if(m>12) return 0;
	int okd=md[m]; if(ifr(y) && m==2) okd--;
	if(d>okd) return 0;
	if(y==24 && m==8 && d>1) return 0;
	return 1;
}
int dp[25][13][33][2];
int nextm(int y,int m,int d){           //给出下一个月的日期
	if(ifok(y,m+1,d)) return y*10000+(m+1)*100+d;
	if(m==12 && ifok(y+1,1,d)) return (y+1)*10000+100+d;
	return 0;
}
int nextd(int y,int m,int d){           //给出下一天的日期
	int ny=y,nm=m,nd=d;
	if(ifok(ny,nm,nd+1)) return ny*10000+nm*100+nd+1;
	int ld=md[m]; if(ifr(y) && m==2) ld--;
	if(d==ld && ifok(ny,nm+1,1)) return ny*10000+(nm+1)*100+1;
	if(d==ld && m==12 && ifok(ny+1,1,1)) return (ny+1)*10000+100+1;
	return 0;
}
bool dfs(int y,int m,int d,bool ifa){
	if(y==24 && m==8 && d==1) return !ifa;
	if(dp[y][m][d][ifa]!=-1) return dp[y][m][d][ifa];
	bool res;
	if(ifa){
		res=0;
		int nm=nextm(y,m,d),nd=nextd(y,m,d);
		if(nm) res|=dfs(nm/10000,nm%10000/100,nm%100,0);
//		cout << nm/10000 << " " << nm%10000/100 << " " << nm%100 << "\n";
		if(nd) res|=dfs(nd/10000,nd%10000/100,nd%100,0);
//		cout << nd/10000 << " " << nd%10000/100 << " " << nd%100 << "\n";
	}else{
		res=1;
		int nm=nextm(y,m,d),nd=nextd(y,m,d);
		if(nm) res&=dfs(nm/10000,nm%10000/100,nm%100,1);
		if(nd) res&=dfs(nd/10000,nd%10000/100,nd%100,1);
	}
	return dp[y][m][d][ifa]=res;
}
void solve(){
	int t; cin >> t;
	memset(dp,-1,sizeof dp);
	while(t--){
		int y,m,d; cin >> y >> m >> d; y-=2000;
		if(dfs(y,m,d,1)) cout << "YES\n";
		else cout << "NO\n";
	}
}

补 C,赛时在洛谷找到写过的原题,直接粘的码。。

                                      牛客传送门                               洛谷传送门

思路:赛时写的二分检查,会wa,赛后用对拍器才找到错误原因

二分时,如果枚举 i顺着放,冲突关系没有经过排序处理,会导致前面放的很随意,以至于后面遇到无法避免的大冲突,但其实是可以避免的

正解应该是按冲突从大到小排序,优先避免大冲突,直至遇到冲突无法避免,使用并查集解决

星期四:

牛客多校10,又是签到打卡

贴道 概率签到 H                                                  牛客传送门

思路:手搓几个样例,会发现 a,b的胜利期望就是 a/(a+b),b/(a+b)

代码如下:

const int N=2e5+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=998244353;
ll n;
ll qpow(ll a,ll n){
	ll res=1;
	while(n){
		if(n&1) (res*=a)%=mod;
		(a*=a)%=mod;
		n>>=1;
	}
	return res;
}
void solve(){
	ll a,b; cin >> a >> b;
	ll qp=qpow(a+b,mod-2);
	cout << a*qp%mod << " " << b*qp%mod;
}

cf edu round169 div2,11点半交了第一发,D wa2,然后狼狈地补了前三道,难蚌

星期五:

泻药,人在北京,刚下飞机

星期六:

补 cf edu round169 div2 D                                          cf传送门

思路: 如果有相同颜色,那么可直通. 如果无相同颜色比如红蓝和黄绿,那么可以注意到,除了红蓝和黄绿两种城市外,其余所有城市都可以当这俩城市的中转点,并且只用中转一次,二分找下标即可

wa的原因是在 二分找 >x的城市下标后,如果没找到直接 continue掉了,而没有注意到后面还有个找 <x的下标的操作, 属于是粗糙了

代码如下:

const int N=2e6+10;
const int mod=1e9+7;
ll n;
int a[N];
vector<int>ve[22];
inline bool ifs(int a,int b){
	for(int i=0;i<4;i++) if((a&1<<i)==(b&1<<i)) return 1;
	return 0;
}
void solve(){
	int q; cin >> n >> q;
	for(int i=1;i<=12;i++) ve[i].clear();
	for(int i=1;i<=n;i++){
		a[i]=0;
		string s; cin >> s;
		if(s[0]=='R' || s[1]=='R') a[i]+=8;
		if(s[0]=='B' || s[1]=='B') a[i]+=4;
		if(s[0]=='G' || s[1]=='G') a[i]+=2;
		if(s[0]=='Y' || s[1]=='Y') a[i]+=1;
		ve[a[i]].push_back(i);
	}
	while(q--){
		int x,y; cin >> x >> y;
		if(x>y) swap(x,y);
		if(ifs(a[x],a[y])){cout << y-x << "\n"; continue;}
		int ans=1e9;
		for(int i=3;i<=12;i++) if(i!=a[x] && i!=a[y]){
			auto it=lower_bound(ve[i].begin(),ve[i].end(),x);
			if(it!=ve[i].end()) ans=min(abs(*it-x)+abs(*it-y),ans);
			if(it!=ve[i].begin()){             //因为continue早了没有实现这一步
				auto it2=--it;
				ans=min(abs(*it2-x)+abs(*it2-y),ans);
			}
		}
		if(ans==1e9) cout << "-1\n";
		else cout << ans << "\n";
	}
}

补 cf round 966 div3 G                                    cf传送门

思路: 二分出发时间,然后跑变种 dij, 看似 t1-t2这个条件有点刁,实则只需在 dij里讨论一下

如果 d已经>=t2, 那么就可以用 l1来更新 dis[v], 或者如果 d+l1 <= t1也能用 l1,否则只能用 l2更新,不过这里还需要讨论一下,若等到 t2再用 l1更快,即 t2+l1 < d+l2,则用 t2+l1更新dis[v]

代码如下:

const int N=2e5+10;
const int mod=1e9+7;
ll n;
struct nod{
	int l1,l2,v;
};
vector<nod>ve[N];
ll t0,t1,t2;
ll dis[N];
bool vi[N];
inline bool check(int mid){
	priority_queue<PII,vector<PII>,greater<>>pq;
//	memset(dis,0x3f,sizeof dis);
//	memset(vi,0,sizeof vi);               //注意样例数
	for(int i=1;i<=n;i++) dis[i]=1e18,vi[i]=0;
	dis[1]=mid;
	pq.push({mid,1});
	while(!pq.empty()){
		auto [d,u]=pq.top(); pq.pop();
		if(vi[u]) continue;
		vi[u]=1;
		if(u==n) break;
		for(auto [l1,l2,v]:ve[u]) if(dis[v]>d+l1){
			if(d>=t2 && dis[v]>d+l1){              //需要根据d分类讨论
				dis[v]=d+l1;
				pq.push({dis[v],v});
			}else if(d<t1){
				if(d+l1<=t1 && dis[v]>d+l1){
					dis[v]=d+l1;
					pq.push({dis[v],v});
				}else if(dis[v]>min(d+l2,t2+l1)){
					dis[v]=min(d+l2,t2+l1);
					pq.push({dis[v],v});
				}
			}else if(dis[v]>min(d+l2,t2+l1)){
				dis[v]=min(d+l2,t2+l1);
				pq.push({dis[v],v});
			}
		}
	}
	return dis[n]<=t0;
}
void solve(){
	int m; cin >> n >> m;
	for(int i=1;i<=n;i++) ve[i].clear();
	cin >> t0 >> t1 >> t2;
	for(int i=1;i<=m;i++){
		int u,v; cin >> u >> v;
		int l1,l2; cin >> l1 >> l2;
		ve[u].push_back({l1,l2,v});
		ve[v].push_back({l1,l2,u});
	}
	ll l=0,r=t0,res=-1;
	while(l<=r){
		ll mid=l+r>>1;
		if(check(mid)) res=mid,l=mid+1;
		else r=mid-1;
	}
	cout << res << "\n";
}

周日:

百度之星决赛, 硬实力不足导致打铁

补 cf round 966 div3 H                                       cf传送门

思路: 简单线段树题, 在权值线段树中找第一段空白长度>= k的数, 维护4e6加上二分+线段树也能过但能优化为维护 2e6值域且线段树上二分

代码如下:

const int N=2e6+10;
const int mod=1e9+7;
ll n;
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
	struct nod{
		int l,r;
		int len;
		int sum,tag;
		int mal,mar,ma;
	}t[N<<2];
	ll ql,qr,qv;
	nod merge(nod a,nod b){
		nod res;
		res.l=a.l,res.r=b.r;
		res.len=a.len+b.len;
		res.sum=a.sum+b.sum;
		res.mal=a.mal==a.len?a.len+b.mal:a.mal;
		res.mar=b.mar==b.len?a.mar+b.len:b.mar;
		res.ma=max({a.ma,b.ma,a.mar+b.mal});
		return res;
	}
	void pushup(int p){t[p]=merge(t[lc],t[rc]);}
	void pushdn(int p){
		if(!t[p].tag) return ;
		t[lc].sum=0;
		t[lc].mal=t[lc].mar=t[lc].ma=t[lc].len;
		t[lc].tag=1;
		t[rc].sum=0;
		t[rc].mal=t[rc].mar=t[rc].ma=t[rc].len;
		t[rc].tag=1;
		t[p].tag=0;
	}
	void bd(int p,int l,int r){
		t[p]={l,r,1,0,0,0,0};
		if(l==r){
			t[p].mal=t[p].mar=t[p].ma=1;
			return ;
		}		
		int mid=l+r>>1;
		bd(lc,l,mid);
		bd(rc,mid+1,r);
		pushup(p);
	}
	void update(int p){
		if(ql<=t[p].l && qr>=t[p].r){
			t[p].sum+=qv;
			if(t[p].sum) t[p].mal=t[p].mar=t[p].ma=0;
			else t[p].mal=t[p].mar=t[p].ma=1;
			return ;
		}
		pushdn(p);
		int mid=t[p].l+t[p].r>>1;
		if(ql<=mid) update(lc);
		if(qr>mid) update(rc);
		pushup(p);
	}
	void updt(int l,int r,int v){
		ql=l,qr=r;
		qv=v;
		update(1);
	}
	nod query(int p){
		if(ql<=t[p].l && qr>=t[p].r) return t[p];
		int mid=t[p].l+t[p].r>>1;
		pushdn(p);
		if(ql>mid) return query(rc);
		if(qr<=mid) return query(lc);
		return merge(query(lc),query(rc));
	}
	int ask(int l,int r){
		ql=l,qr=r;
		return query(1).ma;
	}
	int fnd(int p,int k){
		if(t[p].l==t[p].r) return t[p].l+k-1;
		int mid=t[p].l+t[p].r>>1;
		pushdn(p);
		if(t[lc].ma>=k) return fnd(lc,k);
		if(t[lc].mar+t[rc].mal>=k) return mid-t[lc].mar+1;    //考虑两段拼接
//		return fnd(rc,k);                 //维护值域为4e6时
		if(t[rc].ma>=k) return fnd(rc,k);
		else return t[rc].r-t[rc].mar+1;  //维护值域为2e6时
	}
}tr;
void solve(){
	tr.bd(1,1,2e6);
	int tc; cin >> tc;
	while(tc--){
		tr.t[1].sum=0;
		tr.t[1].ma=tr.t[1].mal=tr.t[1].mar=tr.t[1].len;
		tr.t[1].tag=1;                      //值域清零
		cin >> n;
		for(int i=1;i<=n;i++){
			int a; cin >> a;
			tr.updt(a,a,1);
		}
		int q; cin >> q;
		while(q--){
			char op; int x; cin >> op >> x;
			if(op=='+') tr.updt(x,x,1);
			if(op=='-') tr.updt(x,x,-1);
			if(op=='?'){
//				int l=1,r=4e6,res=0;
//				while(l<=r){
//					int mid=l+r>>1;
//					if(tr.ask(1,mid)>=x) res=mid,r=mid-1;
//					else l=mid+1;
//				}
//				cout << res-x+1 << " ";           //log^2

				cout << tr.fnd(1,x) << " ";       //log
			}
		}
		cout << "\n";
	}
}

  • 24
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值