2022牛客多校联赛加赛 题解

比赛传送门
作者: fn

目录

  • 签到题
    • M题 Maimai DX 2077
    • H题 Here is an Easy Problem of Zero-chan / 一个简单的后缀零问题
  • 基本题
    • E题 Everyone is bot / 人人都是复读机
    • J题 Jellyfish and its dream / 水母和它的梦想
  • 进阶题
    • K题 Killer Sajin's Matrix / 杀手萨金的矩阵


签到题

M题 Maimai DX 2077

题目大意
有个音游叫 Maimai DX 2077。
这个音游有四种音符,每种音符有五个判定。
不同音符的不同判定会获得不同基础分数。
绝赞的判定单独计算分数。
给定一次游玩每种音符每种判定的数量,问达成度。

考察内容
模拟

分析
将判定的表作为 4 × 5 数组输入程序。
读入时直接计算 A , A 0 , B , B 0 A, A_0,B,B_0 A,A0,B,B0 即可。

#pragma GCC optimize(3)
#include
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
int t;
int n,m;
int a[5][10];
double b[5][10] = {{0,0,0,0,0},{0,1.0,1.0,0.8,0.5,0},{0,2.0,2.0,1.6,1.0,0},{0,3.0,3.0,2.4,1.5,0},{0,5.0,5.0,2.5,2,0}};
using namespace std;
int main(){ // AC 
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	double suma = 0;
	double sumb = 0;
	double cnt1 = 0;
	double cnt2 = 0;
	for(int i = 1;i<=4;i++){
		int sum = 0;
		for(int j = 1;j<=5;j++){
			cin>>a[i][j];
			sum+=a[i][j];
		}
		cnt1+=sum*b[i][1];
	}
	for(int i = 1;i<=4;i++){
		for(int j = 1;j<=5;j++){
			suma+=a[i][j]*b[i][j];
		}
	}
    sumb = a[4][1]*1.0+a[4][2]*0.5+a[4][3]*0.4+a[4][4]*0.3+a[4][5]*0.0;
	cnt2 = (a[4][1]+a[4][2]+a[4][3]+a[4][4]+a[4][5])*1.0;
	
	double ans = suma/cnt1*100+sumb/cnt2; // 完成度 
	printf("%.9f",ans);
}

H题 Here is an Easy Problem of Zero-chan / 一个简单的后缀零问题

题目大意
有一颗 n 个节点且以 1 为根的有根树。
第 i 个点的点权为 i。
多次查询编号为 x 的点,
∏ i = 1 n l c a ( i , x ) ∏^n_{i=1} lca(i, x) i=1nlca(i,x) 的末尾有多少个零。

考察内容
dfs序,差分

分析
末尾零的个数即因子中2的个数和5的个数取min。

要求多次询问,所以考虑预处理出答案。

预处理子树大小 s z [ i ] sz[i] sz[i] ,预处理dfs序。遍历每一个结点,更新答案的时候对子树进行区间更新,也就是在dfs序上差分。

复杂度 O ( n ) O(n) O(n)

#include
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N = 1e5+5;
int n, m;

vector<int> g[N];

ll num2[N];
ll num5[N];
ll sz[N]; // 子树大小 
ll dfs_[N],len; // dfs序 
ll pos[N]; // 指向dfs序中的位置 

ll c2[N],c5[N]; // dfs序的答案的差分 

ll dfs2(int x,int fa){ // 预处理sz 
	sz[x]=1; // 自己算1个 
	for(auto a1:g[x]){
		if(a1==fa)continue;
		
		sz[x]+=dfs2(a1,x);
	}
	return sz[x];
}
void dfs3(int u,int fa){ // 求dfs序 
    dfs_[++len]=u;  
    int sz=g[u].size();
    for(int i=0;i<sz;i++){
        if(g[u][i]!=fa){
            dfs3(g[u][i],u);
        }
    }
}

