JSOI2018day1简要题解

action

DP:

暴力艹标算系列,实际上正解很妙(以前看过),感兴趣的可以去了解一下。

d p [ i ] [ j ] [ 0 / 1 ] [ 0 / 1 ] dp[i][j][0/1][0/1] dp[i][j][0/1][0/1]表示 i i i子树选了 j j j个点, i i i是否被选的儿子结点窃听到以及是否选 i i i的方案数。复杂度 O ( n k 2 ) O(nk^2) O(nk2),上界卡不满

瞎写了个fft卷积,跑得比暴力慢许多…

#include<bits/stdc++.h>
typedef double db;
typedef long long ll;
const int N=1e5+2,M=101,mod=1e9+7;
using namespace std;

int n,m,ans,sz[N];
int head[N],to[N<<1],nxt[N<<1],tot;

int f[N][M][2][2],rep[M][2][2];

char cp;
inline void rd(int &x)
{
	cp=getchar();int f=0;x=0;
	for(;!isdigit(cp);cp=getchar()) if(cp=='-') f=1;
	for(;isdigit(cp);cp=getchar()) x=x*10+(cp^48);
	if(f) x=-x;
}

inline void ad(int &x,int y){x+=y;if(x>=mod) x-=mod;}
inline void lk(int u,int v)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}


void dfs(int x,int fr)
{
	int i,k,t,l,a,b,y;sz[x]=1;
	f[x][1][0][1]=f[x][0][0][0]=1;
	for(i=head[x];i;i=nxt[i]){
		y=to[i];if(y==fr) continue;
		dfs(y,x);sz[x]+=sz[y];
		for(k=m;k>=0;--k)
		 for(a=0;a<2;++a)
		  for(b=0;b<2;++b)
		   rep[k][a][b]=0;
		for(k=min(m,sz[y]);k>=0;--k)
		 for(t=min(sz[x]-sz[y],m-k);t>=0;--t){
			if(f[y][k][0][0])
			 for(l=0;l<2;++l)
			  ad(rep[k+t][l][1],(ll)f[x][t][l][1]*f[y][k][0][0]%mod);
		    if(f[y][k][0][1])
			 for(l=0;l<2;++l)
			  ad(rep[k+t][1][1],(ll)f[x][t][l][1]*f[y][k][0][1]%mod);
		    if(f[y][k][1][0])
		     for(l=0;l<2;++l){
			  ad(rep[k+t][l][0],(ll)f[x][t][l][0]*f[y][k][1][0]%mod),
			  ad(rep[k+t][l][1],(ll)f[x][t][l][1]*f[y][k][1][0]%mod);
		    }
		    if(f[y][k][1][1])
		     for(l=0;l<2;++l){
			  ad(rep[k+t][1][0],(ll)f[x][t][l][0]*f[y][k][1][1]%mod),
			  ad(rep[k+t][1][1],(ll)f[x][t][l][1]*f[y][k][1][1]%mod);
		    }
		 }
		for(k=m;k>=0;--k)
		 for(a=0;a<2;++a)
		  for(b=0;b<2;++b)
		   f[x][k][a][b]=rep[k][a][b];
	}
}



int main(){
	freopen("action.in","r",stdin);
	freopen("action.out","w",stdout);
	int i,x,y;rd(n);rd(m);
	for(i=1;i<n;++i){rd(x);rd(y);lk(x,y);lk(y,x);}
	dfs(1,0);
	for(i=0;i<2;++i) ad(ans,f[1][m][1][i]);
	printf("%d",ans);
	fclose(stdin);fclose(stdout);
	return 0;
}

defense

贡献转移+前缀和优化

注意图是一个仙人掌,可以特判环边和树边,并不需要真的跑斯坦纳树(复杂度也不对)。

选一个子图 G G G中的若干个点(至少1个)的方案显然是 2 ∣ G ∣ − 1 2^{|G|}-1 2G1

