NOIP模拟95(多校28)

T1 嗑瓜子

解题思路

\(f_{i,j}\) 表示操作 \(i\) 次,拿走了 \(j\) 个瓜子的概率,转移就比较直接了:

\[f_{i+1,j+1}\leftarrow f_{i,j}\times\dfrac{n-j}{n+2\times j-i} \]

\[f_{i+1,j}\leftarrow f_{i,j}\times\dfrac{3\times j-i}{n+2\times j-i} \]

这里如果边界卡不准的话可能会出现使分母出现负数,注意一下,不然就会 RE 获得 50pts 的高分。。。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=2e3+10,mod=998244353;
int n,ans,f[N*3][N],inv[N*3];
void add(int &x,int y){x+=y;if(x>=mod)x-=mod;}
int Inv(int x){if(x>=0)return inv[x];return 0;}
#undef int
int main()
{
	#define int long long
	freopen("eat.in","r",stdin); freopen("eat.out","w",stdout);
	n=read(); f[0][0]=1; inv[1]=1;
	for(int i=2;i<=3*n;i++) inv[i]=inv[mod%i]*(mod-mod/i)%mod;
	for(int i=0;i<3*n-2;i++)
		for(int j=0;j<=min(i,n-1);j++)
			add(f[i+1][j+1],f[i][j]*(n-j)%mod*Inv(n+2*j-i)%mod),
			add(f[i+1][j],f[i][j]*(3*j-i)%mod*Inv(n+2*j-i)%mod);
	for(int i=n;i<=3*n-2;i++) add(ans,f[i][n]*i%mod);
	printf("%lld",ans);
	return 0;
}

T2 第 k 大查询

解题思路

在区间 \([l,r]\)\(s_i\) 是 k 大值的情况当且仅当 \([l,r]\) 中有 \(k-1\) 个大于 \(s_i\) 的数字。

那么我们就可以维护一下每一个数字前面比 \(s_i\) 大的 k 个值以及后面比 \(s_i\) 大的 k 个值,然后指针扫一遍就好了。

那么问题就变成的如何维护一个数前面以及后面的比他大的值,我们可以选择 双向链表 来实现。

对于值域上面按照原序列顺序建一个链表,然后从小往大计算值的贡献,每次计算完之后直接删掉这个值就可以了。

code

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=5e5+10;
int n,m,t1,t2,a[N],b[N],s[N],id[N],fro[N],nxt[N];
ll ans;
#undef int
int main()
{
	#define ll long long
	freopen("kth.in","r",stdin); freopen("kth.out","w",stdout);
	n=read(); m=read(); id[s[0]=n+1]=0; id[s[n+1]=n+2]=n+1;
	for(int i=1;i<=n;i++) s[i]=read(),id[s[i]]=i;
	for(int i=1;i<=n;i++) fro[s[i]]=s[i-1],nxt[s[i]]=s[i+1];
	for(int i=1;i<=n;i++)
	{
		t1=t2=-1;
		for(int j=i;j&&t1<m;j=fro[j]) a[++t1]=id[j];
		for(int j=i;j&&t2<m;j=nxt[j]) b[++t2]=id[j];
		for(int j=1;j<=t1;j++) if(m-j<t2) ans+=1ll*i*(a[j-1]-a[j])*(b[m-j+1]-b[m-j]);
		int tmp1=fro[i],tmp2=nxt[i]; fro[tmp2]=tmp1,nxt[tmp1]=tmp2;
	}
	printf("%lld",ans);
	return 0;
}

T3 树上路径

解题思路

一道及其难调的树形 DP ,需要维护好多的值QAQ。。。

\(f_i\) 表示 \(i\) 节点向子树中可以延伸到最长路径。

\(dis_i\) 表示 \(i\) 节点为根节点的子树中最长的路径。

\(g_i\) 表示 除了以 \(i\) 节点为根的子树,\(fa_i\) 为路径的一个端点可以延伸到最长路径。

\(dp_i\) 表示删去 \(i\rightarrow fa_i\) 这一条边 \(fa_i\) 所在部分的最长路径。

答案显然就是对于每一个边枚举它断掉的情况,然后记录两个部分的直径(假设深度较大的是 \(x\),两个直径就是 \(dis_x\)\(dp_x\))最后再给答案取一个后缀 \(\max\)

对于 \(f_i\) 还有 \(dis_i\) 数组的计算其实就是 DP 求树的直径的打法。

对于 \(g_i\) 数组的直接记录一个子树内的最大值以及次大值就好了。

对于 \(dp_i\) 数组的转移需要记一下关于 \(f_i\) 的最大值次大值以及次次大值,因为计算答案的时候需要算上除了自己这一棵子树,它的父亲节点的其他子树之间的最长路径所拼起来的贡献。

