Codeforces Round #145 F. TorCoder 26棵线段树维护字母变化?一颗就够了

F. TorCoder
time limit per test3 seconds
memory limit per test256 megabytes
inputinput.txt
outputoutput.txt
A boy named Leo doesn't miss a single TorCoder contest round. 
On the last TorCoder round number 100666 Leo stumbled over the
 following problem. He was given a string s, consisting of n 
lowercase English letters, and m queries. Each query is 
characterised by a pair of integers li, ri (1 ≤ li ≤ ri ≤ n).

We'll consider the letters in the string numbered from 1 to n
 from left to right, that is, s = s1s2... sn.

After each query he must swap letters with indexes from li to 
ri inclusive in string s so as to make substring (li, ri) a 
palindrome. If there are multiple such letter permutations, 
you should choose the one where string (li, ri) will be 
lexicographically minimum. If no such permutation exists, 
you should ignore the query (that is, not change string s).

Everybody knows that on TorCoder rounds input line and array 
size limits never exceed 60, so Leo solved this problem easily. 
Your task is to solve the problem on a little bit larger limits. 
Given string s and m queries, print the string that results 
after applying all m queries to string s.

Input
The first input line contains two integers n and m (1 ≤ n, m ≤ 105) 
— the string length and the number of the queries.

The second line contains string s, consisting of n lowercase
 Latin letters.

Each of the next m lines contains a pair of integers li, ri 
(1 ≤ li ≤ ri ≤ n) — a query to apply to the string.

Output
In a single line print the result of applying m queries to 
string s. Print the queries in the order in which they are 
given in the input.


Examples:

7 2
aabcbaa
1 3
5 7
abacaba

3 2
abc
1 2
2 3
abc

10 3
rrrrrrrrrr
2 8
5 8
3 10
rrrrrrrrrr

Note
A substring (li, ri) 1 ≤ li ≤ ri ≤ n) of string s = s1s2... sn 
of length n is a sequence of characters slisli + 1...sri.

A string is a palindrome, if it reads the same from left to right 
and from right to left.


String x1x2... xp is lexicographically smaller than string 
y1y2... yq,if either p < q and x1 = y1, x2 = y2, ... , xp = yp, 
or exists such number r (r < p, r < q), that x1 = y1, x2 = y2
, ... , xr = yr and xr + 1 < yr + 1.

题意:

给定一个长为n的字符串。

有m次操作,每次操作将[l,r]这些位置的字符进行重排,得到字典序最小的回文字符串,如果无法操作就不进行。

求m次操作后的字符串。

思路:

说到底,还是在区间上进行修改,所以考虑线段树?

下面26的思路一点都不好,开一颗线段树,节点上

用二维数组来维护不同字母区间内出现的次数。

线段树,有些地方不注意还是很容易写错的,尤其是

那几个区间边界的表示,例如,区间更新时add要乘以

该节点覆盖区间的长度,在我的代码上这个长度等于

Right[p]-Left[p]+1,而不是R-L+1(这是要修改的区间)。

这一个问题,改了一天?

26颗线段树 维护区间 每个字母的出现次数

为了使得字典序最小,贪心的放进去。

PS:

注意第三组样例情况;

懒标记无效状态不能再用零(实在想用就用其他数表示树节点

            的0权值,简单一点直接将懒标记-1作为无效)。

代码实现:(二维节点)build本题中可有可无

#include<iostream>
#include<cstring>
#include<cmath>
#include<queue>
#include<cstdio>
#include<algorithm>
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;
const int N=4*2e5+100;
const int M=4e4+100;
int Left[N],Right[N],dat[N][26],add[N][26];
string str;
void build(int p,int l,int r) {
   Left[p]=l;
   Right[p]=r;
   if(l==r) {
      for(int i=0; i<26; i++) {
         if(i==int(str[l]-'a'))dat[p][i]=1;
         else dat[p][i]=0;
         add[p][i]=-1;
      }
      return ;
   }
   int mid=(l+r)/2;
   build(2*p,l,mid);
   build(2*p+1,mid+1,r);
   for(int i=0; i<26; i++) {
      dat[p][i]=dat[p*2][i]+dat[2*p+1][i];
      add[p][i]=-1;
   }
}
void spread(int p,int zimu) {
   if(add[p][zimu]!=-1) {
      int mid=(Left[p]+Right[p])/2;
      dat[p*2][zimu]=add[p][zimu]*(mid-Left[p]+1);
      dat[p*2+1][zimu]=add[p][zimu]*(Right[p]-mid);
      add[2*p][zimu]=add[p][zimu];
      add[2*p+1][zimu]=add[p][zimu];
      add[p][zimu]=-1;
   }
}
void change(int p,int l,int r,int v,int zimu) {

   if(l<=Left[p]&&r>=Right[p]) {
      dat[p][zimu]=(Right[p]-Left[p]+1)*v;
      add[p][zimu]=v;
      return ;
   }
   spread(p,zimu);
   int mid=(Left[p]+Right[p])/2;
   if(l<=mid)change(2*p,l,r,v,zimu);
   if(r>mid)change(2*p+1,l,r,v,zimu);
   dat[p][zimu]=dat[p*2][zimu]+dat[2*p+1][zimu];
}
int query(int p,int l,int r,int zimu) {

   if(l<=Left[p]&&r>=Right[p]) return dat[p][zimu];
   spread(p,zimu);
   int mid=(Left[p]+Right[p])/2,val=0;
   if(l<=mid)val+=query(2*p,l,r,zimu);
   if(r>mid)val+=query(2*p+1,l,r,zimu);
   return val;
}
int num[N];
int main() {
#ifdef ONLINE_JUDGE
   freopen("input.txt","r",stdin);
   freopen("output.txt","w",stdout);
#endif
   int n,m;
   while(cin>>n>>m) {
      cin>>str;
      for(int i=n-1; i>=0; i--)str[i+1]=str[i];
      build(1,1,n);
      while(m--) {
         int x,y,tmp=0;
         cin>>x>>y;
         int p=x,q=y;
         for(int i=0; i<=25; i++) {
            num[i]=query(1,x,y,i);
            if(num[i]&1)tmp++;
         }
         if((y-x+1&1)&&tmp!=1)continue;
         if((y-x+1)%2==0&&tmp!=0)continue;
         for(int i=0; i<26; i++) {
            change(1,x,y,0,i);
         }
         for(int i=0; i<26; i++) {
            if(num[i]&1) {
               change(1,(x + y)/2,(x + y)/2,1,i);
               num[i] -=1;
            }
            if(num[i])   {
               change(1,p,p+num[i]/2-1,1,i);
               change(1,q-num[i]/2+1,q,1,i);
               p=p+num[i]/2;
               q=q-num[i]/2;
            }
         }
      }
      for(int i=1; i<=n; i++) {
         for(int j=0; j<26; j++) {
            if(query(1,i,i,j)) {
               cout<<char('a'+j);
            }
         }
      }
      cout<<endl;
   }
   return 0;

}

