牛客练习赛91 C D

  1. 魔法学院(hard version) 并查集

大意:给定一个字符串,有 m 个魔法,每个魔法可以将区间 [ l i , r i ] [l_i,r_i] [li,ri]的一个字符修改成 c i c_i ci,每个魔法可以使用无限次。对于每个字符,它的价值就是对应的ASCII码,求最终字符串价值的最大值。

思路:我又又又又又…读错题了。看成了每个魔法把整个区间 [ l i , r i ] [l_i,r_i] [li,ri] 一下子全部修改成 c i c_i ci,然后想了个从小到大贪心,线段树维护的写法把B过了…然后就过不了 C,吐了。

正解:根据题目意思,对于每个魔法我们可以使用无限次,也就等价于对于区间的任意一个字符我们可以修改或者不修改,所以也就是维护区间最大值的问题。然后,就是对于某个区间如果我们已经被大的字符修改过了,那么就可以直接跳过,所以我们贪心的按字符从大到小使用魔法,对于某个修改过的区间可以直接跳过。可以用并查集维护。代码如下:

#include <bits/stdc++.h>
#define rep(i,bbb,eee) for(int i=bbb;i<=eee;i++)
#define frep(i,bbb,eee) for(int i=bbb;i>=eee;i--)
#define mem(a,b) memset(a,b,sizeof(a))
#define inf 0x3f3f3f3f
#define pb push_back
#define AC signed
#define x first
#define y second
//#define int long long 
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;
const int N=10000010,M=998244353;
char s[N];
int n,m,fa[N];
vector<PII> seg[200];
int find(int x)
{
	if(fa[x]!=x)return fa[x]=find(fa[x]);
	return fa[x];
}
void solve()
{
	cin>>n>>m;
	cin>>(s+1);
	rep(i,1,n)fa[i]=i;
	while(m--)
	{
		char c[2];
		int l,r;
		cin>>l>>r>>c;
		seg[c[0]].pb({l,r});
	}
	frep(i,126,33)
	{
		if(seg[i].size()==0)continue;
		for(auto v:seg[i])
		{
			int l=v.x,r=v.y;
			for(int k=find(r);k>=l;k=find(k))
			{
				s[k]=max(s[k],(char)i);
				fa[k]=find(k-1);
			}
		}
	}
	int ans=0;
	rep(i,1,n)ans+=s[i];
	cout<<ans<<"\n";
}
AC main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int _=1;
	//cin>>_;
	while(_--)solve();
	return 0;
}

总结:认真读题,认真读题!!! get 用并查集跳过访问过的区间。

  1. 监狱逃亡 树状数组

大意:给定3*n的矩阵, − 1 e 9 < = a i , j < = 1 e 9 -1e9<=a_{i,j}<=1e9 1e9<=ai,j<=1e9。从(1,1)处走到(3,n)处,每次只能往右或者往下走,求走的格子的数字之和>=0 的方案数。

思路:记 s u m k sum_k sumk为第k行的前缀和。通过读题我们不难发现实际上就是需要两个向下走的位置i,j 满足i<=j, 且 s u m 1 , i + s u m 2 , j − s u m 2 , i − 1 + s u m 3 , n − s u m 3 , j − 1 > = 0 sum_{1,i}+sum_{2,j}-sum_{2,i-1}+sum_{3,n}-sum_{3,j-1}>=0 sum1,i+sum2,jsum2,i1+sum3,nsum3,j1>=0

如果只有两行我们很容想到可以直接枚举从哪个位置开始往下走。所以我们可以尝试先枚举从哪一列(即枚举 i)到第二行,每次计算有多少个j满足 s u m 2 , j + s u m 3 , n − s u m 3 , j − 1 > = s u m 2 , i − 1 − s u m 1 , i sum_{2,j}+sum_{3,n}-sum_{3,j-1}>=sum_{2,i-1}-sum_{1,i} sum2,j+sum3,nsum3,j1>=sum2,i1sum1,i

即每次查询 > = s u m 2 , i − 1 − s u m 1 , i >=sum_{2,i-1}-sum_{1,i} >=sum2,i1sum1,i的个数,树状数组的经典应用。

并且需要保证 i<=j,所以可以从后往前更新。

数组范围太大,需要离散化。

代码如下:

#include <bits/stdc++.h>
#define rep(i,bbb,eee) for(int i=bbb;i<=eee;i++)
#define frep(i,bbb,eee) for(int i=bbb;i>=eee;i--)
#define mem(a,b) memset(a,b,sizeof(a))
#define inf 0x3f3f3f3f
#define pb push_back
#define AC signed
#define x first
#define y second
#define int long long 
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;
const int N=1000010,M=1000000007;
int sum[5][N],a[5][N],n,tr[N],m;
vector<int> v;
int find(int x)
{
	return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
void add(int x,int y)
{
	while(x<=m)tr[x]+=y,x+=x&-x;
}
int ask(int x)
{
	int res=0;
	while(x)res+=tr[x],x-=x&-x;
	return res;
}
void solve()
{
	cin>>n;
	rep(i,1,3)
		rep(j,1,n)
		{
			cin>>a[i][j];
			sum[i][j]=sum[i][j-1]+a[i][j];
		}
	for(int i=1;i<=n;i++)
    {
        v.pb(sum[2][i]+sum[3][n]-sum[3][i-1]);
        v.pb(sum[2][i-1]-sum[1][i]);
    }
    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());
    m=v.size();
	int ans=0;
	frep(i,n,1)
	{
		add(find(sum[2][i]+sum[3][n]-sum[3][i-1]),1);
		int x=find(sum[2][i-1]-sum[1][i]);
		ans+=ask(m)-ask(x-1);
		ans%=M;
	}
	cout<<ans<<"\n";
}
AC main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int _=1;
	//cin>>_;
	while(_--)solve();
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值