4.26 写题

A Yet Another LCP Problem

考虑反过来看串,如果建立前缀树,会发现这个 LCP 长度的问题就可以看成对于所有一类集合的点都到根全部 +1 ,对所有二类集合点求和。但是由于一般来说前缀树显然是建不出来的只能建出 parent 树,而如果建出 parent 树后,相当于这个点父亲到根的所有串全部 +1 ,然后需要特殊处理当前点。因此树剖显得挺难受,于是考虑建立虚树后 dfs 两遍做即可。细节可以参考代码。

复杂度 O(nlogn)O(n\log n) 对每个点上串的长度需要排序,以及建虚树时需要排序。

#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
#include "cmath"
#include "vector"
#include "map"
#include "set"
#include "queue"
#include "assert.h"
using namespace std;
#define MAXN 400406
//#define int long long
#define rep(i, a, b) for (int i = (a), i##end = (b); i <= i##end; ++i)
#define per(i, a, b) for (int i = (a), i##end = (b); i >= i##end; --i)
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define vi vector<int>
#define all(x) (x).begin() , (x).end()
#define mem( a ) memset( a , 0 , sizeof a )
typedef long long ll;
const int P = 1e9 + 7;
int n , q;

struct SAM {
	int son[MAXN][26] , par[MAXN] , len[MAXN] , cnt , lst;
	SAM( ) : cnt(1) , lst(1) {  }
	void ins( char a ) {
		int x = a - 'a' , cur = ++ cnt , p = lst;
		len[cur] = len[lst] + 1;
		while( p && !son[p][x] ) son[p][x] = cur , p = par[p];
		if( !p ) par[cur] = 1;
		else {
			int q = son[p][x];
			if( len[q] == len[p] + 1 ) par[cur] = q;
			else {
				int ct = ++ cnt;
				len[ct] = len[p] + 1 , par[ct] = par[q];
				memcpy( son[ct] , son[q] , sizeof son[q] );
				par[cur] = par[q] = ct;
				for( ; son[p][x] == q ; p = par[p] ) son[p][x] = ct;
			}
		}
		lst = cur;
	}
	vi G[MAXN];
	void ade( ) {
		rep( i , 2 , cnt ) G[par[i]].pb( i );
	}
	int dfn[MAXN] , R[MAXN] , clo;
	int g[MAXN][20] , dep[MAXN];
	void dfs( int u ) {
		dfn[u] = ++ clo;
		if( u == 1 ) dep[u] = 1;
		for( int v : G[u] ) {
			g[v][0] = u;
			for( int k = 1 ; k <= 18 ; ++ k ) if( g[g[v][k - 1]][k - 1] ) g[v][k] = g[g[v][k - 1]][k - 1]; else break;
			dep[v] = dep[u] + 1;
			dfs( v );
		}
		R[u] = clo;
	}
	int lca( int u , int v ) {
		if( dep[u] < dep[v] ) swap( u , v );
		if( dep[u] ^ dep[v] ) per( k , 18 , 0 ) if( dep[g[u][k]] >= dep[v] ) u = g[u][k];
		if( u == v ) return u;
		per( k , 18 , 0 ) if( g[u][k] != g[v][k] ) u = g[u][k] , v = g[v][k];
		return g[u][0];
	}
	int stk[MAXN] , tp , fa[MAXN];
	vi V[MAXN];
	ll sa[MAXN] , sw[MAXN] , bs[MAXN];
	vi lt[MAXN] , sb[MAXN];
	vector<ll> st[MAXN];
	ll as;
	void build( vi& poi ) {
		as = 0;
		poi.pb( 1 );
		sort( all( poi ) , [&]( int a , int b ) { return dfn[a] < dfn[b]; } );
		for( int i = poi.size() - 1 ; i > 0 ; -- i ) poi.pb( lca( poi[i] , poi[i - 1] ) );
		sort( all( poi ) , [&]( int a , int b ) { return dfn[a] < dfn[b]; } );
		poi.erase( unique( all( poi ) ) , poi.end() );
		tp = 0;
		for( int x : poi ) {
			while( tp && R[stk[tp]] < dfn[x] ) -- tp;
			if( stk[tp] ) {
				V[stk[tp]].pb( x );
				fa[x] = stk[tp];
			}
			stk[++ tp] = x;
			sort( all( lt[x] ) );
			st[x].resize( lt[x].size() );
			if( lt[x].size() ) st[x][0] = lt[x][0];
			for( int i = 1 ; i < lt[x].size() ; ++ i ) st[x][i] = st[x][i - 1] + lt[x][i];
		}
	}
	