代码实现(26棵线段树):

#include<iostream>
#include<cstring>
#include<cmath>
#include<queue>
#include<cstdio>
#include<algorithm>
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;
const int N=4*2e5+100;
const int M=4e4+100;
//封装线段树
//节省空间,去掉Left、Right数组
//用传参数表示Left、Right 
struct segtree {
   int val[N],add[N];
   void clear() {
      memset(val,0,sizeof(val));
      memset(add,-1,sizeof(add));
   }
   void spread(int p,int Left,int Right) {
      if(add[p]!=-1) {
         int mid=(Left+Right)/2;
         val[2*p]=add[p]*(mid-Left+1);
         val[2*p+1]=add[p]*(Right-mid);
         add[2*p+1]=add[2*p]=add[p];
         add[p]=-1;
      }
   }
   void change(int p,int l,int r,int v,int Left,int Right) {
      if(l<=Left&&r>=Right) {
         val[p]=v*(Right-Left+1);
         add[p]=v;
         return ;
      }
      spread(p,Left,Right);
      int mid=(Left+Right)/2;
      if(l<=mid)change(2*p,l,r,v,Left,mid);
      if(r>mid)change(2*p+1,l,r,v,mid+1,Right);
      val[p]=val[2*p]+val[2*p+1];
   }
   int query(int p,int l,int r,int Left,int Right) {
      if(l<=Left&&r>=Right) {
         return val[p];
      }
      spread(p,Left,Right);
      int mid=(Left+Right)/2;
      int val1,val2;
      val1=val2=0;
      if(l<=mid)val1=query(2*p,l,r,Left,mid);
      if(r>mid)val2=query(2*p+1,l,r,mid+1,Right);  
      return val1+val2;
   }
} arr[27];
int num[28];
//偶数区间修改 
void esol(int l,int r,int cnt,int n) {
   if(cnt==0) {
      for(int i=0; i<26; i++) { //清空原来的 
         arr[i].change(1,l,r,0,1,n);
      }
      int p=l,q=r;
      for(int i=0; i<26; i++) {
         if(num[i]) {
            arr[i].change(1,p,p+num[i]/2-1,1,1,n);
            arr[i].change(1,q-num[i]/2+1,q,1,1,n);
            p=p+num[i]/2;
            q=q-num[i]/2;
         }
      }
   }
}
//奇数区间修改 
void osol(int l,int r,int cnt,int n) {
   if(cnt==1) {
      int p=l,q=r;
      for(int i=0; i<26; i++) {
         arr[i].change(1,l,r,0,1,n);
      }
      for(int i=0; i<26; i++) {
         if(num[i]==0)continue;
         if(num[i]&1) {
            arr[i].change(1,(l+r)/2,(l+r)/2,1,1,n);
            num[i]-=1;
         }
         arr[i].change(1,p,p+num[i]/2-1,1,1,n);
         arr[i].change(1,q-num[i]/2+1,q,1,1,n);
         p=p+num[i]/2;
         q=q-num[i]/2;
      }
   }
}
int main() {
   freopen("input.txt","r",stdin);
   freopen("output.txt","w",stdout);
   int n,m,l,r;
   string str;
   while(cin>>n>>m) {
      cin>>str;
      memset(num,0,sizeof(num));
      for(int i=0; i<26; i++) arr[i].clear();
      for(int i=n-1; i>=0; i--) str[i+1]=str[i];
      for(int i=1; i<=n; i++)
         arr[str[i]-'a'].change(1,i,i,1,1,n);
      while(m--) {
         cin>>l>>r;
         int tmp=0;
         for(int i=0; i<26; i++) {
            num[i]=arr[i].query(1,l,r,1,n);
            if(num[i]&1)tmp++;
         }
         if((l-r+1)%2==0) esol(l,r,tmp,n);
         else osol(l,r,tmp,n);

      }
      for(int i=1; i<=n; i++) {
         for(int j=0; j<26; j++) {
            if(arr[j].query(1,i,i,1,n)) {
               cout<<char('a'+j);
            }
         }
      }
      cout<<endl;
   }
   return 0;

}

THE END;

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值