双指针
入门/板题
P1638 逛画展
基础双指针 初始值需要设置为 \(l=1\) 且 \(r=0\)
两种操作:
-
\(tot=m\) 我们出队 在出队过程中统计答案
-
\(tot\neq m\) 选择入队 直到 \(tot=m\)
先进行 \(1\) 操作 因为我们的终止条件是右端点 所以需要让操作右端点的操作 \(2\) 后操作 否则会导致统计到边界外的东西
注意移动指针时的边界条件
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 1e6 + 5;
const int inf = 0x3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , m , cnt[N] , ansl , ansr = inf , tot , a[N];
string s;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
int l = 1 , r = 0;
while ( r <= n )
{
while ( tot == m && l <= r )
{
if ( r - l + 1 < ansr - ansl + 1 ) ansl = l , ansr = r;
tot -= ( -- cnt[a[l++]] == 0 );
}
tot += ( ++ cnt[a[++r]] == 1 );
}
cout << ansl << ' ' << ansr << endl;
return 0;
}
P3143 [USACO16OPEN] Diamond Collector S
显然我们可以先将序列排序再用双指针来进行统计
但是因为有两个架子 所以我们需要从前往后和从后往前都扫一遍 最后枚举中间点并组合一下即可
注意 \(f\) 和 \(g\) 数组都需要取得前缀最大值
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 1e6 + 5;
const int inf = 0x3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , k , f[N] , g[N] , l , r , a[N] , ans;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , k = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
a[n+1] = inf;
sort ( a + 1 , a + n + 1 );
l = 1 , r = 0;
while ( r <= n )
{
while ( a[r] - a[l] > k && l <= r ) ++ l;
f[r] = max ( f[r-1] , r - l + 1 ) , ++ r;
}
l = n + 1 , r = n;
while ( l )
{
while ( a[r] - a[l] > k && l <= r ) -- r;
g[l] = max ( g[l+1] , r - l + 1 ) , -- l;
}
for ( int i = 1 ; i < n ; i ++ ) ans = max ( ans , f[i] + g[i+1] );
cout << ans << endl;
return 0;
}
P8783 [蓝桥杯 2022 省 B] 统计子矩阵
简单双指针 我们枚举一个上边界和一个下边界 并用双指针和前缀和来统计上下边界之间 合法区间的个数
注意计入区间要用 \(r-l+1\) 而不能单纯将答案自增(因为我们双指针一次统计的是以 \(r\) 为左端点的合法区间个数)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
#define int long long
const int N = 500 + 5;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , m , k , a[N][N] , b[N] , ans;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read() , k = read();
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 ; j <= m ; j ++ )
a[i][j] = read() + a[i-1][j];
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = i ; j <= n ; j ++ )
{
for ( int l = 1 ; l <= m ; l ++ ) b[l] = a[j][l] - a[i-1][l];
int l = 1 , r = 0 , tot = 0;
while ( r <= m )
{
while ( l <= r && tot > k ) tot -= b[l++];
ans += r - l + 1 , tot += b[++r];
}
}
cout << ans << endl;
return 0;
}
P8472 [Aya Round 1 G] 咕噜论坛(post)
思想和 \(P8783\) 类似
注意在判断 \(g\) 和 \(b\) 的时候要注意区间内有没有 \(p\) (因为有 \(p\) 是一定不能合法的)
代码虽然长但是一次 \(AC\) 赢!
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 1e3 + 5;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , m , k , sumb[N][N] , sumg[N][N] , sump[N][N] , ans;
int lx , ly , rx , ry;
char ansc;
string mp[N];
int checkb ( int lx , int ly , int rx , int ry )
{
int sp = sump[rx][ry] - sump[lx-1][ry] - sump[rx][ly-1] + sump[lx-1][ly-1];
int sg = sumg[rx][ry] - sumg[lx-1][ry] - sumg[rx][ly-1] + sumg[lx-1][ly-1];
if ( sg <= k && !sp ) return 1;
else return 0;
}
void solveb ( int i , int j )
{
int l = 1 , r = 0;
while ( r <= m )
{
while ( l <= r && !checkb(i,l,j,r) ) ++ l;
if ( l <= r && ( r - l + 1 ) * ( j - i + 1 ) > ans ) ans = ( r - l + 1 ) * ( j - i + 1 ) , lx = i , ly = l , rx = j , ry = r , ansc = 'B';
++ r;
}
}
int checkg ( int lx , int ly , int rx , int ry )
{
int sp = sump[rx][ry] - sump[lx-1][ry] - sump[rx][ly-1] + sump[lx-1][ly-1];
int sb = sumb[rx][ry] - sumb[lx-1][ry] - sumb[rx][ly-1] + sumb[lx-1][ly-1];
if ( sb <= k && !sp ) return 1;
else return 0;
}
void solveg ( int i , int j )
{
int l = 1 , r = 0;
while ( r <= m )
{
while ( l <= r && !checkg(i,l,j,r) ) ++ l;
if ( l <= r && ( r - l + 1 ) * ( j - i + 1 ) > ans ) ans = ( r - l + 1 ) * ( j - i + 1 ) , lx = i , ly = l , rx = j , ry = r , ansc = 'G';
++ r;
}
}
int checkp ( int lx , int ly , int rx , int ry )
{
int sp = sump[rx][ry] - sump[lx-1][ry] - sump[rx][ly-1] + sump[lx-1][ly-1];
return sp == ( rx - lx + 1 ) * ( ry - ly + 1 );
}
void solvep ( int i , int j )
{
int l = 1 , r = 0;
while ( r <= m )
{
while ( l <= r && !checkp(i,l,j,r) ) ++ l;
if ( l <= r && ( r - l + 1 ) * ( j - i + 1 ) > ans ) ans = max ( ( r - l + 1 ) * ( j - i + 1 ) , ans ) , lx = i , ly = l , rx = j , ry = r , ansc = 'P';
++ r;
}
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read() , k = read();
for ( int i = 1 ; i <= n ; i ++ )
{
cin >> mp[i] , mp[i] = " " + mp[i];
for ( int j = 1 ; j <= m ; j ++ )
{
sumb[i][j] = sumb[i-1][j] + sumb[i][j-1] - sumb[i-1][j-1] + ( mp[i][j] == 'B' );
sumg[i][j] = sumg[i-1][j] + sumg[i][j-1] - sumg[i-1][j-1] + ( mp[i][j] == 'G' );
sump[i][j] = sump[i-1][j] + sump[i][j-1] - sump[i-1][j-1] + ( mp[i][j] == 'P' );
}
}
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = i ; j <= n ; j ++ )
solveb(i,j) , solveg(i,j) , solvep(i,j);
cout << ans << endl;
for ( int i = lx ; i <= rx ; i ++ )
for ( int j = ly ; j <= ry ; j ++ )
mp[i][j] = ansc;
for ( int i = 1 ; i <= n ; i ++ , cout.put(endl) )
for ( int j = 1 ; j <= m ; j ++ )
cout << mp[i][j];
return 0;
}
P8708 [蓝桥杯 2020 省 A1] 整数小拼接
双指针的题需要注意的最大问题:区间合法的判断条件
我们可以先将数组排序再尝试进行拼接
第一次我们先用 \(l\) 拼在 \(r\) 的前面
如果区间合法(指 \(a[l]a[r]\) 这个数 \(\le k\)) 那么显然 \(a[l]\) 可以和 \(a_i(i\in [l+1,r] )\) 中的所有数去匹配
那么我们再尝试增大 \(l\) 指针去匹配即可 注意计入区间的时候因为自己不能和自己匹配 所以不能 \(+1\)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
#define int long long
const int N = 1e5 + 5;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int merge ( int x , int y )
{
int res = 1 , temp = y;
while ( temp ) res *= 10 , temp /= 10;
return x * res + y;
}
int n , k , a[N] , ans;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , k = read();
generate ( a + 1 , a + n + 1 , read );
sort ( a + 1 , a + n + 1 );
int l = 1 , r = n;
while ( l != r )
{
while ( l < r && merge ( a[l] , a[r] ) <= k ) ans += r - l , l ++;
if ( l == r ) break;
-- r;
}
l = 1 , r = n;
while ( l != r )
{
while ( l < r && merge ( a[r] , a[l] ) <= k ) ans += r - l , l ++;
if ( l == r ) break;
-- r;
}
cout << ans << endl;
return 0;
}
P1381 单词背诵
基础双指针 注意区间合法条件为 \(tot=res\) 而不是 \(tot=n\)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 1e5 + 5;
const int inf = 0x3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , m , ans = inf , res;
map<string,int> need , cnt , vis;
string str , s[N];
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read();
for ( int i = 1 ; i <= n ; i ++ ) cin >> str , need[str] = 1;
m = read();
for ( int i = 1 ; i <= m ; i ++ )
{
cin >> s[i];
if ( need[s[i]] && !vis[s[i]] ) vis[s[i]] = 1 , ++ res;
}
if ( res == 0 ) return cout << 0 << endl << 0 << endl , 0;
int l = 1 , r = 0 , tot = 0;
while ( r <= m )
{
while ( l <= r && tot == res ) ans = min ( ans , r - l + 1 ) , tot -= need[s[l]] ? ( -- cnt[s[l]] == 0 ) : 0 , ++ l;
++ r , tot += need[s[r]] ? ( ++ cnt[s[r]] == 1 ) : 0;
}
cout << res << endl << ans << endl;
return 0;
}
P3029 [USACO11NOV] Cow Lineup S
和上一道题没什么区别 只是需要离散化
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 1e6 + 5;
const int inf = 0x3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , ans = inf , cnt[N] , res , vis[N];
vector<int> lsh;
struct node { int x , id; } a[N];
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i].x = read() , a[i].id = read() , lsh.eb(a[i].id);
sort ( lsh.begin() , lsh.end() );
lsh.erase(unique(lsh.begin(),lsh.end()),lsh.end());
for ( int i = 1 ; i <= n ; i ++ ) a[i].id = lower_bound ( lsh.begin() , lsh.end() , a[i].id ) - lsh.begin() + 1 , res += !vis[a[i].id] , vis[a[i].id] ++;
sort ( a + 1 , a + n + 1 , [](const node &a , const node &b) { return a.x < b.x; } );
int l = 1 , r = 0 , tot = 0;
while ( r <= n )
{
while ( l <= r && tot == res ) ans = min ( a[r].x - a[l].x , ans ) , tot -= ( -- cnt[a[l++].id] == 0 );
tot += ( ++ cnt[a[++r].id] == 1 );
}
cout << ans << endl;
return 0;
}
P8775 [蓝桥杯 2022 省 A] 青蛙过河
转化题意 对于所有长度 \(= ans\) 的区间 都需要让区间和 \(\ge 2x\) 求这个最小的 \(ans\)
因为如果最小跳跃长度为 \(ans\) 那么所有长度为 \(ans\) 的区间内 每次石头的高度都会有一个 \(-1\) 所以显然需要让每一个区间和都 \(\ge 2x\)
所以我们对于每一个 \(r\) 需要求出最短的符合条件( 区间和 \(\ge 2x\) )的 \(l\) 来取 \(max\) 进行统计
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 1e5 + 5;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , x , a[N] , tot , ans;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , x = read();
for ( int i = 1 ; i < n ; i ++ ) a[i] = read();
int l = 1 , r = 0 , tot = 0;
while ( r < n )
{
while ( l <= r && tot >= 2 * x ) tot -= a[l++];
tot += a[++r];
ans = max ( ans , r - l + 1 );
}
cout << ans << endl;
return 0;
}
进阶/转化
Andrey and Escape from Capygrad
对于线段的双指针入门题
可以发现对于在 \([l,r]\) 区间的一次询问 要么询问本身呆在原地不动 要么向后跳到 \(b\)
因为跳到的最远点具有单调性 那么我们需要将询问离线排序并用双指针进行统计
\(PS:\) 具有单调性的原因:
假设一个 \(x'>x\) 且 \(x\) 能到达的地方比 \(x'\) 远
那么一定存在一个区间 \([l,r](r<x')\) 使得 \(x\) 可以跳到更远的地方 所以此时的 \(b_i\) 一定大于 \(r\) 和 \(b_i\le r\) 矛盾
\(upd\ on\ 10.30\): 貌似并不是很传统的双指针()
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 2e5 + 5;
const int inf = 0x3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , qq , ans[N];
struct edge { int l , r , a , b; } a[N];
struct query { int pos , id; } q[N];
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
int T = read();
while ( T -- )
{
n = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i].l = read() , a[i].r = read() , a[i].a = read() , a[i].b = read();
qq = read();
for ( int i = 1 ; i <= qq ; i ++ ) q[i].pos = read() , q[i].id = i;
sort ( a + 1 , a + n + 1 , [](const edge &a , const edge &b) { return a.l < b.l; } );
sort ( q + 1 , q + qq + 1 , [](const query &a , const query &b) { return a.pos < b.pos; } );
int pos = 0 , now = 1;
for ( int i = 1 ; i <= qq ; i ++ )
{
pos = max ( pos , q[i].pos );
while ( a[now].l <= pos && now <= n ) pos = max ( pos , a[now].b ) , ++ now;
ans[q[i].id] = pos;
}
for ( int i = 1 ; i <= qq ; i ++ ) cout << ans[i] << ' ';
cout << endl;
}
return 0;
}
Cyclic Rotation
玄幻奇妙的题 思想是双指针
我们发现转 \(a\) 比较难 但是我们可以从终止状态 \(b\) 回推
从后往前贪心地匹配 如果遇到了一个不能匹配的点 那么如果 \(b[i]=b[i+1]\) 那么这两个点可以成为一个循环的起始和末尾 用 \(map\) 记录一下
然后继续贪心匹配 如果又遇到了一个不能匹配的点 如果 \(map\) 中有这个数 \(a[i]\) 那么相当于 \(b[i]\) 为起始的这个循环可以转回来 那么我们清除贡献即可
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 3e5 + 5;
const int inf = 0x3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , a[N] , b[N];
map<int,int> cnt;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
int T = read();
while ( T -- )
{
cnt.clear();
int flag = 1;
n = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
for ( int i = 1 ; i <= n ; i ++ ) b[i] = read();
int posa = n , posb = n;
b[n+1]=0;
while ( posb )
{
while ( posa && posb && a[posa] == b[posb] ) -- posa , -- posb;
// cout << posa << ' ' << posb << endl;
if ( posa == 0 || posb == 0 ) break;
if ( b[posb] == b[posb+1] ) cnt[b[posb--]] ++;
else if ( cnt[a[posa]] ) cnt[a[posa--]] --;
else { flag = 0; break; }
}
cout << ( flag ? "YES" : "NO" ) << endl;
}
return 0;
}
Integers Have Friends
转化题意:\([l,r]\) 合法当且仅当差分数组 \([l,r-1]\) 合法
证明:设 \(a_l\bmod m=a_{l+1}\bmod m=\dots=a_r\bmod m=x\)
那么有
- \(a_l-x=k_l\times m\)
- \(a_{l+1}-x=k_{l+1}\times m\)
- \(a_r-x=k_r\times m\)
也就是 \(a_l,a_{l+1},\dots,a_r\) 之间相差一定是 \(m\) 的整数倍
那么我们用 \(st\) 表维护区间 \(gcd\) 并双指针处理即可
注意记录答案时的 \(r-l+2\) 处理 因为我们 \([l,r]\) 合法当且仅当差分数组 \([l,r-1]\) 合法 所以在差分数组上进行操作的时候需要加一
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
#define int long long
const int N = 2e5 + 5;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , st[N][31] , maxx , a[N];
int query ( int l , int r )
{
int len = __lg ( r - l + 1 );
return __gcd ( st[l][len] , st[r-(1<<len)+1][len] );
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
int T = read();
while ( T -- )
{
maxx = 0;
n = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
for ( int i = 1 ; i < n ; i ++ ) st[i][0] = abs ( a[i+1] - a[i] );
for ( int j = 1 ; j <= 25 ; j ++ )
for ( int i = 1 ; i + ( 1 << j ) - 1 < n ; i ++ )
st[i][j] = __gcd ( st[i][j-1] , st[i+(1<<j-1)][j-1] );
int l = 1 , r = 0;
while ( r < n )
{
while ( l <= r && query ( l , r ) == 1 ) l ++;
maxx = max ( maxx , r - l + 2 ) , r ++;
//差分合法性见第一页最后一篇
//这里不是 r-l+1 的原因是 如果 [l,r] 区间合法 那么显然[l,r+1] 区间都是合法的(因为我们记录的是差分
}
cout << maxx << endl;
}
return 0;
}
P6465 [传智杯 #2 决赛] 课程安排
双指针确实十分多变()
先不考虑 \(l\) 的限制 看如何求出头尾不同 相邻不同的序列
先看相邻不同的限制 显然对于每一个 \(l\) 端点 离它最远的 \(r\) 端点都是单调不降的 那么我们考虑枚举这个 \(l\)
不能枚举 \(r\) 个人认为是对于 \(r\) 左端点 \(l\) 可能移动很大 比如 \(a[r]=a[r-1]\)
再看头尾不同的限制 我们维护一个桶 存储区间内某个 \(a_i\) 的出现次数 那么对于一个 \(l\) 合法区间即为 \(r-l+1-cnt_{a_l}-1\) (后面 \(-1\) 是因为自己不能和自己匹配)
再考虑 \(l\) 的限制 实质上是限制序列长度在 \([l,n]\) 之间 我们可以将长度为 \([1,l-1]\) 和 \([1,n]\) 的答案都算出来 作差即可
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
#define int long long
const int N = 1e6 + 5;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , lim , a[N] , l , r , tot , cnt[N] , res1 , res2;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
int T = read();
while ( T -- )
{
res1 = res2 = 0;
n = read() , lim = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , cnt[i] = 0;
l = 1 , r = 0;
while ( l <= n )
{
while ( l > r || r < n && a[r] != a[r+1] ) ++ cnt[a[++r]];
-- cnt[a[l]] , res1 += r - l + 1 - cnt[a[l]];
++ l;
}
l = 1 , r = 0;
while ( l <= n )
{
while ( l > r || r < n && a[r] != a[r+1] && r - l + 1 < lim - 1 ) ++ cnt[a[++r]];
-- cnt[a[l]] , res2 += r - l + 1 - cnt[a[l]];
++ l;
}
cout << res1 - res2 << endl;
}
return 0;
}
P1712 [NOI2016] 区间
双指针+线段树的转化
我们显然可以将区间按照长度排序来方便统计(因为题目中要求是最长的区间长度差值)
那么我们可以用双指针维护区间的选取 并用线段树维护覆盖的合法性
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 1e6 + 5;
const int inf = 0x3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , m , tot;
struct Tree
{
struct node { int maxx , add; } t[N<<2];
inl void pushmaxx ( int p , int val ) { t[p].maxx += val , t[p].add += val; }
inl void down ( int p ) { if ( t[p].add ) pushmaxx ( ls , t[p].add ) , pushmaxx ( rs, t[p].add ) , t[p].add = 0; }
inl void up ( int p ) { t[p].maxx = max ( t[ls].maxx , t[rs].maxx ); }
void upd ( int p , int l , int r , int x , int y , int val )
{
if ( x == 0 && y == 0 ) return;
if ( x <= l && r <= y ) return pushmaxx ( p , val );
down(p);
if ( x <= mid ) upd ( lson , x , y , val );
if ( mid + 1 <= y ) upd ( rson , x , y , val );
up(p);
}
}T;
vector<int> lsh;
struct edge { int l , r , len; } a[N];
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i].l = read() , a[i].r = read() , lsh.eb(a[i].l) , lsh.eb(a[i].r);
sort ( lsh.begin() , lsh.end() );
lsh.erase(unique(lsh.begin(),lsh.end()),lsh.end());
tot = lsh.size();
for ( int i = 1 ; i <= n ; i ++ ) a[i].len = a[i].r - a[i].l , a[i].l = lower_bound ( lsh.begin() , lsh.end() , a[i].l ) - lsh.begin() + 1 , a[i].r = lower_bound ( lsh.begin() , lsh.end() , a[i].r ) - lsh.begin() + 1;
sort ( a + 1 , a + n + 1 , [](const edge &a , const edge &b) { return a.len < b.len; } );
int l = 1 , r = 0 , ans = inf;
while ( r <= n )
{
while ( l <= r && T.t[1].maxx == m ) ans = min ( ans , a[r].len - a[l].len ) , T.upd ( 1 , 1 , tot , a[l].l , a[l].r , -1 ) , l ++;
++ r , T.upd ( 1 , 1 , tot , a[r].l , a[r].r , 1 );
}
cout << ( ans == inf ? -1 : ans ) << endl;
return 0;
}
Boring Segments
和上一题几乎一样 不同的是我们这次按照区间权值排序 而且因为需要保证两个区间之间有交集 那么区间端点需要 \(-1\)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
#define int long long
const int N = 1e6 + 5;
const int inf = 0x3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , m , tot , ans = inf;
struct edge { int l , r , w; } a[N];
struct Tree
{
struct node { int minn , add; } t[N<<2];
inl void pushminn ( int p , int val ) { t[p].add += val , t[p].minn += val; }
inl void down ( int p ) { if ( t[p].add ) pushminn ( ls , t[p].add ) , pushminn ( rs , t[p].add ) , t[p].add = 0; }
inl void up ( int p ) { t[p].minn = min ( t[ls].minn , t[rs].minn ); }
void upd ( int p , int l , int r , int x , int y , int val )
{
if ( x <= 0 && y <= 0 ) return;
// cout << p << ' ' << l << ' ' << r << ' ' << x << ' ' << y << ' ' << val << endl;
if ( x <= l && r <= y ) return pushminn ( p , val );
down(p);
if ( x <= mid ) upd ( lson , x , y , val );
if ( mid + 1 <= y ) upd ( rson , x , y , val );
up(p);
}
int query ( int p , int l , int r , int x , int y )
{
if ( x <= l && r <= y ) return t[p].minn;
int res = inf; down(p);
if ( x <= mid ) res = min ( res , query ( lson , x , y ) );
if ( mid + 1 <= y ) res = min ( res , query ( rson , x , y ) );
return res;
}
}T;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i].l = read() , a[i].r = read() , a[i].w = read();
sort ( a + 1 , a + n + 1 , [](const edge &a , const edge &b) { return a.w < b.w; } );
int l = 1 , r = 0;
while ( r <= n )
{
while ( l <= r && T.query ( 1 , 1 , m - 1 , 1 , m - 1 ) ) ans = min ( ans , a[r].w - a[l].w ) , T.upd ( 1 , 1 , m - 1 , a[l].l , a[l].r - 1 , -1 ) , l ++;
++ r , T.upd ( 1 , 1 , m - 1 , a[r].l , a[r].r - 1 , 1 );
}
cout << ans << endl;
return 0;
}
Quiz Master
前置知识:\(b\) 被 \(a\) 整除指的是 \(b\bmod a = 0\)
那么我们对于每一个数 求出它的因数 并开桶来统计 \(1-m\) 中的因数访问情况
双指针统计即可
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 1e5 + 5;
const int maxn = 1e5;
const int inf = 0x3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , m , cnt[N] , a[N] , ans = inf;
vector<int> vec[N];
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
for ( int i = 1 ; i <= maxn ; i ++ )
for ( int j = i ; j <= maxn ; j += i )
vec[j].eb(i);
int T = read();
while ( T -- )
{
n = read() , m = read();
ans = inf;
for ( int i = 1 ; i <= m ; i ++ ) cnt[i] = 0;
for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
sort ( a + 1 , a + n + 1 );
int l = 1 , r = 0 , tot = 0;
while ( r <= n )
{
while ( l <= r && tot == m )
{
ans = min ( ans , a[r] - a[l] );
for ( auto v : vec[a[l]] ) if ( v <= m ) tot -= ( -- cnt[v] == 0 );
++ l;
}
++ r;
for ( auto v : vec[a[r]] ) if ( v <= m ) tot += ( ++ cnt[v] == 1 );
}
cout << ( ans == inf ? -1 : ans ) << endl;
}
return 0;
}
Glider
大家写双指针好像都喜欢移动 \(l\) 指针,那就来一篇移动 \(r\) 指针的题解。
对于本题,移动区间显然有单调性,因为走越多的区间,高度一定不会上升,那么可以采用双指针来统计。
我们设 \(ll_i\) 为一段上升气流区间的左端点,\(rr_i\) 为右端点,\(l\) 为左指针,\(r\) 为右指针,\(tot\) 为当前的高度。
那么初始条件为 \(l=r=1\),\(tot=m\)。
当当前高度 \(\le0\) 的时候,尝试移动左指针,使得区间合法( \(tot\ge 0\) )。
否则计入答案并移动右指针,注意在统计完区间之后还要加上剩余的单调下降高度 \(tot\)。
#include<bits/stdc++.h>
using namespace std;
#define inl inline
#define eb emplace_back
#define pb pop_back
#define endl '\n'
#define mid (l+r>>1)
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define getchar() cin.get()
#define int long long
const int N = 2e5 + 5;
const int inf = 0x3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , m , ll[N] , rr[N] , ans;
vector<int> vec[N];
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read();
for ( int i = 1 ; i <= n ; i ++ ) ll[i] = read() , rr[i] = read();
int l = 1 , r = 1 , tot = m;
while ( r <= n )
{
while ( l <= r && tot <= 0 ) tot += ll[l+1] - rr[l], ++ l;
ans = max ( ans , rr[r] - ll[l] + tot );
tot -= ll[r+1] - rr[r]; ++ r;
}
cout << ans << endl;
return 0;
}