LINK

E - Magic Square(签到,模拟)

没什么好说的,模拟转转转就好了,自认为我写的还是比较简便的.

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5+10;
int t,n;
char a[101][101];
void rotate(int x,int y,int num)
{
	for(int i=1;i<=num;i++)
	{
		char q = a[x][y], w = a[x][y+1], e = a[x+1][y+1], r = a[x+1][y];
		a[x][y] = r, a[x][y+1] = q, a[x+1][y] = e, a[x+1][y+1] = w;
	}
}
int main()
{
	cin >> t;
	while( t-- )
	{
		cin >> n;
		for(int i=1;i<=3;i++)	cin >> ( a[i]+1 );
		while( n-- )
		{
			string s; cin >> s;
			int type = ( s[1]=='C' )?1:3;//C就是顺时针转1次,否则顺时针转三次 
			if( s[0]=='1' )	rotate( 1,1,type );
			else if( s[0]=='2' )	rotate( 1,2,type );
			else if( s[0]=='3' )	rotate( 2,1,type );
			else	rotate( 2,2,type );
		}
		for(int i=1;i<=3;i++)	cout << ( a[i]+1 ) << endl;
	}
}

J. Taotao Picks Apples(二分,ST表)

题意

给定一个数组 h h h,其中 h i h_i hi表示第 i i i个苹果的高度.

首先第一个苹果必选,然后选择第 i i i个苹果当且仅当 h i h_i hi大于之前选的所有苹果

现在给出 m m m个独立的询问,每次给定 p , q p,q p,q,问若把 h p h_p hp改成 q q q,可以拿到几个苹果?


做法

预处理 p r e [ i ] pre[i] pre[i]表示在原始序列中 [ 1 , i ] [1,i] [1,i]拿到苹果的最大高度, a n s [ i ] ans[i] ans[i]表示拿到的数量

这个 O ( n ) O(n) O(n)扫一遍就好了

还需要处理 s u f [ i ] suf[i] suf[i]表示如果第 i i i个苹果被拿走,在 [ i , n ] [i,n] [i,n]区间能拿走几个苹果

转移为 s u f [ i ] = s u f [ l a s ] + 1 suf[i]=suf[las]+1 suf[i]=suf[las]+1

其中 l a s > = i & & h l a s > h i las>=i\&\&h_{las}>h_i las>=i&&hlas>hi且满足 l a s las las是索引最小的

这个简单,我们可以在 [ i + 1 , n ] [i+1,n] [i+1,n]二分一个 m i d mid mid,每次询问 [ i + 1 , m i d ] [i+1,mid] [i+1,mid]的最大值是否大于 h i h_i hi即可

这样就能得到最小索引的 l a s las las,使用 S T ST ST表处理区间最大值可以在 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))处理 s u f suf suf数组

于是对于每个询问,分两种情况.

①.当 p r e [ p − 1 ] > = q pre[p-1]>=q pre[p1]>=q时,那么第 p p p个苹果其实没有被选中,此时需要找到 [ p + 1 , n ] [p+1,n] [p+1,n]第一个大于 p r e [ p − 1 ] pre[p-1] pre[p1] h l a s h_{las} hlas

答案为 a n s [ p − 1 ] + s u f [ l a s ] ans[p-1]+suf[las] ans[p1]+suf[las]

②.当 p r e [ p − 1 ] < q pre[p-1]<q pre[p1]<q时,那么第 p p p个苹果被选中,此时需要找到 [ p + 1 , n ] [p+1,n] [p+1,n]第一个大于 q q q h l a s h_{las} hlas

答案为 a n s [ p − 1 ] + s u f [ l a s ] + 1 ans[p-1]+suf[las]+1 ans[p1]+suf[las]+1