求期望转成总和除以 2 n 2^{n} 2n,分别考虑每条树边和每个环对答案的贡献:

  • 经过树边的次数显然为选择点集包含两端子树内若干个点(至少1个)的方案数。
  • 经过一个环当且仅当至少选了环上的两个点,此时环边的最小贡献为环长 l l l-选择的相邻两个环点的最大距离 x x x。设 d p x dp_x dpx表示选择选择的相邻环点距离均 ≤ x \leq x x的方案数,可以断环为链枚举第一个选的点是链上第几个,前缀和优化后 n 3 n^3 n3得到 d p 1 − l dp_{1-l} dp1l的答案。显然最远距离恰好为 x x x的方案数为 d p x − d p x − 1 dp_x-dp_{x-1} dpxdpx1,环的贡献就是 ∑ i = 1 l − 1 ( l − i ) ( f i − f i − 1 ) \sum\limits_{i=1}^{l-1}(l-i)(f_i-f_{i-1}) i=1l1(li)(fifi1)

p.s
注意邻接表边要开4倍

#include<bits/stdc++.h>
#define pb push_back
typedef long long ll;
using namespace std; 
const int N=205,mod=1e9+7;

int n,m,ans,bel[N],bin[N];
int sz[N],dep[N],vs[N],tim,cir;
int ta[N],tb[N],ca,cb,f[N],val[N],fa[N];
int head[N],to[N<<2],nxt[N<<2],tot;

vector<int>g[N];

char cp;
inline void rd(int &x)
{
	cp=getchar();int f=0;x=0;
	for(;!isdigit(cp);cp=getchar()) if(cp=='-') f=1;
	for(;isdigit(cp);cp=getchar()) x=x*10+(cp^48);
	if(f) x=-x;
}

inline void ad(int &x,int y){x+=y;if(x>=mod) x-=mod;}
inline int inc(int x,int y){x+=y;return x>=mod?x-mod:x;}
inline int dec(int x,int y){x-=y;return x<0?x+mod:x;}

void dfs(int x,int fr)
{
	int i,j,a,b;vs[x]=tim;
	fa[x]=fr;dep[x]=dep[fr]+1;
	for(i=head[x];i;i=nxt[i]){
		j=to[i];if(j==fr) continue;
		if(vs[j]!=tim) dfs(j,x);
		else if(j<x){
		ca=cb=0;a=x;b=j;
		for(cir++;a!=b;){
			if(dep[a]<dep[b]) {tb[++cb]=b;b=fa[b];}
			else {ta[++ca]=a;a=fa[a];}
		}
		for(j=1;j<=ca;++j) g[cir].pb(ta[j]);
		g[cir].pb(a);
		for(j=cb;j>0;--j) g[cir].pb(tb[j]);
		for(j=g[cir].size()-1;j>=0;--j) bel[g[cir][j]]=cir;
	    }
	}
}

int gt(int x,int fr)
{
	int re=1,i,j;vs[x]=tim;
	for(i=head[x];i;i=nxt[i]){
		j=to[i];if(j!=fr && vs[j]!=tim) re+=gt(j,x); 
	}
	return re;
}

inline void lk(int u,int v)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}

inline int fp(int x,int y)
{
	int re=1;
	for(;y;y>>=1,x=(ll)x*x%mod)
	  if(y&1) re=(ll)re*x%mod;
	return re;
}