void dfs4(int x,int fa){ // 更新x的答案 
	ll d=x; 
	ll cnt2=0,cnt5=0; 
	while(d%2==0){
		cnt2++;
		d/=2;
	}
	while(d%5==0){
		cnt5++;
		d/=5;
	}	
	c2[pos[x]]+=cnt2*sz[x];
	c2[pos[x]+1]-=cnt2*sz[x]; // 单点更新 
		
	c5[pos[x]]+=cnt5*sz[x];
	c5[pos[x]+1]-=cnt5*sz[x];
	
	
	for(auto a1:g[x]){ // 枚举子树,更新子树 
		if(a1==fa)continue;
		
		ll r=pos[a1]+sz[a1]-1; // dfs序上的右边界 
		ll val=sz[x]-sz[a1]; // 除了这个子树的大小 
		
		c2[pos[a1]]+=cnt2*val;
		c2[r+1]-=cnt2*val;
		
		c5[pos[a1]]+=cnt5*val;
		c5[r+1]-=cnt5*val;
	}
	
	for(auto a1:g[x]){
		if(a1==fa)continue;
		
		dfs4(a1,x);
	}
}

int main(){ // dfs序+差分
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for(int i=1; i<=n-1; i++){
        int a, b; cin >> a >> b;
        g[a].push_back(b);
        g[b].push_back(a);
    }

    for(int i=0;i<=n;i++){
    	num2[i]=num5[i]=0; // 初始化 
    }
    
    dfs2(1,0); // 预处理子树大小sz 
	
	len=0; 
	dfs3(1,0); // 求dfs序 

	for(int i=1;i<=n;i++){
		pos[dfs_[i]]=i; // i指向dfs序中pos[i]的位置 
	} 
	
	dfs4(1,0); // 更新答案 
	
	dfs_[0]=0; 
	
	for(int i=1;i<=n;i++){
		num2[i]=num2[i-1]+c2[i]; // 对c作前缀和 
		num5[i]=num5[i-1]+c5[i];
	}
	  
    for(int i=1; i<=m; i++){
        int x; cin>>x;
	 	cout<<min(num2[pos[x]],num5[pos[x]])<<endl;   
    }
}
/*
5 5
2 3
5 4
2 5
1 5
1 2 3 4 5

*/

基本题

E题 Everyone is bot / 人人都是复读机

题目大意

有 n 个人打算在群里复读。一次复读的过程如下:
每一轮,n 个人按照编号从小到大依次选择是否复读,每个人最多只会复读一次。

如果某一轮没有人进行复读,那么复读的过程结束。

对于第 i 个人,如果他是所有人中第 j 个进行复读的,他会获得 ai,j 瓶冰红茶。
但是如果他是所有进行了复读的人当中倒数第 p 个进行复读的人,那么他会被禁言,他不会获得任何冰红茶,并且倒扣 154 杯冰红茶。

每个人都想最大化自己获得的冰红茶数量,求最终每个人各自拿到多少冰红茶。

考察内容
博弈论

分析

例1
输入:

5 2
1000 4 3 2 1
4 1000 3 2 1
4 3 1000 2 1
4 3 2 1000 1
4 3 2 1 1000

举例, p = 2 p=2 p=2
首先,轮到每个人要拿的时候必须立刻选择,所以每个人只能拿自己那行的主对角线上的格子。

假设所有人全部拿格子,则最后两人(第4,第5人)必然要等对方先拿,自己再拿,这样对方禁言,自己没事。结果就是最后两个人“打架”,都不拿,只有前3个人拿。
前3个人拿格子,同理,最后两人(第2,第3人)必然要等对方先拿,自己再拿,结果就是两个人都拿不了,一轮轮空复读直接结束。
所以只剩下第一个人拿。第一个人可以方向拿,因为已经分析过了,2345都不敢拿。
拿到格子的人数是 n − p − p = 5 − 2 − 2 = 1 n-p-p=5-2-2=1 npp=522=1 n n n % p = 1 p=1 p=1

