模拟赛20200228(yyq)【右链+dfs序,子树管辖,聚集水流问题】

T1:管理

在这里插入图片描述
998244 8 53 998244{\color{red}{8}}53 998244853取模

题解:

在这里插入图片描述
知道至少要多少还是不好做,能不能把它转化成暂时只计算这么多个呢?
是可以的,因为在一棵树的右链上接一棵子树不会改变原树的dfs序的,所以我们只需要记下右链的长度即可;因为限制关系形成了树形结构,记 s i z [ i ] siz[i] siz[i] i i i至少需要管辖的人数,记 f [ i ] [ j ] f[i][j] f[i][j]表示 [ i , i + s i z [ i ] − 1 ] [i,i+siz[i]-1] [i,i+siz[i]1]形成的树的右链长度为 j j j的方案数,转移就按顺序将 i i i在限制树中的子树合并到 i i i上即可,由 f [ i ] [ x ∼ s i z [ i ] ] ∗ f [ j ] [ y ] → f [ i ] [ x + y ] f[i][x\sim siz[i]]*f[j][y]\to f[i][x+y] f[i][xsiz[i]]f[j][y]f[i][x+y],记录一下后缀和,就是 O ( n 2 ) O(n^2) O(n2)的。

Code:

#include<bits/stdc++.h>
#define maxn 4505
using namespace std;
const int mod = 998244853;
int n,a[maxn],fa[maxn],siz[maxn],sum[maxn],f[maxn][maxn];
int main()
{
	freopen("administration.in","r",stdin);
	freopen("administration.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	a[1]=n;
	for(int i=n;i>=1;i--){
		if((a[i]+=i-1)>n) return puts("0"),0;
		fa[i]=i,a[i]=a[fa[a[i]]];//fa[i]:i在哪个限制的范围中,a[i]:i的限制范围
		f[i][siz[i]=1]=1;
		for(int j=i+1;j<=a[i];j=a[j]+1){
			sum[siz[i]+1]=0;
			for(int k=siz[i];k>=1;k--) sum[k]=(sum[k+1]+f[i][k])%mod,f[i][k]=0;
			for(int x=1;x<=siz[i];x++)
				for(int y=1;y<=siz[j];y++)
					f[i][x+y]=(f[i][x+y]+1ll*sum[x]*f[j][y])%mod;
			siz[i]+=siz[j];
		}
		for(int j=i+1;j<=a[i];j++) fa[j]=i;
	}
	int ans=0;
	for(int i=1;i<=n;i++) ans=(ans+f[1][i])%mod;
	printf("%d\n",ans);
}

T2:

在这里插入图片描述
在这里插入图片描述

题解:

这种水流题要是陷入模拟的怪圈之中就会被各种特殊情况恶心致死
正确的姿势应该是枚举最后水的范围 [ L , R ] [L,R] [L,R],然后硬把所有的水都聚集到这里来。值得注意的是如果考虑 L − 1 L-1 L1 R + 1 R+1 R+1,那么水的高度可能并不具有单调性,我们可先不考虑两边算出水的高度,然后再与两边比较,如果高了就将 [ L , R ] [L,R] [L,R]挖低。

在这里插入图片描述
f ( l , r ) f(l,r) f(l,r)为水的区间为 [ l , r ] [l,r] [l,r]时水的高度,那么显然 f ( l , r ) ≥ f ( l , r + 1 ) f(l,r)\ge f(l,r+1) f(l,r)f(l,r+1),考虑固定 l l l,扫描 r r r,那么就可以令高度从1000在整数范围往下递减,算答案的时候根据在水下的块数补上小数部分的答案。复杂度 O ( n 2 ) O(n^2) O(n2)

Code:

#include<bits/stdc++.h>
#define maxn 5005
using namespace std;
int T,n,lp,rp,ml[maxn],mr[maxn],sl[maxn],sr[maxn],S[maxn],a[maxn],w[maxn],cnt[1005];
int main()
{
	freopen("exploration.in","r",stdin);
	freopen("exploration.out","w",stdout);
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n),a[0]=a[n+1]=1e9,w[0]=0;
		for(int i=1;i<=n;i++) scanf("%d",&a[i]),S[i]=S[i-1]+a[i];
		for(int i=1;i<=n;i++) scanf("%d",&w[i]),w[0]+=w[i];
		for(int i=1;i<=n;i++)
			if(!w[i]) ml[i]=sl[i]=0;
			else{
				lp=i,ml[i]=a[i],sl[i]=0;
				for(int j=i+1;j<=n;j++) ml[j]=min(ml[j-1],a[j]);
				for(int j=i+1;j<=n;j++) sl[j]=sl[j-1]+a[j]-ml[j];
				break;
			}
		for(int i=n;i>=1;i--)
			if(!w[i]) mr[i]=sr[i]=0;
			else{
				rp=i,mr[i]=a[i],sr[i]=0;
				for(int j=i-1;j>=1;j--) mr[j]=min(mr[j+1],a[j]);
				for(int j=i-1;j>=1;j--) sr[j]=sr[j+1]+a[j]-mr[j];
				break;
			}
		double ans=1e9;
		for(int l=1;l<=n;l++){
			memset(cnt,0,sizeof cnt);
			int h=1000,down=0,cd=0;
			for(int r=l;r<=n;r++){
				if(a[r]<=h){
					down+=h-a[r],cnt[a[r]]++,cd++;
					while(down>w[0]) down-=cd-=cnt[h--];
				}
				double x=h+1.0*(w[0]-down)/cd;
				double res=S[r]-S[l-1]-x*(r-l+1)+w[0];
				int lh=a[l-1],rh=a[r+1];
				if(lp<l) lh=ml[l-1],res+=sl[l-1];
				if(r<rp) rh=mr[r+1],res+=sr[r+1];
				if(x>min(lh,rh)) res+=(x-min(lh,rh))*(r-l+1);
				ans=min(ans,res);
			}
		}
		printf("%.5f\n",ans);
	}
}