	void wkr( int u ) {
		for( int v : V[u] ) {
			wkr( v );
			sa[u] += sa[v];
		}
		sw[u] = sw[u] + sa[u] * 1ll * ( len[u] - len[fa[u]] );
	}
	
	void qfs( int u , ll S ) {
		for( int x : sb[u] ) {
			int sm = lower_bound( all( lt[u] ) , x ) - lt[u].begin();
			as += ( x - len[fa[u]] ) * 1ll * ( lt[u].size() - sm );
			if( sm ) as += st[u][sm - 1];
			as += sa[u] * ( x - len[fa[u]] );
		}
		for( int v : V[u] ) {
			if( bs[v] ) as = ( as + S * bs[v] );
			qfs( v , S + sw[v] );
		}
	}
	
} S ;

char ch[MAXN];
int pos[MAXN];

void solve() {
	cin >> n >> q;
	scanf("%s",ch + 1);
	reverse( ch + 1 , ch + n + 1 );
	rep( i , 1 , strlen( ch + 1 ) )
		S.ins( ch[i] ) , pos[i] = S.lst;
	S.ade( );
	S.dfs( 1 );
	rep( i , 1 , q ) {
		int k , l , x , u;
		scanf("%d%d",&k,&l);
		vi pr , gr;
		rep( j , 1 , k ) {
			scanf("%d",&x);
			x = n - x + 1;
			u = pos[x];
			S.lt[u].pb( x );
			pr.pb( u );
			gr.pb( x );
		}
		rep( j , 1 , l ) {
			scanf("%d",&x);
			x = n - x + 1;
			u = pos[x];
			S.bs[u] ++;
			S.sb[u].pb( x );
			pr.pb( u );
		}
		S.build( pr );
		for( int x : gr ) 
			S.sw[pos[x]] += x - S.len[S.fa[pos[x]]] , S.sa[S.fa[pos[x]]] ++;
		S.wkr( 1 ) , S.qfs( 1 , 0 );
		printf("%lld\n",S.as);
		for( int x : pr ) S.V[x].clear() , S.sa[x] = S.sw[x] = S.bs[x] = 0 , S.lt[x].clear() , S.st[x].clear() , S.sb[x].clear();
	}
}

signed main() {
//    freopen("in","r",stdin);
//    freopen("fuckout","w",stdout);
//	int T;cin >> T;while( T-- ) solve();
	solve();
}

B Qanat

我们考虑当我们对 x1,x2,,xnx_1,x_2,\dots ,x_n 打洞,最后花费是啥。为了方便,设 x0=0,xn+1=wx_0 = 0,x_{n+1} = w 。显然对于 xi,xi+1x_i,x_{i+1} 之间的土,设洞的高度为 hih_i ,那么 hi+hi+1+xi+1xi2hi\frac{h_i+h_{i+1} + x_{i+1} - x_i}{2} - h_i 之前的部分会从左边运出去,剩下的会从右边运出去。我们设 $f(a) = \int_{0}^a x\ \text{d}x $ ,那么显然,总花费可以通过对于每两个相邻的洞,我们算出运出这两个洞以及中间的土需要的花费,再减去每个洞本身的花费再加上最后一个洞的花费得到的就是总花费。这三部分可以分开算,最后一部分是常量也就是 f(h)f(h) 对最大值时的取值不重要就不管了,于是式子就是:

