noip模拟43

A. 第一题

儿子遍历顺序按深度由小到大即可


B. 第二题

二分最小值,以点权作为初始距离跑最长路即可

直接用大根堆跑 \(dij\)\(T\),考虑初始权值可以处理,且边权一定,用类似蚯蚓的方法开两个队列,一开始都在第一个队列,松弛后的点放第二个,容易发现两个队列都是单调递减的,比较队头即可

也可以直接 \(spfa\) 到不能松弛为止,由于图的特殊性不会更新很多次

代码实现
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=1e7+5;
const int maxm=5e7+5;
int n,m,k,val[maxn],dis[maxn],hd[maxn],cnt;
bool vis[maxn];
int movx[10]={0,0,-1,-1,1,1};
int movy[10]={-1,1,0,1,-1,0};
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*f;
}
struct Edge{
	int nxt,to;
}edge[maxm];
void add(int u,int v){
	edge[++cnt].nxt=hd[u];
	edge[cnt].to=v;
	hd[u]=cnt;
	return ;
}
struct Node{
	int dis,id;
	Node(){}
	Node(int x,int y):dis(x),id(y){}
	bool operator < (const Node &x)const{
		return dis>x.dis;
	}
}p[maxn];
queue<int>q1,q2;
bool check(int limit){
	for(int i=1;i<=n*m;i++){
		dis[p[i].id]=p[i].dis;
		q1.push(p[i].id);
	}
	memset(vis,0,sizeof vis);
	while((!q1.empty())||(!q2.empty())){
		int x=0,y=0,u=0;
		if(!q1.empty())x=q1.front();
		if(!q2.empty())y=q2.front();
		if(dis[x]>dis[y]){
			q1.pop();
			u=x;
		}
		else q2.pop(),u=y;
		//cout<<"hhh "<<u<<" "<<dis[u]<<endl;
		if(vis[u])continue;
		
		vis[u]=true;
		for(int i=hd[u];i;i=edge[i].nxt){
			int v=edge[i].to;
			if(dis[v]<dis[u]-limit){
				dis[v]=dis[u]-limit;
				//if(!vis[v])
				q2.push(v);
				//,vis[v]=true;
			}
		}
	}
	int sum=0;
	for(int i=1;i<=n*m;i++){
		sum+=dis[p[i].id]-p[i].dis;
		if(sum>k)return false;
	}
	return true;
}
bool pd(int x,int y){
	return x>=1&&x<=n&&y>=1&&y<=m;
}
int id(int x,int y){
	return (x-1)*m+y;
}
signed main(){
//	freopen("s.out","r",stdin);
//	freopen("my.out","w",stdout);
	n=read();
	m=read();
	k=read();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			val[id(i,j)]=read();
			for(int l=0;l<6;l++){
				int x=i+movx[l];
				int y=j+movy[l];
				if(pd(x,y))add(id(i,j),id(x,y));
			}
		}
	}
	for(int i=1;i<=n*m;i++){
		p[i].dis=val[i];
		p[i].id=i;
	}
	sort(p+1,p+n*m+1);
	dis[0]=-1;
//	for(int i=1;i<=n*m;i++)cout<<p[i].dis<<" "<<p[i].id<<endl;;
	int l=0,r=1000000000000;
	while(l<r){
		int mid=(l+r)/2;
		if(check(mid))r=mid;
		else l=mid+1;
	}
	cout<<l;
	return 0;
}

C. 第三题

\(1\) 的个数从大到小依次考虑即可,用数位 \(dp\) 计算个数和总和
这道题是同时卡上下边界的数位 \(dp\)


D. 第四题

\(f[i][j]\) 表示前 \(i\) 个数,最大值为 \(j\) 的方案数,转移 \(f[i][j]=f[i-1][j]*j+f[i-1][j-1]\)

\(g[i][j]\) 表示从第 \(i\) 个数开始到结尾,开头初始最大值为 \(j\) 的方案数,转移 \(g[i][j]=g[i+1][j]*j+g[i+1][j+1]\)

观察答案形式,由于 \(\displaystyle x^2=\binom{x}{2}*2+1\)
那么强制一个数在数列里出现两次

\(j\)\(i\) 位置第一次出现的贡献为:

\[f[i-1][j-1]*(g[i+1][j]+2*(n-i)*g[i+2][j])+\sum_{k>=j}f[i-1][k]*(g[i+1][k]+2*(n-i)*g[i+2][k]) \]

即讨论上一个数的大小即可

然后用前缀和优化为 \(n^2\)

代码实现
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=3005;
int n,mod,f[maxn][maxn],g[maxn][maxn],ans,sum2[maxn],sum1[maxn][maxn],sum[maxn][maxn];
signed main(){
	cin>>n>>mod;
	f[0][0]=1;
//	for(int i=0;i<=n;i++)f[0][i]=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			f[i][j]=(f[i-1][j-1]+f[i-1][j]*j%mod)%mod;
//			cout<<f[i][j]<<" ";
		}
	}
//	g[n+1][n]=g[n+1][n]=1;
	for(int i=1;i<=n;i++)g[n+1][i]=1;
	for(int i=n;i>=1;i--){
		for(int j=n;j>=1;j--){
			g[i][j]=(g[i+1][min(j+1,n)]+g[i+1][j]*j%mod)%mod;
		}
	}
//	cout<<g[5][5]<<endl;
	for(int i=1;i<=n;i++){
		for(int j=n;j>=1;j--){
			sum[i][j]=(sum[i][j+1]+f[i-1][j]*g[i+1][j]%mod)%mod;
			sum1[i][j]=(sum1[i][j+1]+g[i+2][j]*f[i-1][j]%mod)%mod;
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			ans=0;
			ans=(sum[i][j]+2*(n-i)%mod*sum1[i][j]%mod)%mod;
//			for(int k=j;k<=n;k++){
//				ans=(ans+f[i-1][k]*(g[i+1][k]+2*(n-i)%mod*g[i+2][k]%mod)%mod)%mod;
//			}
			ans=(ans+f[i-1][j-1]*(g[i+1][j]+2*(n-i)%mod*g[i+2][j]%mod)%mod)%mod;
			sum2[j]=(sum2[j]+ans)%mod;
		}
	}
	for(int i=1;i<=n;i++)cout<<sum2[i]<<" ";
	return 0;
}
posted @ 2021-08-18 19:02  y_cx  阅读(116)  评论(0编辑  收藏  举报