时间复杂度为 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5+10;
int T,n,m,h[maxn],p,q,pre[maxn],suf[maxn],ans[maxn],mx[maxn][22],lg[maxn];
int getmx(int l,int r)
{
	int k = lg[r-l+1];
	return max( mx[l][k],mx[r-(1<<k)+1][k] );
}
int getsuf(int now,int zhi)
{
	int l = now, r = n, ans = -1;
	while( r>=l )
	{
		int mid = l+r>>1;
		if( getmx(now,mid)>zhi )	r = mid-1, ans = mid;
		else	l = mid+1;
	}
	return ans;
}
int main()
{
	ios::sync_with_stdio( false );
	cin.tie( 0 ); cout.tie( 0 );
	for(int i=2;i<=200000;i++)	lg[i] = lg[i>>1]+1;
	cin >> T;
	while( T-- )
	{
		cin >> n >> m;
		for(int i=1;i<=n;i++)	cin >> h[i],mx[i][0] = h[i];
		for(int i=1;i<=20;i++)
		for(int j=1;j+(1<<i)-1<=n;j++)
			mx[j][i] = max( mx[j][i-1],mx[j+(1<<(i-1))][i-1] );
		for(int i=1;i<=n;i++)
		{
			if( h[i]>pre[i-1] )	pre[i] = h[i],ans[i] = ans[i-1]+1;
			else	pre[i] = pre[i-1], ans[i] = ans[i-1];
		}
		for(int i=n;i>=1;i--)
		{
			int hou = getsuf( i+1,h[i] );
			if( hou==-1 )	suf[i] = 1;
			else	suf[i] = suf[hou]+1;
		}
		while( m-- )
		{
			cin >> p >> q;
			if( pre[p-1]>=q )
			{
				int res = ans[p-1];
				int hou = getsuf( p+1,pre[p-1] );
				if( hou==-1 )	cout << res << endl;
				else	cout << res+suf[hou] << endl;
			}
			else//问题转化为[p,n]中以高度q开头能有的数量 
			{
				int res = ans[p-1]+1;
				int hou = getsuf(p+1,q);
				if( hou==-1 )	cout << res << endl;
				else	cout << res+suf[hou] << endl;
			}
		}
	}
}

A. Character Encoding(容斥,组合数学)

题意
[ 0 , n − 1 ] [0,n-1] [0,n1]中选 m m m个数(重复选,考虑顺序),求和为 k k k的方案数

n , m , k < = 1 0 5 n,m,k<=10^5 n,m,k<=105


正常写是不能够的,我们把 k k k看作 k k k 1 1 1分成 m m m份,每份大小小于等于 n − 1 n-1 n1

这样使用隔板法方案数是 ( k − 1 m − 1 ) \binom{k-1}{m-1} (m1k1)

但是这样分出来每组至少有 1 1 1 1 1 1,然后我们每份大小为 [ 0 , n − 1 ] [0,n-1] [0,n1]

不妨默认给每份加上 1 1 1,那么相当于把 k + m k+m k+m分成 m m m份大小为 [ 1 , n ] [1,n] [1,n]的数字

方案数是 ( k + m − 1 m − 1 ) \binom{k+m-1}{m-1} (m1k+m1),当然这其中有不合法的方案(有些数字会大于 n n n)

我们使用容斥原理,也就是减去有一组大于 n n n的,加上两组大于 n n n的,减去三组大于 n n n的…

答案是

