10.14模拟赛 10.14 模拟赛

抱歉咕了一天,前一天晚自习搞oj去了。

A 序列

开始看错题了1h20分钟。。。。一场爆炸。。

枚举开头是0还是1,就已经确定了每个数字到哪里(这个贪心显然)

然后我们考虑怎样让字典序变小,我们注意到只有同往一个方向走,且可以第一次的可以覆盖第二次的起点的时候可以任意交换。可以拿一个set或者pq维护一下。

真的毒瘤。。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<queue>
#include<stack>
#include<bitset>
#include<set>
using namespace std;
#define int long long
typedef long long ll;
#define MAXN 400006 
#define pb push_back
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define inf 0x3f3f3f3f
#define cmx( a , b ) a = max( a , b )
#define cmn( a , b ) a = min( a , b )
#define upd( a , b ) ( a = ( a + b ) % P )
//#define P 1000000007

void read( signed& x ) {
	scanf("%d",&x);
}
void read( ll& x ) {
	scanf("%lld",&x);
}
int n;
int A[MAXN];
int p[MAXN], po[MAXN], val[MAXN], ok[MAXN], ans[MAXN], ress[2][MAXN];

namespace orzjk {
	int book[MAXN];
	priority_queue<int> q;
	set<int> s;
	void work(int l, int r, int t) {
		if( t ) {
			for (int i = r; i >= l; i--) { s.insert( p[i] ); while (*s.rbegin() > po[i]) s.erase(*s.rbegin()); book[i] = *s.rbegin(); }
			for (int i = r, cur = r; i >= l; i--) { while (cur >= l && book[cur] >= p[i]) q.push(val[po[cur]]), cur--; ok[i] = q.top(), q.pop(); }
		} else {
			for (int i = l; i <= r; i++) { s.insert( p[i] ); while (*s.begin() < po[i]) s.erase(s.begin()); book[i] = *s.begin(); }
			for (int i = l, cur = l; i <= r; i++) { while (cur <= r && book[cur] <= p[i]) q.push(-val[po[cur]]), cur++; ok[i] = -q.top(), q.pop(); }
		}
	} 
}

int doit(int tp) {
	int ct = 0; int res = 0;
	for (int i = 1; i <= n; i++) if (A[i]) ct++, p[ct] = 2 * ct - 1 + tp, po[ct] = i, res += abs(p[ct] - po[ct]);
	if (p[ct] > n) return 0x3f3f3f3f3f3f3f3f;
	for (int i = 1, r; i <= ct; i = r + 1) {
		r = i;
		if (po[i] == p[i]) { ans[po[i]] = val[po[i]]; continue; }
		int t = p[i] < po[i];
		while (r < ct && po[r + 1] != p[r + 1] && t == (p[r + 1] < po[r + 1])) ++ r;
		orzjk::work(i, r, t);
		for (int j = i; j <= r; j++) ans[p[j]] = ok[j];
	}
	return res;
}

int rua(int tp) {
	int res = 0;
	for (int i = 1; i <= n; i++) A[i] = val[i] & 1;
	res += doit(tp);
	for (int i = 1; i <= n; i++) A[i] ^= 1;
	res += doit(tp ^ 1);
	return res;
}

signed main() {
	read( n );
	for (int i = 1; i <= n; i++) read( val[i] );
	int t1 = rua( 0 ); 
	memcpy(ress[0], ans, sizeof(ans));
	int t2 = rua( 1 ); 
	memcpy(ress[1], ans, sizeof(ans));
	if (t1 > t2) swap(ress[0], ress[1]);
	for (int i = 1; i <= n; i++) 
		printf("%lld ", ress[0][i]);
}

B 灯泡

场上思路:

考虑异或差分的形式

那么每次对某个颜色进行修改其实就是把所有这个颜色的位置以及这个颜色的位置的后一个位置异或1。

