noip模擬33[進階啦啦啦]

fengwu2005發表於2021-08-08

noip模擬33 solutions

不知道該咋說,這場考試其實是我這三四場以來最最最最最順心的一場了

為啥呢?因為我這回思考有很多結果,得到了腦袋的回覆

就是你想了半個小時就有了一點點頭緒,那感覺就是"妙哉"

但是分數卻不如人意,只有100pts,掛掉了55pts

按照現在的狀態,下次肯定能A題

T1 Hunter

這個題還是一眼就有45pts,輕輕的狀壓一下,再加上記憶化搜尋

半個小時45pts到手。

45pts·狀壓
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=1e5+5;
const ll mod=998244353;
int n;
ll w[N],dp[1<<20];
ll ksm(ll x,ll y){
	ll ret=1;
	while(y){
		if(y&1)ret=ret*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ret;
}
ll dfs(int x,int s){
	if(x==n+1)return 1;
	if(dp[s])return dp[s];
	ll tmp=0;
	for(re i=1;i<=n;i++){
		if((s>>i-1)&1)continue;
		tmp=(tmp+w[i])%mod;
	}
	ll bas=ksm(tmp,mod-2);
	for(re i=1;i<=n;i++){
		if((s>>i-1)&1)continue;
		if(i==1)dp[s]=(dp[s]+w[i]*bas%mod*x%mod)%mod;
		else dp[s]=(dp[s]+w[i]*bas%mod*dfs(x+1,s|(1<<i-1)%mod))%mod;
	}
	return dp[s];
}
signed main(){
	scanf("%d",&n);
	for(re i=1;i<=n;i++)
		scanf("%lld",&w[i]);
	printf("%lld",dfs(1,0));
}

但是接下來的質的飛躍就比較難受,但是看完題解之後整個人都傻掉了

所以說,對於期望這個東西咋轉移都沒事

畢竟這個期望是具有線性性的,怎麼加都可以,然後這個題就直接做出來了

我們知道第一個獵人死在第幾次,那麼就是在他前面死的人的個數+1

下面就是求期望有多少人死在他前面,

如果當前這個人還沒有死,那麼這個人死在1號獵人之前的概率就是\(\frac{w_i}{w_1+w_i}\)

因為每死一個就會造成1的貢獻,所以期望就是概率

不需要考慮別人的死法,因為這個i包含了所有的其他的獵人

這樣考慮的原因還有一個,就是死的概率和誰開槍沒有關係,只和當前誰剩下有關

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=1e5+5;
const ll mod=998244353;
int n;
ll w[N],dp[1<<20];
ll ksm(ll x,ll y){
	ll ret=1;
	while(y){
		if(y&1)ret=ret*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ret;
}
ll ans;
signed main(){
	scanf("%d",&n);
	for(re i=1;i<=n;i++)
		scanf("%lld",&w[i]);
	for(re i=2;i<=n;i++){
		ans=(ans+w[i]*ksm(w[i]+w[1],mod-2)%mod)%mod;
	}
	printf("%lld",ans+1);
}

這個題其實非常的水,只要充分理解期望就好了。。

T2 Defence

我這個題考場上一眼切了,直接線段樹合併+維護最長區間

這個題之前做過好多次了

這次就維護區間內最長的0就好了。。。。不難吧

注意維護的時候,最後的答案是max(區間內最長的,左右最長的之和)這個可以看程式碼實現

因為初始全部是0,所以這裡的pushup有一些玄學,

如果看不懂可以去網上求救一下其他人的維護1的長度的做法

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=1e5+5;
int n,m,q;
int to[N],nxt[N],head[N],rp;
void add_edg(int x,int y){
	to[++rp]=y;
	nxt[rp]=head[x];
	head[x]=rp;
}
struct XDS{
	int mmx[N*80],rmx[N*80],lmx[N*80];
	int ls[N*80],rs[N*80];
	int seg;
	void pushup(int x,int l,int r){
		if(!ls[x]&&!rs[x])return ;
		int mid=l+r>>1;
		if(ls[x]&&rs[x]){
			mmx[x]=max(lmx[rs[x]]+rmx[ls[x]],max(mmx[rs[x]],mmx[ls[x]]));
			
			lmx[x]=lmx[ls[x]];
			if(lmx[ls[x]]==mid-l+1)lmx[x]=lmx[ls[x]]+lmx[rs[x]];

			rmx[x]=rmx[rs[x]];
			if(rmx[rs[x]]==r-mid)rmx[x]=rmx[rs[x]]+rmx[ls[x]];
		}
		if(ls[x]&&!rs[x]){
			mmx[x]=mmx[ls[x]];
			
			lmx[x]=lmx[ls[x]];
			if(lmx[ls[x]]==mid-l+1)lmx[x]=r-l+1;

			rmx[x]=rmx[ls[x]]+r-mid;
		}
		if(!ls[x]&&rs[x]){
			mmx[x]=mmx[rs[x]];

			lmx[x]=lmx[rs[x]]+mid-l+1;

			rmx[x]=rmx[rs[x]];
			if(rmx[rs[x]]==r-mid)rmx[x]=r-l+1;
		}
		return ;
	}
	void ins(int &x,int l,int r,int pos){
		if(!x)x=++seg;
		if(l==r)return ;
		int mid=l+r>>1;
		if(pos<=mid)ins(ls[x],l,mid,pos);
		else ins(rs[x],mid+1,r,pos);
		pushup(x,l,r);
		return ;
	}
	int merge(int x,int y,int l,int r){
		if(!x||!y)return x+y;
		if(l==r)return x;
		int mid=l+r>>1;
		ls[x]=merge(ls[x],ls[y],l,mid);
		rs[x]=merge(rs[x],rs[y],mid+1,r);
		pushup(x,l,r);
		return x;
	}
}xds;
int rt[N],ans[N];
void dfs(int x){
	for(re i=head[x];i;i=nxt[i]){
		int y=to[i];dfs(y);
		rt[x]=xds.merge(rt[x],rt[y],1,m);
	}
	if(!rt[x])ans[x]=-1;
	else ans[x]=max(xds.mmx[rt[x]],xds.lmx[rt[x]]+xds.rmx[rt[x]]);
}
signed main(){
	scanf("%d%d%d",&n,&m,&q);
	for(re i=1;i<n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		add_edg(x,y);
	}
	for(re i=1;i<=q;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		xds.ins(rt[x],1,m,y);
	}
	dfs(1);
	for(re i=1;i<=n;i++){
		printf("%d\n",ans[i]);
	}
}

所以說為什麼我只有45pts嘞???

因為我沒有判斷-1,但是其實挺冤的。

考場上我明明自己造了一組資料有-1的情況,當時我的\(code\)輸出的是0

然後我暴怒

這輸出0怎麼行呢,讓他變成m

一頓亂搞,把所有的-1全判成了m,考後直接把m改成-1,\(AC\;AC\;AC\;AC\)

T3 Connect

最近總是在考試的時候遇到一些狀壓神題,昨天是二維,今天是帶其他變數

真難!!!

理解了就非常的簡單

我們要將所有的點都加入到當前的狀態內,所以我們的dp第一維一定是點集

既然是dp,那麼我們就可以直接列舉所有狀態,而且這個題複雜度極其的小

設dp[i][j]表示當前點集為i,1-n的這個鏈的結尾是j,

當然你可能會認為,有可能j他不在這個鏈上啊啊,

大哥,這都是dp了,不在鏈上他能轉移過去,列舉就完事了,而且你當時也不知道誰在誰不在

我們有兩種情況一種是在當前點直接併入其他點集,但是這個點集中的點只可以跟當前j連邊

為啥不可以跟前邊連啊啊啊?因為前面的在前面已經計算過了,所以這裡面有好多無用狀態

但是他方便了你的轉移

另外一種就之在j上再接一個點k,那麼加入這個點後的點集的端點就是k啦,

為啥不能是j,因為你前面還會計算k在前面,j接在k後面的情況

dp就是dp,不需要考慮那麼多好吧。。。。

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=20;
int n,m;
int dis[N][N];
int sum[1<<15];
int dp[1<<15][16],val[1<<15][16];
signed main(){
	scanf("%d%d",&n,&m);
	for(re i=1;i<=m;i++){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		dis[x][y]=dis[y][x]=z;
	}
	for(re i=1;i<(1<<n);i++)
		for(re j=1;j<=n;j++)
			for(re k=j+1;k<=n;k++)
				if(((i>>j-1)&1)&&((i>>k-1)&1))sum[i]+=dis[j][k];
	for(re i=1;i<(1<<n);i++)
		for(re j=1;j<=n;j++){
			if((i>>j-1)&1)continue;
			for(re k=1;k<=n;k++)
				if(((i>>k-1)&1)&&dis[j][k])val[i][j]+=dis[j][k];
		}
	memset(dp,-1,sizeof(dp));
	dp[1][1]=0;
	for(re i=1;i<(1<<n);i++){
		for(re j=1;j<=n;j++){
			if(dp[i][j]==-1)continue;
			for(re k=1;k<=n;k++){
				if(((i>>k-1)&1)||!dis[j][k])continue;
				dp[i|(1<<k-1)][k]=max(dp[i|(1<<k-1)][k],dp[i][j]+dis[j][k]);
			}
			int tmp=(i^((1<<n)-1));
			for(re k=tmp;k;k=(k-1)&tmp){
				dp[i|k][j]=max(dp[i|k][j],dp[i][j]+val[k][j]+sum[k]);
			}
		}
	}
	cout<<sum[(1<<n)-1]<<" "<<dp[(1<<n)-1][n]<<endl;
	printf("%d",sum[(1<<n)-1]-dp[(1<<n)-1][n]);
}