競プロ復習 ABC174 F問題
https://atcoder.jp/contests/abc174/tasks/abc174_f
クオリ区間ごとの個数を求める問題。
・累積和を使う
この手の問題は、クオリをソートした順にみて行くといいです。
(前のクオリの結果が再利用できるため、高速化に繋がる)
今回はrを昇順ソートしました。
struct QUARR{
int l;
int r;
int index;
int type;
int ans;
bool operator<(QUARR q) const{
if(type==1){
if(q.r==r) return q.l>l;
return q.r>r;
}
else{
return q.index>index;
}
}
};
答えはソート前で出力しないといけないため、戻す用のソートも定義しています。
rが小さい順に見ていき、玉の色の一番右を記録しておきます。
※
1 2 3 1 3 1
というc[i]に対してrをどんどん広げて、玉の色の一番右を記録
r=1 1->1
r=2 1->1 2->2
r=3 1->1 2->2 3->3
r=4 1->4 2->2 3->3
r=5 1->4 2->2 3->5
r=6 1->6 2->2 3->5
また、右端の玉の色が記録されている場所を1にするようなBIT列を考えます。
※1 2 3 1 3 1
r=1 100000
r=2 110000
r=3 111000
r=4 011100
r=5 010110
r=6 010011
このbit列に対して、[l,r]区間の答えを求める際、1の数を数えることで答えが求まるようになります。
(0の場所にある玉の色は、それより右にある1のどれかと同じになっているため)
また、累積和を用いることでO(1)で区間の和が求めそうです。
※1 2 3 1 3 1
r=1 111111
r=2 122222
r=3 123333
r=4 012333
r=5 011233
r=6 011123
[3,5]の場合、r=5の累積和を用いて3-1=2と答えが求まります。
・フェニック木で累積和を高速化
累積和の更新にオーダーがかかるため、まだTLE解です。
ただ、こういった累積和の更新を高速に行えるデータ構造があります。
https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A7%E3%83%8B%E3%83%83%E3%82%AF%E6%9C%A8
フェニック木(fenwick tree)です。(BITって呼ばれてるものと同じ?)
C++の標準ライブラリにないデータ構造なので、自前で準備する必要がありますが、実装は簡単にできます。
int ft_bit[NMAX];
void ft_add(int a,int w,int N){
for(int x=a;x<=N;x+=x&-x){
ft_bit[x]+=w;
}
}
int ft_sum(int a){
int ret=0;
for(int x=a;x>0;x-=x&-x){
ret+=ft_bit[x];
}
return ret;
}
参考(http://hos.ac/slides/20140319_bit.pdf)
ft_addが累積和の更新です。[a,N]区間にwを加えます。
x&-xは、xを二進で見たときに一番下にある1が取れます。
ft_sumは累積和の算出です。[1,N]区間の累積和を求めます。
二進の一番下の1に1を足したり、引いたりしているためループは長くてもO(logN)で終わりそうです。
・提出コード
という訳で、以下提出コードです。
//#include<bits/stdc++.h> #include <iostream> #include <algorithm> #include <cmath> #include <vector> #include <queue> #include <set>
/* #include <boost/multiprecision/cpp_int.hpp> #include <boost/multiprecision/cpp_dec_float.hpp> #include <boost/rational.hpp>
namespace mp = boost::multiprecision;
using Real = mp::number<mp::cpp_dec_float<1024>>;
using Bint = mp::cpp_int;
*/
using namespace std; #define int long long #define REP(i,s,e) for((i)=(s);(i)<(e);(i)++) #define RREP(i,s,e) for((i)=((s)-1);(i)>=(e);(i)--) #define FOR(i,n) for((i)=(0);(i)<(n);(i)++) #define RFOR(i,n) for((i)=((n)-1);(i)>=(0);(i)--) #define MOD 1000000007 #define NMAX 500010
struct QUARR{
int l;
int r;
int index;
int type;
int ans;
bool operator<(QUARR q) const{
if(type==1){
if(q.r==r) return q.l>l;
return q.r>r;
}
else{
return q.index>index;
}
}
};
int ft_bit[NMAX];
void ft_add(int a,int w,int N){
for(int x=a;x<=N;x+=x&-x){
ft_bit[x]+=w;
}
}
int ft_sum(int a){
int ret=0;
for(int x=a;x>0;x-=x&-x){
ret+=ft_bit[x];
}
return ret;
}
signed main(){
QUARR qu[NMAX];
int last[NMAX];
int c[NMAX];
int N,Q;
cin>>N>>Q;
for(int i=1;i<=N;i++){
cin>>c[i];
last[i]=-1;
}
for(int i=1;i<=Q;i++){
int l,r;
cin>>l>>r;
qu[i].l=l;
qu[i].r=r;
qu[i].index=i;
qu[i].type=1;
qu[i].ans=0;
}
sort(qu+1,qu+Q+1);
for(int i=1;i<=qu[1].r;i++){
if(last[c[i]]==-1){
ft_add(i,1,N);
last[c[i]]=i;
}
else{
ft_add(last[c[i]],-1,N);
ft_add(i,1,N);
last[c[i]]=i;
}
}
qu[1].ans=ft_sum(qu[1].r)-ft_sum(qu[1].l-1);
qu[1].type=2;
for(int i=2;i<=Q;i++){
for(int j=qu[i-1].r+1;j<=qu[i].r;j++){
if(last[c[j]]==-1){
ft_add(j,1,N);
last[c[j]]=j;
}
else{
ft_add(last[c[j]],-1,N);
ft_add(j,1,N);
last[c[j]]=j;
}
}
qu[i].ans=ft_sum(qu[i].r)-ft_sum(qu[i].l-1);
qu[i].type=2;
}
sort(qu+1,qu+Q+1);
for(int i=1;i<=Q;i++){
cout<<qu[i].ans<<endl;
}
}
各クオリごとにft_sumしているためO(QlogN)、rを[1,N]区間で伸ばす際にft_addするためO(NlogN)なので、合計でO((Q+N)logN)です。
※Qmax+Nmax=10^6,logNmax≒5.7、ft_sunやft_addは二回していることを考えると、10^7の計算量で落ち着きそうです。
なのですが、1.7sほど実行時間がかかっているため、書き方がかなり悪い...。(ソートとかを加味しても、10^7なら500msくらいで落ち着いてほしい)
・8/3 20:00追記 遅いわけ
https://xuzijian629.hatenablog.com/entry/2019/03/31/130708
どうもendlはは遅いようで、
cout<<qu[i].ans<<endl;
->cout<<qu[i].ans<<"\n";
とするだけで、700ms縮まって1000msになりました。
cin.tie(nullptr); を追加、#define int long longを削除(これは意味ないかも)
で50ms縮まって950msになりました。
cinとcoutをscanfとprintfに書き換えると、500msほど縮まって480msになりました。(早い!!)
これで想定通りの早さになりました。I/Oの多い問題でcin,coutは避けた方がよさそうです。
・最後に
https://kenkoooo.com/atcoder/#/table/
atcoder problemで見た感じ、このF問題は水色上位らしいです...。
なかなか青色への道は遠そうですね。
この記事が気に入ったらサポートをしてみませんか?