i=1n+12f(hi+hi+1+xi+1xi2)f(hi)\sum_{i=1}^{n+1} 2f(\frac{h_i+h_{i+1} + x_{i+1} - x_{i}}{2}) - f(h_i)

我们设 k=hwk = \frac{h}{w} ,那么 hi=kxih_i = kx_i ,于是我们可以写出一个 nn 元函数。然后就可以对这个东西求偏导,考虑对 xix_i 求偏导,式子大概是:

i((k+1)xi+(k1)xi12)+((k1)xi+(k+1)xi+12)(kxi)22\sum_{i} \left(\frac{(k+1)x_i + (k-1)x_{i-1}}{2}\right) + \left(\frac{(k-1)x_i + (k+1)x_{i+1}}{2}\right) - \frac{\left(kx_i\right)^2} 2

剩下的都和 xix_i 无关。设 a=k+12,b=k12a = \frac{k+1}{2} , b = \frac{k-1}{2} ,最后可以得到一个关于 a,b,ka,b,k 的式子,大概是:

xi+1=k22a22b22abxixi1x_{i+1} = \frac{k^2-2a^2-2b^2}{2ab} x_i - x_{i-1}

然后我们设 x1=x,x0=0x_1 = x,x_0 = 0 ,可以得到 xnx_n 关于 xx 的式子,解出即可。

#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
#include "cmath"
#include "vector"
#include "map"
#include "set"
#include "queue"
using namespace std;
#define MAXN 200306
//#define int long long
#define rep(i, a, b) for (int i = (a), i##end = (b); i <= i##end; ++i)
#define per(i, a, b) for (int i = (a), i##end = (b); i >= i##end; --i)
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define vi vector<int>
#define all(x) (x).begin() , (x).end()
#define mem( a ) memset( a , 0 , sizeof a )
typedef long long ll;
int w , h , n;
int A[MAXN];

double S[MAXN];

double p2( double x ) {
	return x * x;
}

double s[MAXN];

void solve() {
	cin >> w >> h >> n;
	double k = 1. * h / w , a = ( k + 1 ) / 2 , b = ( k - 1 ) / 2;
	double t = ( k * k - 2 * a * a - 2 * b * b ) / ( a * b * 2 ) , q = -1;
	s[1] = 1;
	rep( i , 2 , n + 1 ) s[i] = s[i - 1] * t + q * s[i - 2];
	double x = 1. * w / s[n + 1];
	rep( i , 1 , n ) S[i] = s[i] * x;
	S[n + 1] = w;
	double as = 0;
	rep( i , 1 , n + 1 ) {
		as = ( as + p2( ( ( k + 1 ) * S[i] + ( k - 1 ) * S[i - 1] ) / 2 ) - p2( k * S[i] ) / 2 );
	}
	as += p2( k * S[n + 1] ) / 2;
	cout << as << endl;
	rep( i , 1 , min( 10 , n ) ) printf("%.10lf\n",S[i]);
}

signed main() {
//    int T;cin >> T;while( T-- ) solve();
    solve();
}

C 精准预测

我们考虑给每个时刻建出每个人的活点和死点,这样就可以类似 2-sat 来建边。但是这样复杂度不可接受。于是考虑只对每个人的关键时刻(也就是与它相连的边的时刻)来建点,这样点的个数就是 O(n+m)O(n+m) 了。

然后建点完成后考虑怎么算答案。我们可以对于每个人建立出 T+1T+1 时刻的活点然后从这个点跑一次 dfs ,看看可以到达哪些人的死点。如果它可以到达自己的死点,显然计算所有人的答案的时候它都不在贡献里,把它放入必死点。否则,答案显然就是所有点减去这个点能到的死点并上必死点的集合大小。可以发现这些集合操作都是可以 bitset 优化的。这样时间复杂度仅仅是 O(n(n+m)ω)O(\frac{n(n+m)}{\omega}) 可以接受,但是一算空间就炸了。所以可以考虑我们分块来做,类似 Ynoi 掉进兔子洞,每块分别做做完再合并即可,空间复杂度就可以接受了。