int main(){
	freopen("defense.in","r",stdin);
	freopen("defense.out","w",stdout);
	int i,j,k,x,y,len;
	rd(n);rd(m);bin[0]=1;
	for(i=1;i<=n;++i) bin[i]=inc(bin[i-1],bin[i-1]);
	for(i=1;i<=m;++i){rd(x);rd(y);lk(x,y);lk(y,x);}
	tim++;dfs(1,0);
	
	for(i=1;i<=n;++i) if(!bel[i]) bel[i]=++cir;
	for(i=1;i<=n;++i){
		for(j=head[i];j;j=nxt[j]) if(bel[to[j]]!=bel[i]){
		    tim++;x=gt(to[j],i);sz[i]+=x;
			if(to[j]>i) ad(ans,(ll)dec(bin[n-x],1)*dec(bin[x],1)%mod); 
		}
		sz[i]=dec(bin[sz[i]+1],1);
	}
	
	for(y=1;y<=cir;++y){
		len=g[y].size();
		for(i=1;i<len;++i)
		 for(val[i]=0,x=0;x<i;++x){
		   for(j=0;j<x;++j) f[j]=0;
		   f[x]=sz[g[y][x]];
		   for(j=x+1;j<len;++j)
		   	  f[j]=inc(f[j-1],(ll)dec(f[j-1],j<i+1?0:f[j-i-1])*sz[g[y][j]]%mod);
		   ad(val[i],dec(f[len-1],f[len-1-i+x]));
		 }
		for(i=1;i<len;++i) 
		  ad(ans,(ll)(len-i)*dec(val[i],val[i-1])%mod);
	}
	
	printf("%d",(ll)ans*fp(2,(ll)n*(mod-2)%(mod-1))%mod);
	fclose(stdin);fclose(stdout);
	return 0;
}

fleet

二分+二分图匹配

二分最远距离 m i d mid mid时,可以得到每个点能够到达的圆上的一段区间,最优解显然可以取到某个点刚好卡在区间边界上的情况——否则整体左/右平移直到某个点卡上。

考虑枚举被卡住的点,其它位置就是固定的了,跑匈牙利判断是否存在完美匹配即可。

但复杂度是 O ( n ⋅ n 3 log ⁡ w ) O(n·n^3\log w) O(nn3logw)的,膜了claris的题解学习了一发优化技巧:

  • 稠密图可以压位存边,匈牙利复杂度变成 n 3 32 \dfrac{n^3}{32} 32n3
  • 可以先枚举卡住的点,再在之前的基础上二分( r r r设为之前的 a n s ans ans),如果 a n s − e p s ans-eps anseps不可行就没有二分的必要了。
  • 将点的顺序随机打乱,第 i i i个点可以得到前 i i i个点的最小答案的概率是 1 i \dfrac 1i i1的,所以期望二分次数是 log ⁡ n \log n logn

优化后期望复杂度 O ( ( n + log ⁡ n log ⁡ w ) n 3 32 ) O((n+\log n\log w)\dfrac{n^3}{32}) O((n+lognlogw)32n3)

STO Claris

为了好(偷)写(懒),代码调用了很多可能会造成一定精度误差的函数,但这题似乎没卡精度?

#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef unsigned int ui;
const int N=205;
const db pi=acos(-1.0),eps=1e-8;

int n,m,R,bel[N],tim;db dlt,ans,mn;
ui nt[N][7],vs[7];

inline db sqr(db x){return x*x;}
struct P{
	db x,y;
	inline db sq()
	{return sqrt(sqr(x)+sqr(y));}
}p[N],pos;

char cp;
inline void rd(int &x)
{
	cp=getchar();int f=0;x=0;
	for(;!isdigit(cp);cp=getchar()) if(cp=='-') f=1;
	for(;isdigit(cp);cp=getchar()) x=x*10+(cp^48);
	if(f) x=-x;
}

int dfs(int x)
{
	for(int j,i=0;i<=m;++i){
		for(;(vs[i]&nt[x][i]);){
			j=i<<5 | __builtin_ctz(vs[i]&nt[x][i]);
			vs[i]^=(1U<<(j&31));
			if(!bel[j] || dfs(bel[j])){
				bel[j]=x;return 1;
			}
		}
	}
	return 0;
}

inline db dist(P a,P b)
{return sqrt(sqr(a.x-b.x)+sqr(a.y-b.y));}

