海亮 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\)区间冲突的情况已经在第二种情况判断完成了)

具体体现在:

  1. 所有区间的结束点最小值小于这个区间的开始节点(此时这个区间结束点最小值的区间的右端点小于\(i\)\(b\)区间的左端点 这个区间必然在\(i\)\(b\)区间左面 所以这两个区间一定不冲突)
  2. 所有区间的起始点最大值大于这个区间的终止节点(此时这个区间起始点最大值的区间的左端点大于\(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. 生日礼物

连续正数合并 连续负数合并 再按照绝对值排序 用优先队列从小到大维护

每次取出一个小值 并将"正负正"类型的合并并取得 将"负正负"类型的合并并抛弃

选出正数的意义是不选这个正数 选出负数的意义是合并两边的正数 如果负数在最左面或者最右面 那么选这个负数不会合并两边的正数 所以一定抛弃

具体实现:

  1. 如果取出了一个值不在最边上 那么无论是上面的哪一种情况都要将它删去(如果是"正负正" 那么删去这个小值之后 我们会保留两个边上的大值 如果是"负正负" 那么这个正数一定要删去(因为当前正的连续段数一定大于\(m\) 删去它不会不满足答案
  2. 如果这个值在最边上 那么正数直接抛弃 负数也抛弃 且当前的块数不会递减

如果取出一个负数且在边上 那么直接抛弃

维护相邻关系用链表即可

坑点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] 贪吃蛇

  1. 如果最长蛇吃掉最短的蛇不是最短的蛇 那么第二条蛇就一定会保全自己 就可以放心吃
  2. 如果最长蛇吃掉最短的蛇是最短的蛇 那么看第二条蛇
    • 如果下面的第三条蛇吃最短的蛇不是最短的 那么它一定会保全自己选择不吃 所以第二条蛇会放心吃
    • 否则继续套娃 注意到第一条蛇的选择和套娃的层数的奇偶性有关
posted @ 2023-07-07 17:33  Echo_Long  阅读(16)  评论(0编辑  收藏  举报