#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
#include "cmath"
#include "vector"
#include "map"
#include "set"
#include "queue"
#include "bitset"
#pragma GCC optimize("Ofast,unroll-loops")
using namespace std;
#define MAXN 500046
//#define int long long
#define rep(i, a, b) for (int i = (a), i##end = (b); i <= i##end; ++i)
#define per(i, a, b) for (int i = (a), i##end = (b); i >= i##end; --i)
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define vi vector<int>
#define all(x) (x).begin() , (x).end()
#define mem( a ) memset( a , 0 , sizeof a )
typedef long long ll;
int T , n , m;
int A[MAXN];

set<pii> poa[MAXN] , pod[MAXN];
int cnt;

int fd( int u , int t , int w ) { // w 1 Alive 0 Dead.
	if( w ) {
		auto it = poa[u].lower_bound( mp( t , 0 ) );
		if( it == poa[u].end() || it -> fi != t ) return -1;
		else return it -> se;
	} else {
		auto it = pod[u].lower_bound( mp( t , 0 ) );
		if( it == pod[u].end() || it -> fi != t ) return -1;
		else return it -> se;
	}
}

void gt( int u , int t ) {
	int x = fd( u , t , 1 );
	if( x != -1 ) return;
	poa[u].insert( mp( t , ++ cnt ) );
	pod[u].insert( mp( t , ++ cnt ) );
}

vi G[MAXN];

const int B = 13000;
bitset<B + 3> S[500006] , od;

int vis[MAXN];
void dfs( int u ) {
	if( vis[u] ) return;
	vis[u] = 1;
	for( int x : G[u] ) {
		dfs( x );
		S[u] |= S[x];
	}
}

int dd[MAXN] , al[MAXN] , res[MAXN] , fk[MAXN];

void solve() {
	cin >> T >> n >> m;
	rep( i , 1 , m ) {
		int c , t , x , y;
		scanf("%d%d%d%d",&c,&t,&x,&y);
		gt( x , t ) , gt( y , t + !c );
		if( c ) {
			G[fd( x , t , 1 )].pb( fd( y , t , 0 ) );
			G[fd( y , t , 1 )].pb( fd( x , t , 0 ) );
		} else {
			G[fd( x , t , 0 )].pb( fd( y , t + 1 , 0 ) );
			G[fd( y , t + 1 , 1 )].pb( fd( x , t , 1 ) );
		}
	}
	rep( i , 1 , n ) gt( i , T + 1 ) , dd[i] = pod[i].rbegin() -> se , al[i] = poa[i].rbegin() -> se;
	rep( i , 1 , n ) {
		int fr = pod[i].begin() -> se;
		for( auto t : pod[i] ) if( t.se != fr ) {
			G[fr].pb( t.se );
			fr = t.se;
		}
		fr = poa[i].rbegin() -> se;
		for( auto t = ++ poa[i].rbegin() ; t != poa[i].rend() ; ++ t ) {
			G[fr].pb( t -> se );
			fr = t -> se;
		}
	}
	for( int l = 1 ; l <= n ; l += B ) {
		int r = min( l + B - 1 , n );
		rep( i , l , r ) S[dd[i]].set( i - l );
		mem( vis );
		rep( i , 1 , n ) dfs( al[i] );
		od.reset( );
		rep( i , l , r ) 
			if( S[al[i]].test( i - l ) ) {
				od[i - l] = 1 , fk[i] = 1;
			}
		rep( i , 1 , n ) if( !fk[i] ) res[i] += ( r - l + 1 ) - ( od | S[al[i]] ).count();
		rep( i , 1 , cnt ) if( vis[i] ) S[i].reset();
	}
	rep( i , 1 , n ) printf("%d ",fk[i] ? 0 : res[i] - 1);
}