( k + m − 1 m − 1 ) + ∑ i = 1 ( − 1 ) i ∗ ( m i ) ∗ ( k + m − 1 − n ∗ i m − 1 ) \binom{k+m-1}{m-1}+\sum\limits_{i=1}(-1)^i*\binom{m}{i}*\binom{k+m-1-n*i}{m-1} (m1k+m1)+i=1(1)i(im)(m1k+m1ni)

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 3e5+10;
const int mod = 998244353;
int n,m,k,fac[maxn],inv[maxn];
int quick(int x,int n)
{
	int ans = 1;
	for( ; n ; n>>=1,x=x*x%mod )
		if( n&1 )	ans = ans*x%mod;
	return ans;
}
int C(int n,int m)
{
	if( m>n )	return 0;
	return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
signed main()
{
//	ios::sync_with_stdio( false ); cin.tie( 0 ); cout.tie( 0 );
	fac[0] = 1; inv[0] = 1;
	for(int i=1;i<=300000;i++)
		fac[i] = fac[i-1]*i%mod, inv[i] = quick( fac[i],mod-2 );
	int t; cin >> t;
	while( t-- )
	{
		cin >> n >> m >> k;
		int ans = C(k+m-1,m-1);
		for(int i=1; ;i++)
		{
			if( k+m-1-n*i<m-1 )	break;
			if( i&1 )	ans = ( ans-C(m,i)*C(k+m-1-n*i,m-1)%mod )%mod;
			else	ans = ( ans+C(m,i)*C(k+m-1-n*i,m-1)%mod )%mod;
		}
		cout << ( ans%mod+mod )%mod << endl;
	}
}

D. Parentheses Matrix(神仙构造)

题意

括号矩阵的每个元素都是 ( ( ( ) ) )

括号矩阵的优秀度为符合合法括号序列的行数和列数

现在给定矩阵的长宽,构造一个最大优秀度的矩阵输出.


神仙题,想不到。

2018 Chinese Multi-University Training, Nanjing U Contest(多校第八场)_c++

I - Make ZYB Happy(广义SAM模板)

S A M SAM SAM的模板题

由于长度为 m m m的串包含长度为 [ 1 , m − 1 ] [1,m-1] [1,m1]的子串的贡献和

所以预处理一个 r e s [ i ] res[i] res[i]表示长度为 i i i的随机串的价值期望,做一遍前缀和即可

于是对 n n n个串建广义 S A M SAM SAM,然后对于每个字符串

遍历每个前缀节点,一直向上跳后缀链接,同时把这些节点的贡献乘上当前字符串的价值(设节点价值为 a n s i ans_i ansi)

当然要记得打标记,不然会重复算

于是对节点 u u u来说,自身包含的长度为 [ l f a u + 1 , l u ] [l_{fa_u}+1,l_u] [lfau+1,lu]的子串的价值都是 a n s u ans_u ansu

差分一下就好了

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10;
const int mod = 1e9+7;
string s[maxn];
int n,zhi[maxn],res[maxn],base[maxn];
int zi[maxn][27],fa[maxn],l[maxn],ans[maxn],col[maxn],las = 1, id = 1;
void insert(int c,int type,int szhi)
{
	int p = las, np = ++id; las = id;
	l[np] = l[p]+1; ans[np] = 1;
	for( ; p&&zi[p][c]==0;p=fa[p] )	zi[p][c] = np;
	if( p==0 )	fa[np] = 1;
	else
	{
		int q = zi[p][c];
		if( l[q]==l[p]+1 )	fa[np] = q;
		else
		{
			int nq = ++id;
			l[nq] = l[p]+1, fa[nq] = fa[q]; 
			col[nq] = col[q], ans[nq] = ans[q];
			memcpy( zi[nq],zi[q],sizeof zi[nq] );
			fa[np] = fa[q] = nq; ans[nq] = 1;
			for( ; p&&zi[p][c]==q;p=fa[p] )	zi[p][c] = nq;
		}
	}
}
void INSERT(int sid)
{
	int slen = s[sid].length();
	for(int i=0;i<slen;i++)	insert( s[sid][i]-'a',sid,zhi[sid] );
}
int quick(int x,int n)
{
	int ans = 1;
	for( ; n ; n>>=1,x=1ll*x*x%mod )
		if( n&1 )	ans = 1ll*ans*x%mod;
	return ans;
}
signed main()
{
	cin >> n;
	for(int i=1;i<=n;i++)	cin >> s[i];
	for(int i=1;i<=n;i++)	cin >> zhi[i];
	for(int i=1;i<=n;i++)	INSERT( i ), las = 1;
	
	for(int i=1;i<=n;i++)
	{
		int r = 1, ls = s[i].length();
		for(int j=0;j<ls;j++)
		{
			r = zi[r][s[i][j]-'a'];
			int x = r, type = i, szhi = zhi[i];
			while( x!=1 )
			{
				if( col[x]==type )	break;
				if( col[x]==0 )	ans[x] = szhi;
				else	ans[x] = 1ll*ans[x]*szhi%mod;
				col[x] = type;	x = fa[x];
			}
		}
	}	
	
	for(int i=2;i<=id;i++)
	{
		int L = l[fa[i]]+1, R = l[i]+1;
 		res[L] = ( res[L]+ans[i] )%mod; res[R] = ( res[R]-ans[i] )%mod;
	}
	for(int i=1;i<=1000000;i++)	res[i] = ( res[i]+res[i-1] )%mod;
	for(int i=1;i<=1000000;i++)	res[i] = ( res[i]+res[i-1] )%mod;
	base[0] = 1;
	for(int i=1;i<=1000000;i++)	base[i] = ( 1ll*base[i-1]*26 )%mod;
	for(int i=2;i<=1000000;i++)	base[i] = ( 1ll*base[i-1]+base[i] )%mod;	
	int m; cin >> m;
	for(int i=1;i<=m;i++)
	{
		int x; cin >> x;
		cout << ( 1ll*res[x]*quick( base[x],mod-2 )%mod+mod )%mod << endl;
	}
	return 0;
}

L.From ICPC to ACM(模拟贪心)

一共有k个月
i i i个月原材料价格为 c i c_i ci,制造一台电脑要花费一个原材料和 m i m_i mi元,当月最多生产 p i p_i pi台电脑客户需求 d i d_i di

当月卖出的原材料和电脑不收存储费,没用完的原材料和电脑可以留到下个月,但需要收取当月费用

i i i个月能存放 e i e_i ei台电脑价格为 E i E_i Ei,存放原材料容量无限价格为 R i R_i Ri

求满足这 k k k个月客户需求的最小成本


首先因为原材料可以无限存储,所以在第 i i i个月来说,最小的原材料代价其实是 min ⁡ k = 1 i { c k + ∑ j = k i − 1 R j } \min\limits_{k=1}^i\{c_k+\sum\limits_{j=k}^{i-1}R_j\} k=1mini{ck+j=ki1Rj}

既然原材料已经达到最小了,那就需要让之前存储的电脑的代价最小

不妨在之前的月份中,只要当月还能制造电脑就存入仓库

到了下个月若仓库大小不够,就弹出成本最大的那些电脑.

然后利用当月的最小代价原材料制作 p i p_i pi台电脑加入仓库,如果超过容量就弹出最大的那些电脑

为此我们需要知晓当前代价最小的电脑和最大的电脑,使用一个 m u l t i s e t < 电 脑 代 价 , 电 脑 数 量 > multiset<电脑代价,电脑数量> multiset<,>的二元组即可

但是如何衡量成本?毕竟每个月的电脑存储成本都不同,都需要动态变化.

没关系,我们假设每台电脑都是从第一个月开始存储的,装入第 i i i个月生产的电脑在价格上减去 ∑ j = 1 i − 1 E j \sum\limits_{j=1}^{i-1}E_j j=1i1Ej,这样可以保证相对成本不变,真正算成本的时候再加上第一个月到当前月的所有存储费

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 3e5+10;
const int inf = 0x3f3f3f3f;
multiset<pair<int,int> >s;
int n,c[maxn],R[maxn];//原材料费用,存储费用
int m[maxn],e[maxn],E[maxn];//配置电脑费用,当月存储电脑数,存储费用
int d[maxn],p[maxn]; //当月需求电脑数,当月可生产电脑数 
signed main()
{
	int T; cin >> T;
	while( T-- )
	{
		int ans = 0, num = 0;
		cin >> n;
		for(int i=1;i<=n;i++)	cin >> c[i] >> d[i] >> m[i] >> p[i];
		for(int i=1;i<n;i++)	cin >> e[i] >> R[i] >> E[i];
		for(int i=2;i<=n;i++)	c[i] = min( c[i],c[i-1]+R[i-1] );//当月最便宜材料费 
		int flag = 1, pre = 0;
		for(int i=1;i<=n;i++)
		{
			pre += E[i-1];//从第一个月累加下来的存储电脑费用
			s.insert( { c[i]+m[i]-pre,p[i] } );//先用原材料填满仓库
			num += p[i];
			if( num<d[i] ){ flag = 0; break; }//仍然不满足需求
			num -= d[i];
			auto it = s.begin();
			while( d[i]>(it->second) )//删掉代价最小的d[i]台机器
			{
				d[i] -= ( it->second );
				ans += ( it->second )*( pre+(it->first) );
				it = s.erase(it);	
			} 
			if( d[i] )
			{
				int x = it->first, y = it->second-d[i];
				ans += d[i]*( pre+x );
				s.erase( it ); s.insert( {x,y} );
			}
			if( num>e[i] )//仓库的电脑数大于库存了 
			{
				int dec = num-e[i]; num = e[i];
				auto it = s.end(); it--;
				while( dec>it->second )
				{
					dec -= it->second;
					it = s.erase( it ); it--;
				}
				if( dec )
				{
					int x = it->first, y = it->second-dec;
					s.erase( it ); s.insert( {x,y} );					
				}
			}
		}
		if( flag )	cout << ans << endl;
		else	cout << -1  << endl;
		s.clear();
	}
}