noip模拟33 hunter defence connect

Hunter

考场:

考场上完全没思路,写了一个状压还挂了

正解:

一号猎人的存活时间等于在他之前死的猎人的数量加一,所以只要计算出在一号死之前有多少猎人死的期望就可以了。对于每一个猎人,在一号之前死的概率为 \(\frac{w_{i}}{w_{1}+w_{i}}\)所以,他的期望便是枚举每一个人后的和加一

代码实现
#include<bits/stdc++.h>
#define lt long long
#define int long long
using namespace std;
const int N=1050000,mod=998244353;
int n;
lt f[N];
lt ch[N];
lt nn;
lt ens;
bool vis[N];

lt poow(lt x,lt y){
	lt ans=1;
	while(y){
		if(y&1) ans=(ans*x)%mod;
		y>>=1;
		x=x*x%mod;
	}
	return ans;
}

signed main(){
	scanf("%lld",&n);
	for(int i=1;i<=n;++i){
		scanf("%lld",&ch[i]);
	}
	ens=1;
	for(int i=2;i<=n;++i){
		ens=(ens+(ch[i]*poow(ch[1]+ch[i],mod-2))%mod)%mod;
	}
	printf("%lld",ens);
	return 0;
}

Defence

考场上想到了一个奇怪的思路,不细说了

正解:

使用线段树合并维护每一个节点(本人第一次写线段树合并)。
首先,dfs,在向下dfs时,对于有法术的点动态开点建立线段树。递归到最底层,回溯,如果父节点有线段树则合并父节点与子节点,若没有则先将一个儿子赋给父亲

tip:线段树合并的时间复杂度是O(nlogn)

代码实现


#include<bits/stdc++.h>
using namespace std;

const int N=100050;
int n,m,q,tot,pnt;
int head[N],root[N];
int ans[N];
vector<int> zkk[N];

struct tree{
	int le,ri,l,r,len;
}t[N*80];

struct edge{
	int nxt,to;
}e[N*2];

void ade(int x,int y){
	e[++tot].nxt=head[x];
	e[tot].to=y;
	head[x]=tot;
}

void pushup(int u){
	if(t[u].l){
		t[u].le=t[t[u].l].le;
		t[u].ri=t[t[u].l].ri;
		t[u].len=t[t[u].l].len;
	}
	else{
		t[u].le=t[t[u].r].le;
	}
	if(t[u].r){
		t[u].ri=t[t[u].r].ri;
		t[u].len=max(t[u].len,t[t[u].r].len);
	}
	if(t[u].l&&t[u].r){
		t[u].len=max(t[u].len,t[t[u].r].le-t[t[u].l].ri-1);
	}
}

void adp(int &u,int l,int r,int x){
	if(!u) u=++pnt;
	if(l==r){
		t[u].le=x;
		t[u].ri=x;
		return;
	}
	int mid=((l+r)>>1);
	if(x<=mid){
		adp(t[u].l,l,mid,x);
	}
	else{
		adp(t[u].r,mid+1,r,x);
	}
	pushup(u);
}

void merge(int x,int y){
	if(!t[x].l&&!t[x].r&&!t[y].l&&!t[y].r) return ;
	if(t[x].l&&t[y].l) merge(t[x].l,t[y].l);
	if(t[x].r&&t[y].r) merge(t[x].r,t[y].r);
	if(!t[x].l) t[x].l=t[y].l;
	if(!t[x].r) t[x].r=t[y].r;
	pushup(x);
}

void dfs(int u,int z){
	int size=zkk[u].size();
	for(int i=0;i<size;++i){
		adp(root[u],1,m,zkk[u][i]);
	}
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==z) continue;
		dfs(v,u);
		if(root[u]){
			merge(root[u],root[v]);
		}
		else{
			root[u]=root[v];
		}
	}
//	printf("%d %d %d %d %d\n",u,m-t[root[u]].ri+t[root[u]].le-1,t[root[u]].len,t[root[u]].le,t[root[u]].ri);
	ans[u]=max(t[root[u]].len,m-t[root[u]].ri+t[root[u]].le-1);
	if(!root[u]){
		ans[u]=-1;
	}
}