T3:谈判

在这里插入图片描述
n ≤ 500 , w i ≤ 1 0 6 n≤500,w_i≤10^6 n500wi106

题解:

(原题CF1061E)
两棵树,集合限制,带权 ⇒ \Rarr 费用流。
S S S向第一棵树的限制点连容量为 b [ i ] − ∑ j 是 j 到 i 路 径 上 的 第 一 个 限 制 点 b [ j ] b[i]-\sum_{j是j到i路径上的第一个限制点} b[j] b[i]jjib[j],费用为0的边。
第二棵树的限制点向 T T T连容量为 b [ i ] − ∑ j 是 j 到 i 路 径 上 的 第 一 个 限 制 点 b [ j ] b[i]-\sum_{j是j到i路径上的第一个限制点} b[j] b[i]jjib[j],费用为0的边。
设每个城市在第一棵树中被 x x x管辖,在第二棵树中被 y y y管辖,则 x x x y y y连容量为1,费用为 w [ i ] w[i] w[i]的边。
跑最大费用最大流即可。
注意 b [ i ] b[i] b[i]可以为0,所以初值设为-1.

Code:

#include<bits/stdc++.h>
#define maxn 1005
#define maxm 5005
using namespace std;
const int inf = 0x3f3f3f3f;
int n,S,T,X,Y,a[maxn];
int fir[maxn],nxt[maxm],to[maxm],c[maxm],w[maxm],tot=1;
inline void line(int x,int y,int z,int v){
	nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y,c[tot]=z,w[tot]=v;
	nxt[++tot]=fir[y],fir[y]=tot,to[tot]=x,c[tot]=0,w[tot]=-v;
}
void Fail(){puts("-1");exit(0);}
struct Tree{
	vector<int>G[maxn];
	int b[maxn],bel[maxn]; bool flg;
	void link(int x,int y){G[x].push_back(y),G[y].push_back(x);}
	int dfs(int u,int ff,int top){
		int sum=0; bel[u]=top;
		for(int i=0,lim=G[u].size(),v;i<lim;i++) if((v=G[u][i])!=ff)
			sum+=dfs(v,u,b[v]?v:top);
		if(b[u]){
			if(b[u]<sum) Fail();
			if(!flg) line(S,u,b[u]-sum,0);
			else line(u+n,T,b[u]-sum,0);
			return b[u];
		}
		return sum;
	}
}T1,T2;
int pre[maxn],E[maxn],dis[maxn]; bool inq[maxn];
queue<int>q;
bool SPFA(){
	memset(dis,-0x3f,(T+1)<<2);
	dis[S]=0,q.push(S),pre[T]=-1;
	while(!q.empty()){
		int u=q.front(); q.pop(),inq[u]=0;
		for(int i=fir[u],v;i;i=nxt[i]) if(c[i]&&dis[v=to[i]]<dis[u]+w[i]){
			dis[v]=dis[u]+w[i],pre[v]=u,E[v]=i; if(!inq[v]) inq[v]=1,q.push(v);
		}
	}
	return ~pre[T];
}
int Costflow(){
	int ans=0,flow=0;
	while(SPFA()){
		int mn=inf;
		for(int i=T;i!=S;i=pre[i]) mn=min(mn,c[E[i]]);
		for(int i=T;i!=S;i=pre[i]) c[E[i]]-=mn,c[E[i]^1]+=mn;
		ans+=dis[T]*mn,flow+=mn;
	}
	if(flow!=T1.b[X]) Fail();
	return ans;
}
int main()
{
	freopen("negotiation.in","r",stdin);
	freopen("negotiation.out","w",stdout);
	int x,y,m;
	scanf("%d%d%d",&n,&X,&Y),S=0,T=2*n+1; 
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<n;i++) scanf("%d%d",&x,&y),T1.link(x,y);
	for(int i=1;i<n;i++) scanf("%d%d",&x,&y),T2.link(x,y);
	scanf("%d",&m);
	while(m--) scanf("%d%d",&x,&y),T1.b[x]=y;
	scanf("%d",&m);
	while(m--) scanf("%d%d",&x,&y),T2.b[x]=y;
	if(T1.b[X]!=T2.b[Y]) Fail();
	T1.dfs(X,0,X),T2.flg=1,T2.dfs(Y,0,Y);
	for(int i=1;i<=n;i++) line(T1.bel[i],T2.bel[i]+n,1,a[i]);
	printf("%d\n",Costflow());
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值