大概三遍 DFS 就解决了。。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=5e5+10;
int n,Ans,ans[N],f[N],g[N],dis[N],dep[N],mx[N],sec[N],tri[N],dp[N];
int tot=1,head[N],ver[N<<1],nxt[N<<1];
bool vis[N];
void add_edge(int x,int y)
{
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
void dfs(int x,int fa)
{
	dep[x]=dep[fa]+1;
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i]; if(to==fa) continue; dfs(ver[i],x);
		dis[x]=max(dis[x],max(dis[to],f[x]+f[to]+1));
		f[x]=max(f[x],f[to]+1);
		if(f[to]+1>mx[x]) tri[x]=sec[x],sec[x]=mx[x],mx[x]=f[to]+1;
		else if(f[to]+1>sec[x]) tri[x]=sec[x],sec[x]=f[to]+1;
		else tri[x]=max(tri[x],f[to]+1);
	}
}
void dfs2(int x,int fa)
{
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i]; if(to==fa) continue;
		if(f[to]+1==mx[x]) g[to]=max(g[x]+1,sec[x]+1);
		else g[to]=max(g[x]+1,mx[x]+1); dfs2(ver[i],x);
	}
}
void dfs3(int x,int fa)
{
	int num1=0,num2=0;
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i]; if(to==fa) continue;
		if(num1<dis[to]+1) num2=num1,num1=dis[to]+1;
		else num2=max(num2,dis[to]+1);
	}
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i]; if(to==fa) continue; dp[to]=g[x];
		if(f[to]+1==mx[x]) dp[to]=max(dp[to],sec[x]);
		else dp[to]=max(dp[to],mx[x]);
	}
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i]; if(to==fa) continue;
		dp[to]=max(dp[to],dis[to]+1==num1?num2:num1);
		if(f[to]+1==mx[x]) dp[to]=max(dp[to],sec[x]+g[x]+1);
		else dp[to]=max(dp[to],mx[x]+g[x]+1);
		if(f[to]+1==mx[x]) dp[to]=max(dp[to],sec[x]+tri[x]+1);
		else if(f[to]+1==sec[x]) dp[to]=max(dp[to],mx[x]+tri[x]+1);
		else dp[to]=max(dp[to],mx[x]+sec[x]+1); dfs3(to,x);
	}
}
#undef int
int main()
{
	#define int long long
	freopen("tree.in","r",stdin); freopen("tree.out","w",stdout);
	n=read();
	for(int i=1,x,y;i<n;i++)
		x=read(),y=read(),
		add_edge(x,y),add_edge(y,x);
	dfs(1,0); dfs2(1,0); dfs3(1,0);
	for(int i=1;i<n;i++)
	{
		int x=ver[i<<1],y=ver[i<<1|1]; if(dep[x]>dep[y]) swap(x,y);
		int num1=dis[y]+1,num2=dp[y];
		ans[num1]=max(ans[num1],num2); ans[num2]=max(ans[num2],num1);
	}
	for(int i=n;i>=1;i--) ans[i]=max(ans[i],ans[i+1]),Ans+=ans[i];
	printf("%lld",Ans);
	return 0;
}

T4 糖

解题思路

思路特别妙,我们先在每一个点把背包填满,那么如果到了最后还没有用掉的话可以直接按照原价卖出计算贡献。

如果背包中糖果的买入价格小于当前点的卖价,我们就可以当做他们是以当前点卖价买进来的,于是可以直接更改他们的价格但是不对答案造成贡献。

那么如果背包中糖果的买入价格大于当前点的买入价,我们就可以直接替换掉这些糖果。

最后再把背包填满,然后计算路径上的删掉的糖果,优先吃掉买的代价较小的糖果。

上述操作都可以通过 单调队列 来实现。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=2e5+10;
int n,m,head=1,tail,ans,hav,s[N],a[N],b[N];
struct Node{int val,cnt;}q[N];
#undef int
int main()
{
	#define int long long
	freopen("candy.in","r",stdin); freopen("candy.out","w",stdout);
	n=read(); m=read();
	for(int i=1;i<=n;i++) s[i]=read();
	for(int i=0;i<n;i++) a[i]=read(),b[i]=read();
	for(int i=0;i<n;i++)
	{
		int sum=0,dis=s[i+1]-s[i];
		while(head<=tail&&q[head].val<=b[i]) sum+=q[head++].cnt;
		if(sum) q[--head]=(Node){b[i],sum};
		while(head<=tail&&q[tail].val>=a[i])
			ans-=q[tail].val*q[tail].cnt,hav-=q[tail--].cnt;
		if(hav<m) q[++tail]=(Node){a[i],m-hav},ans+=a[i]*(m-hav),hav=m;
		while(head<=tail&&dis>=q[head].cnt) dis-=q[head++].cnt;
		if(dis) q[head].cnt-=dis; hav-=s[i+1]-s[i];
	}
	while(head<=tail) ans-=q[head].cnt*q[head].val,head++;
	printf("%lld",ans);
	return 0;
}
posted @ 2021-11-11 21:41  Varuxn  阅读(59)  评论(0编辑  收藏  举报