暑假集训CSP提高模拟2

A.活动投票

主元素问题,用摩尔投票

#include<bits/stdc++.h>
using namespace std;
int n,a=-1,acnt,x;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i){
		scanf("%d",&x);
		if(acnt==0){
			a=x;
			acnt++;
		}
		else if(a==x){
			acnt++;
		}
		else{
			acnt--;
			if(acnt==0) a=-1;
		}
	}
	printf("%d",a);
}

B.序列

mei ting dong

C.Legacy

赛时打的时候想可能是 DIJ 会假,结果 DIJ 没假,但是建图意料之中的 T 了.

这道题的建图非常有特色,区间建图的话,虽然不知道怎么建,但是确实是应该想到线段树.

考虑建分层图,然后把目标区间用线段树拆成节点,因为目标区间可以是出边也可以是入边,所以分两层,然后在每层叶节点连边权为零的边,这样建图复杂度只有 \(log\). 然后在树上跑 DIJ 就行了

关于 SPFA

  • 它死了

D.DP搬运工1

题解里说了,考虑设一个 \(f_{i,j,k}\) 做 DP

但是我觉得题解里的说法不是很清楚,我对这个 DP 的理解是,假设我们现在有一堆格子需要填,用 \(i\) 来表示已经填入的数字数量,\(j\) 来表示目前的空缺总数(注意这里的 “空缺”,一个空格组成的联通块称作一个 “空缺”,这么定义是因为比较好转移),\(k\) 用来表示当前填入数字后的 \(\max\) 总和

我们考虑以下情况(用 \(S\) 表示一个空缺,\(x,y\) 分别表示数字)

xSy

在我们将 \(S\) 填上的时候,假设我们每次都填一个更大的值,显然,填上之前的和是 \(a+b\),之后的和是 \(2k\),显然这么做会很麻烦,因为我们在填的时候还要考虑先把 \(a+b\) 减掉. 因此我们可以想到,与其先加上 \(a+b\) 再减掉,还不如直接不统计这个 \(a+b\) 了,因此我们直接在最后填上的时候再加上边界的贡献,这样就会比较简单.

因此,枚举五种情况:填在外面并合并,填在外面并新建连通块,填在里面并左右合并,填在里面并左或右合并,填在里面并新建联通块. 可以分别得到连通块贡献 \(0,1,-1,0,1\)\(\max\) 求和贡献 \(i,0,2i,i,0\),并且注意情况 \(1,2,4\) 是左右均可的合并方式,方案数贡献需要乘二

初始化 \(f[1][0][0]=1\)

\[f_{i,j,k+i}=2f_{i-1,j,k} \]

\[f_{i,j,k}=2f_{i-1,j-1,k} \]

\[f_{i,j,k}=f_{i-1,j-1,k} \]

\[f_{i,j,k+i}=2f_{i-1,j,k} \]

\[f_{i,j,k+2i}=f_{i-1,j+1,k} \]

统计答案 \(f[n][0][i]\)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=998244353;
int f[51][51][2501];
int n,kk;
signed main(){
	cin>>n>>kk;
	f[1][0][0]=1;
	for(int i=2;i<=n;++i){
		for(int j=0;j<=n-i+1;++j){
			for(int k=0;k<=kk;++k){
				if(!f[i-1][j][k]) continue;
				if(j){
					f[i][j][k+i]+=2*f[i-1][j][k]*j%p;
					f[i][j+1][k]+=f[i-1][j][k]*j%p;
					f[i][j-1][k+2*i]+=f[i-1][j][k]*j%p;
				}
				f[i][j][k+i]+=2*f[i-1][j][k]%p;
				f[i][j+1][k]+=2*f[i-1][j][k]%p;
			}
		}
	}
	int ans=0;
	for(int i=0;i<=kk;++i){
		ans+=f[n][0][i];
		ans%=p;
	}
	cout<<ans;
}
posted @ 2024-07-19 20:18  HaneDaniko  阅读(28)  评论(0编辑  收藏  举报