例2
输出:

1000 0 0 0 0

输入:

5 3
1000 4 3 2 1
4 1000 3 2 1
4 3 1000 2 1
4 3 2 1000 1
4 3 2 1 1000

p = 3 p=3 p=3 时,同理。
最后3个人“打架”,前面两个人把对角线拿掉。
拿到格子的人数是 n − p = 5 − 3 = 2 n-p=5-3=2 np=53=2 n n n % p = 2 p=2 p=2

输出:

1000 1000 0 0 0

显然可以推广。

得出结论:前 n n n % p p p 个人可以拿。所有答案是主对角线左上角前 n n n % p p p 个,后面的人都拿不了,直接输出0。

#include
#define ll long long
#define int long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1005;
ll n,m,p,a[N][N];

signed main(){ // 
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n>>p;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>a[i][j];
		}
	}

	int h=n%p;
	for(int i=1;i<=h;i++){ // 
		if(i==n)cout<<a[i][i]; // 拿斜对角线 
		else cout<<a[i][i]<<' ';
	}
	for(int i=h+1;i<=n;i++){
		if(i==n)cout<<0;
		else cout<<0<<' ';
	}
	return 0;
}

J题 Jellyfish and its dream / 水母和它的梦想

题目大意
给一个序列,值域为 { 0 , 1 , 2 } \{0,1,2\} {0,1,2}
如果 ( a i + 1 ) m o d    3 = a i + 1 m o d    n (a_i + 1) \mod 3 = a_{i+1 \mod n} (ai+1)mod3=ai+1modn (取模意义下加一等于后一个数),就可以将 a i a_i ai 赋值为 ( a i + 1 ) m o d    3 (a_i + 1) \mod 3 (ai+1)mod3(后一个数)。

问有限次操作后是否能使所有元素均相等。
1 ≤ ∑ n ≤ 1 0 6 1 ≤∑n ≤ 10^6 1n106

考察内容
单调栈

分析

解法不唯一。

看作一个单调栈,序列 a a a 中的元素依次入栈。每次入栈之前把栈顶相同的或者小1的元素pop出来。

最后再把整个序列入栈一遍,处理一下环形就可以。

#include
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e6+10;
ll n,m,a[N];
int len=0;

int main(){ // 单调栈 
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int t; cin>>t;
	while(t--){
		len=0; 
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>a[i]; // 0,1,2
		}
		
		deque<ll> b;
		
		ll n2=n;
		
		for(int i=n;i>=2;i--){
			if(a[i]==a[1] || (a[i]+1)%3==a[1]){
				n2=i-1;
			}
			else{
				break;
			}
		}
		
		for(int i=1;i<=n;i++){
			if(i==1){
				len++;
				b.push_back(a[i]);
			}	
			else{ // i>=2
				while(len>=1 && (b.back()==a[i] || (b.back()+1)%3==a[i])){
					len--;
					b.pop_back();
				}
				len++;
				b.push_back(a[i]);
			}
		}
		
		
		for(int i=1;i<=n;i++){ 
			if(len>=2){ 
				ll t1=a[i]; // 

				while(len>=1 && (b.back()==t1 || (b.back()+1)%3==t1)){
					len--;
					b.pop_back();
				}
				len++;
				b.push_back(t1);
			}
			else{ // len<=1
				break;
			}
		} 
		
		if(len<=1)cout<<"Yes"<<endl;
		else cout<<"No"<<endl;
	}
	return 0;
}
/*
1
6
2 0 2 1 0 1

*/ 

进阶题

K题 Killer Sajin’s Matrix / 杀手萨金的矩阵

题目大意
在一个大小为 n ∗ m n * m nm 的二维网格中放置 k k k 个 1,使该网格的每一行和每一列的和均为奇数。

考察内容
构造