int main(){
	scanf("%d%d%d",&n,&m,&q);
	int uc,vc;
	for(int i=1;i<n;++i){
		scanf("%d%d",&uc,&vc);
		ade(uc,vc);
		ade(vc,uc);
	}
	int ac,bc;
	for(int i=1;i<=q;i++){
		scanf("%d%d",&ac,&bc);
		zkk[ac].push_back(bc);
	}
	dfs(1,0);
	for(int i=1;i<=n;++i){
//		printf("%d:",root[i]);
		printf("%d\n",ans[i]);
	}
	return 0;
}

Connect

考场上通过输出(n-2)骗了点分,状压真的不会写呀!!!

正解:

考虑状压
如果想让从起点到终点只有一条路径,需要将剩下的节点与点集只与路径上的一个点相连(此时可以保证从起点到终点的路径的唯一性)
考虑实现,建立dp数组f[i][j],i表示状态,j表示当前路径的终点。
枚举状态,枚举终点,进行两种转移
1.枚举一个点集t(t与状态点集不相交),考虑让t与终点连边的贡献,与t点集中的内部贡献,终点不变
2.枚举j所能到达的点,作为下一个终点进行转移

tip:枚举一个集合的子集的方法
for(int tt=ss;tt;tt=(tt-1)&ss)
tip2:可以先维护答案正确性(连接一条从起点到终点的链),再使不影响答案正确性的需要部分加入(加入剩余点集)

代码实现

#include<bits/stdc++.h>
#define lt long long
#define int long long
using namespace std;

int n,nn,m,tot,toc;
int head[20];
int f[40000][20];
int dis[40000][20];
int t[40000];
struct edge{
	int fr,nxt,to,dis;
}e[7000],c[5000];


void ade(int x,int y,int z){
	e[++tot].nxt=head[x];
	e[tot].dis=z;
	e[tot].to=y;
	head[x]=tot;
}

void adc(int x,int y,int z){
	c[++toc].fr=x;
	c[toc].to=y;
	c[toc].dis=z;
}	

signed main(){
	scanf("%lld%lld",&n,&m);
	int uc,vc,wc;
	for(int i=1;i<=m;i++){
		scanf("%lld%lld%lld",&uc,&vc,&wc);
		adc(uc,vc,wc);
		ade(uc,vc,wc);
		ade(vc,uc,wc);
	}
	nn=(1<<n)-1;
	for(int i=1;i<=nn;++i){
		for(int j=1;j<=m;++j){
			if((i>>(c[j].fr-1)&1)&&(i>>(c[j].to-1)&1)){
				t[i]+=c[j].dis;
			}
		}
	//	printf("%lld\n",t[i]);
	}
	for(int j=1;j<=n;j++){
		for(int tt=nn;tt;tt=(tt-1)&nn){
			int sum=0;
			for(int k=head[j];k;k=e[k].nxt){
				int v=e[k].to;
				if(!((tt>>(v-1))&1)) continue;
				sum+=e[k].dis;
			}
			dis[tt][j]=sum;
		}
	}
	
	for(int i=1;i<=nn;++i){
		for(int j=1;j<=n;++j){
			f[i][j]=-0x3f3f3f3f;
		}
	}
	
	f[1][1]=0;
	for(int i=0;i<=nn;++i){
		for(int j=1;j<=n;++j){
			if(f[i][j]==-0x3f3f3f3f)continue;
			if((i>>((j-1)&1))==0) continue;
			for(int k=head[j];k;k=e[k].nxt){
				int v=e[k].to;
				if((i>>(v-1))&1) continue;
				f[i|(1<<(v-1))][v]=max(f[i|(1<<(v-1))][v],f[i][j]+e[k].dis);
			}
			int ss=nn^i;
			for(int tt=ss;tt;tt=(tt-1)&ss){
				f[i|tt][j]=max(f[i|tt][j],f[i][j]+t[tt]+dis[tt][j]);
			}
		}
	}
	lt ens=0;
	ens=t[nn]-f[nn][n];
	printf("%lld\n",ens);
	return 0;
}

posted @ 2021-08-08 18:35  wangcongrui  阅读(67)  评论(0)    收藏  举报