开个帖记录 2019-09-01之后接触到的线段树类型题
2019-09-08
XKC's basketball team
题目链接:https://nanti.jisuanke.com/t/41387
题意:
给你n个数字,寻找第i个数字后面比i大至少m且距离i最远的数字
分析:
线段树维护区间最大 , 然后从右区间开始搜寻
#include#include #include #include #include
2019-10-03
D. Distinct Characters Queries
题目链接:https://codeforces.com/contest/1234/problem/D
题意:给你一个字符串 , 有q个操作:
①、 将 pos 位置的字符改为 c
②、查询 L~ R 区间不同字符的个数
分析:
挺水的一题。因为全是小写字符 , 所以我们可以对每个单独字符开个线段树, 那么一共就开了26个线段树 ,然后预处理:将母串中第 i 个位置的字符对应的线段树的第 i 个区间的值 + 1。
那么当操作为 ① 的时候我们只要将母串pos位置的字符对应线段树的pos区间值 -1 , 然后c字符对应线段树的pos区间 +1
当操作为 ②的时候我们只要判断每个字符是否有出现在L ~ R区间 , 即遍历 26 颗线段树 L ~ R 的区间和是否为 0 .若不为 0 , ans++ , 遍历完后输出ans即可
#include#define ios std::ios::sync_with_stdio(false) , std::cin.tie(0) , std::cout.tie(0) #define sd(n) scanf("%d",&n) #define sdd(n,m) scanf("%d%d",&n,&m) #define sddd(n,m,k) scanf("%d%d%d",&n,&m,&k) #define pd(n) printf("%d\n", (n)) #define pdd(n,m) printf("%d %d\n", n, m) #define pld(n) printf("%lld\n", n) #define pldd(n,m) printf("%lld %lld\n", n, m) #define sld(n) scanf("%lld",&n) #define sldd(n,m) scanf("%lld%lld",&n,&m) #define slddd(n,m,k) scanf("%lld%lld%lld",&n,&m,&k) #define sf(n) scanf("%lf",&n) #define sff(n,m) scanf("%lf%lf",&n,&m) #define sfff(n,m,k) scanf("%lf%lf%lf",&n,&m,&k) #define rep(i,a,n) for (int i=a;i<=n;i++) #define per(i,n,a) for (int i=n;i>=a;i--) #define mm(a,n) memset(a, n, sizeof(a)) #define pb push_back #define all(x) (x).begin(),(x).end() #define fi first #define se second #define ll long long #define numm ch - 48 #define INF 0x3f3f3f3f #define sz(x) ((int)x.size()) #define pi 3.14159265358979323 #define debug(x) cout << #x << ": " << x << endl #define debug2(x, y) cout <<#x<<": "< #define debug3(x, y, z) cout <<#x<<": "< #define debug4(a, b, c, d) cout <<#a<<": "<using namespace std; template void read(T &res) { bool flag=false; char ch; while(!isdigit(ch=getchar()))(ch=='-')&&(flag=true); for(res=numm; isdigit(ch=getchar()); res=(res<<1)+(res<<3)+numm); flag&&(res=-res); } template void Out(T x) { if(x<0)putchar('-'),x=-x; if(x>9)Out(x/10); putchar(x%10+'0'); } ll pow_mod(ll x, ll n , ll mod) { ll res=1; while(n) { if(n&1)res=res*x%mod; x=x*x%mod; n>>=1; } return res; } #define lson l,mid,rt << 1 #define rson mid + 1,r,rt << 1 | 1 #define ll long long using namespace std; const int N = 4e5 + 5000; const ll mod = 1e9 + 7; ll n,k; ll a[N]; int t[N][26]; void update(int l,int r,int rt,int id,int k,int val) { if(k < l || k > r) return ; if(l == r) { t[rt][id] += val; return ; } int mid = l + r >> 1; if(k <= mid) update(lson,id,k,val); else update(rson,id,k,val); t[rt][id] = t[rt << 1][id] + t[rt << 1 | 1][id]; } int qu(int l,int r,int rt,int ql,int qr,int id) { if(r < ql || l > qr) return 0; int res = 0; if(ql <= l && qr >= r) return t[rt][id]; int mid = l + r >> 1; if(ql <= mid) res += qu(lson,ql,qr,id); if(qr > mid) res += qu(rson,ql,qr,id); return res; } int main() { string str = " "; string strr; cin >> strr; str += strr; n = sz(strr); rep(i ,1 ,n) { update(1,n,1,str[i] - 'a',i,1); } int q,op,l,r; sd(q); while(q--) { sd(op); if(op == 1) { int pos; char c; sd(pos); cin >> c; update(1,n,1,str[pos] - 'a',pos ,-1); str[pos] = c; update(1,n,1,c - 'a',pos,1); } else { sdd(l , r); ll ans = 0; rep(i,0,25) { if(0 < (qu(1,n,1,l,r,i))) ans++; } pd(ans); } } return 0; }
2019-10-06
The Child and Sequence
题目链接:https://codeforces.com/contest/438/problem/D
题意:
有n个数、m条指令 , 指令对应的操作分别是:
①、区间求和
②、区间取模
③、单点修改
根据指令完成操作
分析:
很明显这题就是要求维护一棵支持区间取膜,单点更新,区间求和的线段树。区间求和 和 单点修改都没什么问题,问题就在区间取模
先将区间和求出来再取模这是肯定不行的 , 所以我们需要换种思想——对于一个区间,如果它的区间和 sum < Mod , 那我们对整个区间取模就没有意义了 , 这是个剪枝的条件
所以我们可以先进行单点更新 , 知道区间和 sum < Mod , 那么我们就没有对它进行取模的必要了。 再者对于每个数取模一个小于它的数 , 它的值肯定会小于原来的二分之一
当搜寻到某个区间和小于 Mod 的时候 , 我们就可以直接停止搜索 , 这样算下来时间复杂度并不会很高 , 也就能做了。
详见代码
View Code
2019-10-08
Max answer
题目链接:https://nanti.jisuanke.com/t/38228
题意:
给一个长为n(n <= 500000)的数组a, 对每个区间,求区间和乘区间最小值的最大值(−1e5 ≤ ai ≤1e5).
分析:
这道题乍一看像是单调栈的模板题 , 但很可惜的是 ai 的取值可以为负数 , 所以对于以 ai 为最小值的区间 , 若 ai 为负数 , 要找到最小区间和 , 若为 ai 为正数 , 要找到最大区间和
对于正数ai,利用单调栈寻找以ai为最小值的区间,利用前缀和数组求区间和再*ai 即结果(ai 为正数 , ai为区间最小值)
先用单调栈求出以a[i]为最小值能够延伸的左端点L[i]和右端点R[i] , 然后对于每个正数,我们用该数乘以这个区间和。区间和用前缀和来求,对于每个数所能影响的最大区间我们已经求出来了。对于负数来说,我们需要求在它右区间的最小前缀和减去左区间的最大前缀和 , 因为只有它的区间和最小对应的这个区间的value值才最大,又由区间和=sum[j]-sum[i]可知,sum[j]最小,sum[i]最大时才能时这个区间和最小,所以问题的关键转化为寻找最小的sum[j]和最大的sum[i]
对于右区间的最小前缀和,以及左区间的最大前缀和我们用线段树来求
#include#define ios std::ios::sync_with_stdio(false) , std::cin.tie(0) , std::cout.tie(0) #define sd(n) scanf("%d",&n) #define sdd(n,m) scanf("%d%d",&n,&m) #define sddd(n,m,k) scanf("%d%d%d",&n,&m,&k) #define pd(n) printf("%d\n", (n)) #define pdd(n,m) printf("%d %d\n", n, m) #define pld(n) printf("%lld\n", n) #define pldd(n,m) printf("%lld %lld\n", n, m) #define sld(n) scanf("%lld",&n) #define sldd(n,m) scanf("%lld%lld",&n,&m) #define slddd(n,m,k) scanf("%lld%lld%lld",&n,&m,&k) #define sf(n) scanf("%lf",&n) #define sff(n,m) scanf("%lf%lf",&n,&m) #define sfff(n,m,k) scanf("%lf%lf%lf",&n,&m,&k) #define rep(i,a,n) for (int i=a;i<=n;i++) #define per(i,n,a) for (int i=n;i>=a;i--) #define mm(a,n) memset(a, n, sizeof(a)) #define pb push_back #define all(x) (x).begin(),(x).end() #define fi first #define se second #define ll long long #define numm ch - 48 #define MOD 1000000007 #define INF 0x3f3f3f3f #define pi 3.14159265358979323 #define debug(x) cout << #x << ": " << x << endl #define debug2(x, y) cout <<#x<<": "< #define debug3(x, y, z) cout <<#x<<": "< #define debug4(a, b, c, d) cout <<#a<<": "<using namespace std; template void read(T &res){bool flag=false;char ch;while(!isdigit(ch=getchar()))(ch=='-')&&(flag=true); for(res=numm;isdigit(ch=getchar());res=(res<<1)+(res<<3)+numm);flag&&(res=-res);} template void Out(T x){if(x<0)putchar('-'),x=-x;if(x>9)Out(x/10);putchar(x%10+'0');} ll gcd(ll a,ll b){return b?gcd(b,a%b):a;} ll lcm(ll a,ll b){return a*b/gcd(a,b);} ll pow_mod(ll x,ll n,ll mod){ll res=1;while(n){if(n&1)res=res*x%mod;x=x*x%mod;n>>=1;}return res;} ll fact_pow(ll n,ll p){ll res=0;while(n){n/=p;res+=n;}return res;} const int N = 5e5 + 10; ll sum[N],a[N]; ll maxn[N << 2],minn[N << 2]; void build(int l,int r,int root) { if(l==r) { maxn[root]=minn[root]=sum[l]; return ; } int mid=l+r>>1; build(l,mid,root<<1); build(mid+1,r,root<<1|1); maxn[root]=max(maxn[root<<1],maxn[root<<1|1]); minn[root]=min(minn[root<<1],minn[root<<1|1]); } ll qmax(int l,int r,int root,int ql,int qr) { if(l>=ql&&r<=qr) return maxn[root]; int mid=l+r>>1; ll ans=-INF; if(mid>=ql) ans=qmax(l,mid,root<<1,ql,qr); if(mid<qr) ans=max(ans,qmax(mid+1,r,root<<1|1,ql,qr)); return ans; } ll qmin(int l,int r,int root,int ql,int qr) { if(l>=ql&&r<=qr) return minn[root]; int mid=l+r>>1; ll ans=INF; if(mid>=ql) ans=qmin(l,mid,root<<1,ql,qr); if(mid<qr) ans=min(ans,qmin(mid+1,r,root<<1|1,ql,qr)); return ans; } int LL[N],RR[N],st[N]; int main() { ios; int n; cin >> n; rep(i ,1 ,n) cin >> a[i],sum[i] = sum[i-1]+a[i]; build(1,n,1); int top=0; rep(i ,1 ,n) { while(top&&a[st[top]]>a[i]) RR[st[top--]]=i-1; st[++top]=i; } while(top) RR[st[top--]]=n; for(int i=n;i>=1;i--) { while(top&&a[st[top]]>a[i]) LL[st[top--]]=i+1; st[++top]=i; } while(top) LL[st[top--]]=1; ll ans = -INF; rep(i ,1 ,n) { if(a[i]>0) ans=max(ans,a[i]*(sum[RR[i]]-sum[LL[i]-1])); else ans=max( ans,a[i]*( qmin(1,n,1,i,RR[i])-qmax(1,n,1,LL[i],i)) ); } cout << ans << endl; return 0; }