分析
2022牛客多校联赛加赛 题解_第1张图片
2022牛客多校联赛加赛 题解_第2张图片
p.s. 赛后数据有加强。

#include
using namespace std;
int n,m,k,l,t,r,stx[100010],sty[100010],stc;
bool b;

int main(){ // 构造 
	scanf( "%d", &t );
	while ( t-- ){
		scanf( "%d%d%d", &n, &m, &k );
		r = max( n, m ); // 大的一边 
		l = min( n, m ); // 小的一边 
		long long sup = ( (long long) n) * m - ( (r & 1) ? 0 : r);
		if ( ( (n & 1) ^ (m & 1) ) || ( (r & 1) ^ (k & 1) ) || k < r || k > sup || ( (r & 1) && k == ( (long long) n) * m - 2ll) ){
			printf( "No\n" ); // 方案不存在 
			continue;
		}
		
		printf( "Yes\n" ); // 方案存在 
		if ( k == r + 2 ){
			for ( int i = 1; i <= l - 3; ++i )
				printf( "%d %d\n", i, i );
			printf( "%d %d\n%d %d\n%d %d\n%d %d\n%d %d\n", l, l - 1, l - 1, l, l - 1, l - 1, l - 2, l - 1, l - 1, l - 2 );
			for ( int i = l + 1; i <= r; ++i )
				printf( "%d %d\n", b ? n : i, b ? i : m );
			continue;
		}
		
		
		b = (n <= m);
		for ( int i = 1; i <= l; ++i )
			printf( "%d %d\n", i, i );
		for ( int i = l + 1; i <= r; ++i )
			printf( "%d %d\n", b ? n : i, b ? i : m );
		k	-= r;
		stc	= 0;
		for ( int i = l + 1; k && (k ^ 6) && i <= r; i += 2 )
			for ( int j = 1; k && (k ^ 6) && j < l - 1; j += 2 ){
				k		-= 4;
				stx[stc]	= b ? j : i;
				sty[stc++]	= b ? i : j;
			}
		for ( int i = 2; k && (k ^ 6) && i <= l; i += 2 )
			for ( int j = 1; k && (k ^ 6) && j < i; j += 2 )
				if ( (i - j) ^ j ){
					k		-= 4;
					stx[stc]	= j;
					sty[stc++]	= i - j;
				}
		for ( int i = ( (l - 2) & 131070); k && (k ^ 6) && i > 0; i -= 2 )
			for ( int j = (l & 131070) - i + 1; k && (k ^ 6) && j < l; j += 2 )
				if ( ( ( (l >> 1) << 2) - i - j) ^ j ){
					k		-= 4;
					stx[stc]	= j;
					sty[stc++]	= ( (l >> 1) << 2) - i - j;
				}
		if ( k == 6 ){
			printf( "%d %d\n%d %d\n%d %d\n%d %d\n%d %d\n%d %d\n", l, l - 1, l - 1, l, l, l - 2, l - 2, l, l - 1, l - 2, l - 2, l - 1 );
			k = 0;
		}
		if ( k % 6 == 2 ){
			--stc;
			k += 4;
		}
		else if ( k % 6 ){
			stc	-= 2;
			k	+= 8;
		}
		
		for ( int i = 0; i < stc; ++i ){
			printf( "%d %d\n%d %d\n%d %d\n%d %d\n", stx[i], sty[i], stx[i]+1, sty[i], stx[i], sty[i]+1, stx[i]+1, sty[i]+1 );
		}
		for ( int i = 1; k; i += 2 ){
			k -= 6;
			printf( "%d %d\n%d %d\n%d %d\n%d %d\n%d %d\n%d %d\n", i + 1, i, i, i + 1, i, l, l, i, i + 1, l, l, i + 1 );
		}
	}
	return(0);
}

你可能感兴趣的:(多校联赛,算法,c++,acm,icpc,算法竞赛)