inline bool ck(int x,int y,db lim)
{
	int i,j;db dis=p[x].sq(),rv=atan2(p[x].y,p[x].x);
	rv+=y*acos(((db)R*R+dis*dis-lim*lim)/(2*dis*R));
	for(i=1;i<=n;++i) 
	 for(j=0;j<=m;++j)
	   nt[i][j]=0U;
	for(i=0;i+1<n;++i){
		rv+=dlt;pos=(P){cos(rv)*R,sin(rv)*R};
		for(j=1;j<=n;++j) if(j!=x && dist(p[j],pos)-eps<lim)
		nt[j][i>>5]|=1U<<(i&31);
	}
	memset(bel,0,sizeof(bel));
	for(i=1;i<=n;++i) if(i!=x){
		for(j=0;j<=m;++j) vs[j]=~0U;
		if(!dfs(i)) return false;;
	}
	return true;
}

int main(){
	srand(19260817);
	freopen("fleet.in","r",stdin);
	freopen("fleet.out","w",stdout);
	int i,j,x,y;db nw,l,r,mid;rd(n);rd(R);m=(n-1)>>5;
	for(i=1;i<=n;++i){
	   rd(x);rd(y);p[i]=(P){(db)x,(db)y};
	   nw=p[i].sq();ans=max(ans,nw);
	   if(nw<=R) mn=max(mn,R-nw);else mn=max(mn,nw-R);
	}ans+=R;
	
	//特判R=0的情况 
	if(R==0) {printf("%.8lf",ans);return 0;}
	
	dlt=(pi+pi)/n;
	random_shuffle(p+1,p+n+1);
	for(i=1;i<=n;++i) 
	 for(j=-1;j<=1;j+=2){
	   l=mn;r=max(min(p[i].sq()+R,ans-eps),mn);
	   if(ck(i,j,r))
	    for(;r-l>eps;) {mid=(l+r)/2;if(ck(i,j,mid)) r=(ans=mid);else l=mid;}
	}
	printf("%.8lf",ans);
	fclose(stdin);fclose(stdout);
	return 0;
}

总结

三题都在可想可做范围内(暂不讨论T1正解)
250-有点凉,200-就真的凉了。

但是最近代码能力欠佳,小错误不断。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用[1],dp[u][j]表示在u子树中选取恰好j个人时能获得的最大价值。而根据引用,该问题的时间复杂度为O(log2​104×nm)。 对于洛谷P2143 [JSOI2010] 巨额奖金问题,我们可以使用动态规划来解决。具体步骤如下: 1. 首先,我们需要构建一棵树来表示员工之间的关系。树的根节点表示公司的总经理,其他节点表示员工。每个节点都有一个权值,表示该员工的奖金金额。 2. 接下来,我们可以使用动态规划来计算每个节点的dp值。对于每个节点u,我们可以考虑两种情况: - 如果选择节点u,则dp[u][j] = dp[v][j-1] + value[u],其中v是u的子节点,value[u]表示节点u的奖金金额。 - 如果不选择节点u,则dp[u][j] = max(dp[v][j]),其中v是u的子节点。 3. 最后,我们可以通过遍历树的所有节点,计算出dp[u][j]的最大值,即为所求的巨额奖金。 下面是一个示例代码,演示了如何使用动态规划来解决洛谷P2143 [JSOI2010] 巨额奖金问题: ```python # 构建树的数据结构 class Node: def __init__(self, value): self.value = value self.children = [] # 动态规划求解最大奖金 def max_bonus(root, j): dp = [[0] * (j+1) for _ in range(len(root)+1)] def dfs(node): if not node: return for child in node.children: dfs(child) for k in range(j, 0, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-1] + node.value) for child in node.children: for k in range(j, 0, -1): for l in range(k-1, -1, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-l-1] + dp[child.value][l]) dfs(root) return dp[root.value][j] # 构建树 root = Node(1) root.children.append(Node(2)) root.children.append(Node(3)) root.children[0].children.append(Node(4)) root.children[0].children.append(Node(5)) root.children[1].children.append(Node(6)) # 求解最大奖金 j = 3 max_bonus_value = max_bonus(root, j) print("最大奖金为:", max_bonus_value) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值