然后分颜色大于根号和小于根号处理。

  1. 如果某颜色点数量小于根号,直接暴力处理。

  2. 如果某颜色点数大于根号,我们维护的其实是这个颜色的点中有多少个1

    那么更改这个颜色的时候会对其他块造成影响

    • 大颜色对小颜色的影响:

      我们考虑每一个会被影响的位置。首先,每一个位置最多只会被一个大块影响(因为修改只影响后一个),那么我们可以标记这一个大块,在统计小块的时候要判一下标记。为了统计答案,我们还要维护大块所影响的小块的1的个数和总的数量。

    • 大颜色对大颜色的影响:

      每两个大颜色之间我们维护g x y表示x这个大颜色影响的点中是y的个数以及1的个数。必须满足一个点本身是大块,且前一个位置也是大块才会对g有贡献。

然而这个做法很难写也可能有锅,所以还是看solution吧


solution

把灯泡看成一个个点。

如果 $ i , i+ 1 $ 都是亮的,我们就可以对它们连一条边。那么一个区间就是一个连通块。由于链也是树,所以连通块的数量是点数 - 边数。

然后考虑翻转的是大于根号还是小于根号。小于根号直接做,大于根号的情况:

  • 计算大块对大块的影响

    可以预处理大块和大块间可以连多少边。然后操作时枚举大块就好了。

  • 计算大块对小块的影响

    暴力翻转小块的时候把小块周围的大块打标记,然后就可以计算翻转大块的影响了

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 200006
#define MAXM 456
int n , q , k , blo;
int lar[MAXN] , pre[MAXN];
vector<int> col[MAXN]; int smalc[MAXN] , sz[MAXN] , mark[MAXN] , G[MAXM][MAXM] , g[MAXN] ;
int idlar[MAXN] , bac[MAXN];
bool light[MAXN];
int A[MAXN] , L[MAXN] , en , res = 0 , cur = 0;
int main() {
//	freopen("bulb2.in","r",stdin);
//	freopen("out","w",stdout);
	cin >> n >> q >> k;
	blo = sqrt( n );
	for( int i = 1 ; i <= n ; ++ i ) scanf("%d",&A[i]) , col[A[i]].push_back( i );
	for( int i = 1 ; i <= k ; ++ i ) {
		if( col[i].size() >= blo )  {
			for( auto& j : col[i] ) lar[j] = i;
			L[++en] = i;
			idlar[i] = en , bac[en] = i;
		}
		else smalc[i] = 1;
		sz[i] = col[i].size();
	}
	for( int i = 1 ; i <= n ; ++ i ) if( lar[i] && lar[i + 1] && lar[i] != lar[i + 1] ) 
		++ G[idlar[lar[i]]][idlar[lar[i + 1]]] , ++ G[idlar[lar[i + 1]]][idlar[lar[i]]];
	for( int i = 1 ; i <= n ; ++ i ) if( lar[i] && lar[i] == lar[i + 1] )
		++ g[lar[i]];
	int u;
	while( q-- ) {
		scanf("%d",&u);
		if( smalc[u] ) {
			if( !light[u] ) {
				cur += sz[u];
				light[u] = 1;
				for( auto i : col[u] ) {
					if( A[i - 1] != u && light[A[i - 1]] ) ++ res;
					if( light[A[i + 1]] ) ++ res;
					if( lar[i - 1] ) ++ mark[A[i - 1]];
					if( lar[i + 1] ) ++ mark[A[i + 1]];
				}
			} else {
				cur -= sz[u];
				for( auto i : col[u] ) {
					if( A[i - 1] != u && light[A[i - 1]] ) -- res;
					if( light[A[i + 1]] ) -- res;
					if( lar[i - 1] ) -- mark[A[i - 1]];
					if( lar[i + 1] ) -- mark[A[i + 1]];
				}
				light[u] = 0;
			}
		} else {
			if( !light[u] ) {
				cur += sz[u] , res += g[u];
				light[u] = 1;
				for( int i = 1 ; i <= en ; ++ i ) if( L[i] != u ) {
					int co = L[i];
					if( light[co] ) res += G[idlar[u]][idlar[co]];
				}
				res += mark[u];
			} else {
				cur -= sz[u] , res -= g[u];
				light[u] = 0;
				for( int i = 1 ; i <= en ; ++ i ) if( L[i] != u ) {
					int co = L[i];
					if( light[co] ) res -= G[idlar[u]][idlar[co]];
				}
				res -= mark[u];
			}
		}
		printf("%d
",cur - res);
	}
}
/*
9 10 3
2 2 2 1 3 3 3 1 3

*/

C 比赛

一个神仙的推式子dp

用 $ F[i][j] $ 表示 $ i $ 个人存在这样的 $ j $ 个胜利者的概率。怎么转移呢?

由于编号 1 ~ n 其实只是个相对大小,可以考虑加入一个编号小于所有当前编号的人,那么就有式子:

$ F[i][j] = F[i - 1][j - 1] imes p^{i - j} + F[i-1][j] imes (1 - p)^{j} $

大概可以多项式优化,但是我不会(,其实也有更简单的做法

然后再考虑如果把一个编号大于所有当前编号的人,那么就有式子:

$ F[i][j] = F[i - 1][j - 1] imes ( 1 - p ) ^ {i - j} + F[i - 1][j] imes p^j $

根据定义,这两个式子应该是一摸一样的,写一起再移个项可以有

$ F[i][j] imes ( (1 - p) ^ j - p ^j ) = F[i][j - 1] imes ( (1 - p)^{i-j+1} - p^{i - j + 1} ) $

然后可以愉快递推了。 复杂度 $ O(n) $

同时还要注意,当 p 和 1 - p 相同这个式子是错的。因为 0 x = 0

特判掉这种情况,这个情况下下标没用,于是就变成了选择 $ k $ 个人并且它们打败了所有的人。

神仙思路,我是想不到,但是以后可以注意一下。

#pragma GCC optimize( 3 )
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<queue>
#include<stack>
#include<bitset>
using namespace std;
#define int long long
typedef long long ll;
#define MAXN 1000006 
#define pb push_back
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define sz size
#define inf 0x3f3f3f3f
#define cmx( a , b ) a = max( a , b )
#define cmn( a , b ) a = min( a , b )
#define upd( a , b ) ( a = ( a + b ) % P )
#define P 998244353
int n , m;
int a , b , p;
int A[MAXN];

void read( signed& x ) {
	scanf("%d",&x);
}
void read( ll& x ) {
	scanf("%lld",&x);
}

int power( int x , int a ) {
	int cur = x % P , ans = 1;
	while( a ) {
		if( a & 1 ) ans *= cur , ans %= P;
		cur *= cur , cur %= P , a >>= 1;
	}
	return ans;
}
int k;
int J[MAXN];
signed main() {
	read( n ) , read( a ) , read( b );
	p = power( b , P - 2 ) * a % P;
	int cur = 1 , x = 1 , ans = 0;
	if( p == 499122177 ) {
		J[0] = 1;
		for( int i = 1 ; i <= n ; ++ i ) J[i] = J[i - 1] * i % P;
		for( int m = 1 ; m < n ; ++ m ) {
			cur = ( J[n] * power( J[m] * J[n - m] % P , P - 2 ) % P * power( power( 2 , m * ( n - m ) ) , P - 2 )) % P;
			ans += x * cur , ans %= P;
			x = x * x % P + 2 , x %= P; 
		}
		cout << ans << endl;
		return 0;
	}
	for( int i = 1 ; i < n ; ++ i ) {
		cur *= ( ( power( 1 - p + P , n - i + 1 ) - power( p , n - i + 1 ) + P ) % P * ( power( power( 1 - p + P , i ) - power( p , i ) + P , P - 2 ) % P ) ) % P;
		cur %= P;
		ans = ( ans + x * cur ) % P;
		x = x * x % P + 2 , x %= P;
	}
	cout << ans << endl;
}