signed main() {
//	freopen("9.in","r",stdin);
//	freopen("ot","w",stdout);
//    int T;cin >> T;while( T-- ) solve();
    solve();
}

D AUOJ117 随机变量

不难发现,设 A(z)=i=0kpkzkA(z) = \sum_{i=0}^k p_kz^k ,然后只要求出 An(z)(modzx)A^n(z) \pmod {z^x} ,就可以算出答案(分别算最后得值小于和大于等于 xx 得情况)。

这个形式的式子是可以类似暴力多项式 exp\exp 来做的,设 G(z)=An(z)G(z) = A^n(z) ,那么我们对两边分别求导,得到

G(z)=nA(z)An1(z)G(z)A(z)=nA(z)An(z)G(z)A(z)=nA(z)G(z)tx,i+1+j=tztgiiaj=ni+1+j=taiigji=0tgiiati=ni=0tgiati(ti)i=0tgi[iatinati(ti)]=0\begin{aligned} G'(z) &= nA'(z) A^{n-1}(z)\\ G'(z)A(z) &= nA'(z) A^n(z)\\ G'(z)A(z) &= nA'(z)G(z)\\ \forall t \le x,\sum_{i+1+j=t} z^tg_iia_j &= n\sum_{i+1+j=t} a_iig_j\\ \sum_{i=0}^t g_iia_{t-i} &= n\sum_{i=0}^t g_i a_{t-i} (t-i)\\ \sum_{i=0}^t g_i \left[ia_{t-i} - na_{t-i} (t-i)\right] &= 0 \end{aligned}

最后得到的这个式子就可以 O(k)O(k) 递推了,因为 tit-i 的范围是 k\le k ,然后复杂度就是 O(xk)O(xk) 大概 5×1075\times 10^7

#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
#include "cmath"
#include "vector"
#include "map"
#include "set"
#include "queue"
using namespace std;
#define MAXN 200006
//#define int long long
#define rep(i, a, b) for (int i = (a), i##end = (b); i <= i##end; ++i)
#define per(i, a, b) for (int i = (a), i##end = (b); i >= i##end; --i)
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define vi vector<int>
#define all(x) (x).begin() , (x).end()
#define mem( a ) memset( a , 0 , sizeof a )
typedef long long ll;
const int P = 998244353;
int n , k , x;
int A[MAXN];
int as[10000006] , iv[10000006];

int Pow( int x , int a ) {
	int ret = 1;
	while( a ) {
		if( a & 1 ) ret = ret * 1ll * x % P;
		x = x * 1ll * x % P , a >>= 1;
	}
	return ret;
}

void solve() {
	cin >> n >> k >> x;
	int S = 0;
	rep( i , 0 , k ) scanf("%d",A + i) , S = ( S + A[i] ) % P;
	rep( i , 0 , k ) A[i] = A[i] * 1ll * Pow( S , P - 2 ) % P;
	iv[1] = 1;
	rep( i , 2 , x ) iv[i] = ( P - P / i ) * 1ll * iv[P % i] % P;
	as[0] = Pow( A[0] , n );
	int iv0 = P - Pow( A[0] , P - 2 );
	int res = 0 , s = as[0];
	rep( i , 1 , x ) {
		int sr = 0;
		rep( j , i - k , i - 1 ) 
			sr = ( sr + as[j] * 1ll * A[i - j] % P * ( j + P - n * 1ll * i % P + n * 1ll * j % P ) % P ) % P;
		as[i] = sr * 1ll * iv0 % P * iv[i] % P;
		s = ( s + as[i] ) % P;
		res = ( res + as[i] * 1ll * i ) % P;
	}
	res = ( res + ( 1 + P - s ) * 1ll * x ) % P;
	cout << res << endl;
}

signed main() {
//    int T;cin >> T;while( T-- ) solve();
    solve();
}
\