海亮 7.4 数据结构1
Hailiang 7.4 数据结构
P2827 [NOIP2016 提高组] 蚯蚓
\(80pts\)做法显然 直接模拟并输出即可 注意每次要将标记的值积累到数值中 \(priority\_queue\)也需要按照实际值排序
加了特判后\(85pts\)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , m , k , u , v , t , cntt , flag , add;
double p;
struct node { int val , del; friend bool operator < ( const node a , const node b ) { return a.val + add - a.del < b.val + add - b.del; } };
priority_queue<node>q;
priority_queue<int>qq;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read() , k = read() , u = read() , v = read() , t = read();
p = (double)u / v;
if ( k == 0 )
{
for ( int i = 1 ; i <= n ; i ++ ) qq.push ( read() );
for ( int i = 1 ; i <= m ; i ++ )
{
int temp = qq.top(); qq.pop();
if ( i % t == 0 ) cout << temp << ' ' ;
qq.push ( (floor)( temp * p ) );
qq.push ( temp - (floor)( temp * p ) );
}
cout.put('\n') , cntt = 0;
while ( !qq.empty() ) { cntt ++; if ( cntt % t == 0 ) cout << qq.top() << ' '; qq.pop(); }
exit(0);
}
for ( int i = 1 ; i <= n ; i ++ ) q.push ( { read() , 0 } );
for ( int i = 1 ; i <= m ; i ++ )
{
node temp = q.top(); q.pop();
temp.val = temp.val + add - temp.del;
if ( i % t == 0 ) cout << temp.val << ' ' ;
add += k;
q.push ( { (floor)( temp.val * p ) , add } );
q.push ( { temp.val - (floor)( temp.val * p ) , add } );
}
cout.put('\n') , cntt = 0;
while ( !q.empty() ) { cntt ++; if ( cntt % t == 0 ) cout << q.top().val + add - q.top().del << ' '; q.pop(); }
return 0;
}
\(100pts\)
一个非常经典的优化:
去掉优先队列 我们将
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N = 1e7 + 5;
const int inf = 0x3f3f3f3f;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , m , k , u , v , t , ha , hb , hc , ta , tb , tc , delta , qa[N] , qb[N] , qc[N] , a[N];
double p;
int get_max()
{
int x = -inf;
if ( ha <= ta ) x = max ( x , qa[ha] );
if ( hb <= tb ) x = max ( x , qb[hb] );
if ( hc <= tc ) x = max ( x , qc[hc] );
if ( ha <= ta && x == qa[ha] ) ha ++;
else if ( hb <= tb && x == qb[hb] ) hb ++;
else if ( hc <= tc && x == qc[hc] ) hc ++;
return x;
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read() , k = read() , u = read() , v = read() , t = read();
p = (double)u / v;
for ( int i = 1 ; i <= n ; i ++ ) qa[i] = read();
sort ( qa + 1 , qa + n + 1 , greater<int>() );
ha = hb = hc = 1 , ta = n;
for ( int i = 1 ; i <= m ; i ++ )
{
int x = get_max() + delta;
if ( i % t == 0 ) cout << x << ' ';
int pp = (floor)(x * p);
delta += k;
qb[++tb] = pp - delta;
qc[++tc] = x - pp - delta;
}
cout << endl;
for ( int i = 1 ; i <= n + m ; i ++ )
{
int x = get_max() + delta;
if ( i % t == 0 ) cout << x << ' ';
}
return 0;
}
Messenger Simulator
我们用双端队列维护整个序列 用\(pos[i]\)维护每一个颜色的位置
可以发现最靠前的位置要么是1要么是起始位置 那么直接在提到队列前面的时候进行维护即可
现在不好维护的是最靠后的位置 那么我们可以开一个双端队列 每一个位置上有值即为1 无值即为0 跑一次树状数组统计前缀和即可得出该点的当前排名
最后跑一遍全图来更新每个点的最末排名
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N = 3e5 + 5;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , m , maxx[N] , minn[N] , head = N + 1 , tail = N , q[N*2] , t[N] , pos[N];
//对于每一个数 只可能在退出队列的时候取到最小值 在入队列的时候取到最大值
int lowbit ( int x ) { return x & (-x); }
void add ( int x , int val ) { for ( int i = x ; i <= N * 2 ; i += lowbit(i) ) t[i] += val; }
int query ( int x ) { int res = 0; for ( int i = x ; i ; i -= lowbit(i) ) res += t[i]; return res; }
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read();
for ( int i = 1 ; i <= n ; i ++ ) maxx[i] = minn[i] = i , pos[i] = ++tail , add ( pos[i] , 1 );
for ( int i = 1 ; i <= m ; i ++ )
{
int k = read();
minn[k] = 1;
maxx[k] = max ( maxx[k] , query ( pos[k] ) );
add ( pos[k] , -1 );
add ( --head , 1 );
pos[k] = head;
}
for ( int i = 1 ; i <= n ; i ++ )
maxx[i] = max ( maxx[i] , query ( pos[i] ) );
for ( int i = 1 ; i <= n ; i ++ ) cout << minn[i] << ' ' << maxx[i] << endl;
return 0;
}
New Year and Conference
首先 题意为:
一个人开\(n\)场会议 每一场会议都要在\(a,b\)两个地方 每一场会议都在这两个地方有起始和终止时间
那么对于任意两场会议 如果这两场会议的\(a\)会议区间是重合的 那么\(b\)会议区间是重合的 否则如果\(a\)区间不重合 \(b\)区间也要保证不重合
题中的问题是询问有没有特例 有输出\(NO\) 否则输出\(YES\)
\(maxsegtree\) 中维护的是区间内第\(i\)个讲座的\(b\)的开始点的最大值 \(minsegtree\)中维护的是区间内第\(i\)个讲座的\(b\)的结束点的最小值
重点解释\(check\)函数中的判断:
\(lower\_bound\) 查找到了一个与第\(i\)场演讲冲突的位置\(pos\)(此时\(pos\)与\(i\)演讲的左端点是冲突的)
那么此时在后面的\((i+1,pos)\)中(这些区间是和\(i\)区间左冲突的) 如果所有区间的\(b\)区间有一个(只要有一个就行)和\(i\)演讲的\(b\)没有冲突
如果没有冲突 那么直接返回(\(a\)区间不冲突\(b\)区间冲突的情况已经在第二种情况判断完成了)
具体体现在:
- 所有区间的结束点最小值小于这个区间的开始节点(此时这个区间结束点最小值的区间的右端点小于\(i\)的\(b\)区间的左端点 这个区间必然在\(i\)的\(b\)区间左面 所以这两个区间一定不冲突)
- 所有区间的起始点最大值大于这个区间的终止节点(此时这个区间起始点最大值的区间的左端点大于\(i\)的\(b\)区间的右端点 这个区间必然在\(i\)的\(b\)区间右面 所以这两个区间一定不冲突)
那么直接返回\(no\)
做两遍的目的: 因为你第一遍只保证了左面冲突的时候右面一定冲突
所以将两个区间交换之后就可以保证右面冲突的时候左面一定冲突
这样就可以保证正确性
#include <bits/stdc++.h>
using namespace std;
#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
const int inf = 0x3f3f3f3f;
const int N = 1e6 + 5;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , m , ans;
struct node { int sa , ea , sb , eb; friend bool operator < (const node a , const node b) { return a.sa == b.sa ? a.ea < b.ea : a.sa < b.sa; } } a[N];
int tmaxx[N] , tminn[N];
void build ( int p , int l , int r )
{
if ( l == r ) return tmaxx[p] = a[l].sb , tminn[p] = a[l].eb , void();
build ( lson ) , build ( rson );
tmaxx[p] = max ( tmaxx[ls] , tmaxx[rs] ) , tminn[p] = min ( tminn[ls] , tminn[rs] );
}
int querymax ( int p , int l , int r , int x , int y )
{
if ( x <= l && r <= y ) return tmaxx[p];
int maxx = -inf;
if ( x <= mid ) maxx = max ( maxx , querymax ( lson , x , y ) );
if ( mid + 1 <= y ) maxx = max ( maxx , querymax ( rson , x , y ) );
return maxx;
}
int querymin ( int p , int l , int r , int x , int y )
{
if ( x <= l && r <= y ) return tminn[p];
int minn = inf;
if ( x <= mid ) minn = min ( minn , querymin ( lson , x , y ) );
if ( mid + 1 <= y ) minn = min ( minn , querymin ( rson , x , y ) );
return minn;
}
int check ()
{
sort ( a + 1 , a + n + 1 );
build ( 1 , 1 , n );
for ( int i = 1 ; i <= n ; i ++ )
{
int pos = lower_bound ( a + 1 , a + n + 1 , (node) { a[i].ea , inf , 0 , 0 } ) - a - 1;
if ( i + 1 > pos ) continue;//没有重合区间
if ( querymin ( 1 , 1 , n , i + 1 , pos ) < a[i].sb || querymax ( 1 , 1 , n , i + 1 , pos ) > a[i].eb ) return 0;
}
return 1;
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i].sa = read() , a[i].ea = read() , a[i].sb = read() , a[i].eb = read();
if ( !check() ) { cout << "NO" << endl; return 0; }
for ( int i = 1 ; i <= n ; i ++ ) swap ( a[i].sa , a[i].sb ) , swap ( a[i].ea , a[i].eb );
if ( !check() ) { cout << "NO" << endl; return 0; }
cout << "YES" << endl;
return 0;
}
P6489. 生日礼物
连续正数合并 连续负数合并 再按照绝对值排序 用优先队列从小到大维护
每次取出一个小值 并将"正负正"类型的合并并取得 将"负正负"类型的合并并抛弃
选出正数的意义是不选这个正数 选出负数的意义是合并两边的正数 如果负数在最左面或者最右面 那么选这个负数不会合并两边的正数 所以一定抛弃
具体实现:
- 如果取出了一个值不在最边上 那么无论是上面的哪一种情况都要将它删去(如果是"正负正" 那么删去这个小值之后 我们会保留两个边上的大值 如果是"负正负" 那么这个正数一定要删去(因为当前正的连续段数一定大于\(m\) 删去它不会不满足答案
- 如果这个值在最边上 那么正数直接抛弃 负数也抛弃 且当前的块数不会递减
如果取出一个负数且在边上 那么直接抛弃
维护相邻关系用链表即可
坑点1:\(q.push\)需要在\(a[i]>0\)的判断条件外面写
坑点2:注意边界 \(tot\)和\(n\)要分清
坑点3:注意在输入分段的时候可能会输入0 需要加特判 否则不能用\(last\)来记录上一个的值
否则会将一段"负-0-正"区间连接起来
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N = 1e6 + 5;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , m , last , sum , a[N] , mk[N] , lst[N] , nxt[N] , tot = 1 , ans;
struct node { int id , val; friend bool operator < ( const node &a , const node &b ) { return abs(a.val) > abs(b.val); } };
priority_queue<node> q;
void del ( int x )
{
mk[x] = 1;
lst[nxt[x]] = lst[x];
nxt[lst[x]] = nxt[x];
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read();
for ( int i = 1 ; i <= n ; i ++ )
{
int val = read();
if ( val * a[tot] >= 0 ) a[tot] += val;
else a[++tot] = val;
}
for ( int i = 1 ; i <= tot ; i ++ )
{
if ( a[i] > 0 ) sum ++ , ans += a[i];
nxt[i] = i + 1 , lst[i] = i - 1 , q.push ( { i , a[i] } );
}
while ( sum > m )
{
sum --;
while ( mk[q.top().id] ) q.pop();
int x = q.top().id; q.pop();
if ( nxt[x] != tot + 1 && lst[x] != 0 ) ans -= abs(a[x]);//注意这里是tot+1不是n+1
else if ( a[x] > 0 ) ans -= abs(a[x]);
else { sum ++; continue; }
a[x] += a[nxt[x]] + a[lst[x]];
del(lst[x]) , del(nxt[x]);
q.push ( { x , a[x] } );
}
cout << ans << endl;
return 0;
}
Lexicographically Small Enough
我们首先枚举\(len\) 对于每一个\(len\) 如果它作为答案的话 那么我们一定要保证前\(len-1\)个都相同 第\(len\)个不同 那么我们可以计算这个东西
第\(len\)位不同可以直接用\(vector\)维护位置 那么对于保证前\(len-1\)个位置相同 我们可以用树状数组维护这个下标和\(len\)的位置差
每一次记录\(temp\)为右面的最近的和\(b[i]\)相同的位置 那么我们对于\(temp\)到\(n\)进行树状数组的值减一 因为\(len\)向右移动了\(1\)而这些值并没有移动 查下标时直接用前缀和即可
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mkp make_pair
#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 int long long
const int N = 1e5 + 5;
const int inf = 9e18;
inline int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , q , t[N] , a[N] , b[N];//我们开一个t[i]数组实时维护它和当前len的差
vector<int> v[27];
int lowbit ( int x ) { return x & (-x); }
void upd ( int x , int val ) { for ( int i = x ; i <= n ; i += lowbit(i) ) t[i] += val; }
int query ( int x ) { int res = 0; for ( int i = x ; i ; i -= lowbit(i) ) res += t[i]; return res; }
char ch;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
int T = read();
while ( T -- )
{
memset ( t , 0 , sizeof t );
for ( int i = 0 ; i <= 25 ; i ++ ) while ( !v[i].empty() ) v[i].pop_back();
n = read();
for ( int i = 2 ; i <= n ; i ++ ) upd ( i , 1 );
for ( int i = 1 ; i <= n ; i ++ ) cin >> ch , a[i] = ch - 'a';
for ( int i = 1 ; i <= n ; i ++ ) cin >> ch , b[i] = ch - 'a';
for ( int i = n ; i ; i -- ) v[a[i]].push_back(i);
int ans = inf , now = 0;
for ( int i = 1 ; i <= n ; i ++ )
{
int tmp = inf;
for ( int j = 0 ; j <= b[i] - 1 ; j ++ ) if ( !v[j].empty() ) tmp = min ( tmp , v[j].back() );
if ( tmp <= n ) ans = min ( ans , now + query(tmp) );
if ( v[b[i]].empty() ) break;
tmp = v[b[i]].back();
v[b[i]].pop_back();
now += query(tmp);
upd ( tmp , -1 );
}
cout << ( ans == inf ? -1 : ans ) << endl;
}
return 0;
}
P7078 [CSP-S2020] 贪吃蛇
- 如果最长蛇吃掉最短的蛇不是最短的蛇 那么第二条蛇就一定会保全自己 就可以放心吃
- 如果最长蛇吃掉最短的蛇是最短的蛇 那么看第二条蛇
- 如果下面的第三条蛇吃最短的蛇不是最短的 那么它一定会保全自己选择不吃 所以第二条蛇会放心吃
- 否则继续套娃 注意到第一条蛇的选择和套娃的层数的奇偶性有关