HNOI2015简要题解

Day1T1接水果:弄出dfs序可以发现就是要满足在一个一个矩形里面,求覆盖一个点的第k大矩形,扫描线然后整体二分即可

Code(远古代码):

#include <bits/stdc++.h>
#define N 40005
using namespace std;
struct nod
{
    int x,y,z,v,w;
    nod(int a=0,int b=0,int c=0,int d=0,int e=0){x=a,y=b,z=c,v=d,w=e;}
    bool operator<(const nod &a)const{return x<a.x;}
}a[N*3],q[N*2],t[N*2];
int n,m,k,x,y,z,g,he[N],to[N<<1],ne[N<<1],cnt,anc[N][20],dep[N],in[N],ou[N],I,tot,f[N],val[N],vl[N],ans[N];
bool cmp(nod a,nod b){return a.v<b.v;}
void add(int x,int y){to[++cnt]=y,ne[cnt]=he[x],he[x]=cnt;}
void dfs(int x)
{
    in[x]=++I;for(int i=1;i<=15;i++) anc[x][i]=anc[anc[x][i-1]][i-1];
    for(int i=he[x],y;i;i=ne[i]) if((y=to[i])!=anc[x][0]) anc[y][0]=x,dep[y]=dep[x]+1,dfs(y);
    ou[x]=I;
}
int find(int x,int y){for(int i=15;~i;i--) if((1<<i)<=y) x=anc[x][i],y-=(1<<i);return x;}
void upd(int x,int a){for(int i=x;i<=n;i+=i&-i) f[i]+=a;}
int que(int x){int ans=0;for(int i=x;i;i-=i&-i) ans+=f[i];return ans;}
void solve(int b,int e,int l,int r,int L,int R)
{
    if(b>e) return;
    if(L==R){for(int i=b;i<=e;i++) ans[q[i].z]=L;return;}
    int MID=(L+R)>>1,mid=l-1,p=l;
    for(int i=b;i<=e;i++) vl[q[i].z]=0;sort(a+l,a+r+1,cmp);
    while(mid<r&&a[mid+1].v<=MID) mid++;sort(a+l,a+mid+1);
    for(int i=b;i<=e;i++)
    {
        while(p<=mid&&a[p].x<=q[i].x) upd(a[p].y,a[p].w),upd(a[p].z+1,-a[p].w),p++;
        vl[q[i].z]+=que(q[i].y);
    }
    while(p>l) p--,upd(a[p].y,-a[p].w),upd(a[p].z+1,a[p].w);
    for(int i=(p=b);i<=e;i++) if(val[q[i].z]<=vl[q[i].z]) t[p++]=q[i];
    for(int i=(p=e);i>=b;i--) if(val[q[i].z]>vl[q[i].z]) t[p--]=q[i];
    for(int i=b;i<=e;q[i]=t[i],i++) if(~vl[q[i].z]&&val[q[i].z]>vl[q[i].z]) val[q[i].z]-=vl[q[i].z],vl[q[i].z]=-1;
    solve(b,p,l,mid,L,MID),solve(p+1,e,mid+1,r,MID+1,R);
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=2;i<=n;i++) scanf("%d%d",&x,&y),add(x,y),add(y,x);
    dfs(1);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        if(dep[x]>dep[y]) swap(x,y);
        if(dep[x]<dep[y]&&anc[g=find(y,dep[y]-dep[x]-1)][0]==x)
        {
            a[++tot]=nod(1,in[y],ou[y],z,1);
            a[++tot]=nod(in[g],in[y],ou[y],z,-1);
            a[++tot]=nod(ou[g]+1,in[y],ou[y],z,1);
        }
        else a[++tot]=nod(in[x],in[y],ou[y],z,1),a[++tot]=nod(ou[x]+1,in[y],ou[y],z,-1);
    }
    for(int i=1;i<=k;i++) scanf("%d%d%d",&x,&y,&val[i]),q[i]=nod(in[x],in[y],i,0,0),q[i+k]=nod(in[y],in[x],i,0,0);
    sort(q+1,q+k*2+1),solve(1,k*2,1,tot,1,1000000000);
    for(int i=1;i<=k;i++)printf("%d\n",ans[i]);
}

Day1T2菜肴制作:
一开始想的是用优先队列来做,但是不对
我们要求小的尽量在前面,而不是前面的尽量小,优先队列符合后者
考虑转化一下,前面的尽量小就是大的尽量在后面,同理,小的尽量在前面就是后面的尽量大
所以建个反向图跑拓扑序就完了

Code:

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
const int N=1e5+5;
int in[N],vis[N<<1],head[N],nxt[N<<1],tot=0;
inline void add(int x,int y){vis[++tot]=y;nxt[tot]=head[x];head[x]=tot;in[y]++;}
priority_queue<int>q;
int ans[N],cnt=0,n;
inline bool topsort(){
	for(int i=1;i<=n;i++) if(!in[i]) q.push(i);
	while(!q.empty()){
		int x=q.top();q.pop();ans[++cnt]=x;
		for(int i=head[x];i;i=nxt[i]){
			int y=vis[i];--in[y];
			if(!in[y]) q.push(y);
		}
	}
	for(int i=1;i<=n;i++) if(in[i]) return false;
	return true;
}
int main(){
	int t=read();
	while(t--){
		memset(in,0,sizeof(in));
		memset(head,0,sizeof(head));
		cnt=tot=0;
		n=read();int m=read();
		for(int x,y,i=1;i<=m;i++) x=read(),y=read(),add(y,x);
		if(!topsort()) {puts("Impossible!");continue;}
		for(int i=n;i;i--) cout<<ans[i]<<" ";
		cout<<"\n";
	}
	return 0;
}

Day1T3落忆枫音:如果没有额外的边,那么考虑乘法原理得到答案就是 ∏ i = 2 n i n [ i ] \prod_{i=2}^n{in[i]} i=2nin[i]
加上额外的边后,可能生成环(如果没有就一样的)
多出来的选择就是环上的点都选了环上的父亲
那么我们设 f [ x ] f[x] f[x] x x x s s s(给出的额外边)上产生的贡献,转移就是
f [ x ] = ∑ f [ t o [ x ] ] / i n [ x ] f[x]=\sum{f[to[x]]/in[x]} f[x]=f[to[x]]/in[x]

Code:

#include<bits/stdc++.h>
#define ll long long
#define mod 1000000007
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
const int N=1e5+5;
int vis[N<<1],head[N<<1],nxt[N<<1],tot=0;
int in[N],sum=1;
inline void add(int x,int y){vis[++tot]=y;nxt[tot]=head[x];head[x]=tot;}
inline int ksm(int a,int b){int res=1; for(;b;b>>=1){if(b&1) res=(ll)res*a%mod;a=(ll)a*a%mod;} return res;}
int pt[N],s,t,f[N];
void dfs(int v){
	if(pt[v]) return;
	pt[v]=1;
	if(v==t) {f[v]=(ll)sum*ksm(in[v],mod-2)%mod;return;}
	for(int i=head[v];i;i=nxt[i])
		dfs(vis[i]),f[v]=(f[v]+f[vis[i]])%mod;
	f[v]=(ll)f[v]*ksm(in[v],mod-2)%mod;
}
int ans=1;
int main(){
	int n=read(),m=read();s=read(),t=read();
	for(int x,y,i=1;i<=m;i++) x=read(),y=read(),add(y,x),++in[y];
	in[1]++;
	for(int i=1;i<=n;++i) {
		if(i==t) ans=(ll)(in[i]+1)*ans%mod;
		else ans=(ll)in[i]*ans%mod;
		sum=(ll)in[i]*sum%mod;
	}
	dfs(s);
	ans=(ans+mod-f[s])%mod;
	cout<<ans<<endl;
	return 0;
}

Day2T1开店:可以点分树做,也可以树链剖分+主席树
询问 ∑ v ∈ [ l , r ] d i s ( u , v ) \sum_{v∈[l,r]}{dis(u,v)} v[l,r]dis(u,v)就是求 ( r − l + 1 ) ∗ d e p [ u ] + ∑ v ∈ [ l , r ] d e p [ v ] − 2 ∗ ∑ v ∈ [ l , r ] d e p [ l c a ( u , v ) ] (r-l+1)*dep[u]+\sum_{v∈[l,r]}dep[v]-2*\sum_{v∈[l,r]}{dep[lca(u,v)]} (rl+1)dep[u]+v[l,r]dep[v]2v[l,r]dep[lca(u,v)],前面两项 O ( 1 ) O(1) O(1),最后一项实际上是把每个点到根的路径上的所有点打上一个标记,然后询问u到根节点的路径上的标记数,如果没有年龄的限制,就直接线段树做了
有限制的话,按照年龄排序建主席树,然后区间查询就很简单了

Code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
const int N=1.5e5+5;
ll presum[N],sum[N];
namespace President_tree{
	struct seg{int ls,rs,cnt;ll sum;}tr[N*150];int tot;
	#define lc(k) tr[k].ls
	#define rc(k) tr[k].rs
	void build(int &k,int l,int r){
		k=++tot;
		if(l==r) return;
		int mid=l+r>>1;
		build(lc(k),l,mid);build(rc(k),mid+1,r);
	}
	void ins(int &rt1,int l,int r,int ql,int qr){
		tr[++tot]=tr[rt1];
		if(l==ql && r==qr){++tr[rt1=tot].cnt;return;}
		tr[rt1=tot].sum+=sum[qr]-sum[ql-1];
		int mid=l+r>>1;
		if(qr<=mid) ins(lc(rt1),l,mid,ql,qr);
		else if(ql>mid) ins(rc(rt1),mid+1,r,ql,qr);
		else ins(lc(rt1),l,mid,ql,mid),ins(rc(rt1),mid+1,r,mid+1,qr);
	}
	ll query(int rt,int l,int r,int ql,int qr){
		ll res=1ll*(sum[qr]-sum[ql-1])*tr[rt].cnt;
		if(l==ql && r==qr) return res+tr[rt].sum;
		int mid=l+r>>1;
		if(qr<=mid) return res+query(lc(rt),l,mid,ql,qr);
		else if(ql>mid) return res+query(rc(rt),mid+1,r,ql,qr);
		else return res+query(lc(rt),l,mid,ql,mid)+query(rc(rt),mid+1,r,mid+1,qr);
	}
}
using namespace President_tree;
int n,rt[N];
namespace tree{
	int vis[N<<1],nxt[N<<1],head[N],c[N<<1],tot=0;
	inline void add(int x,int y,int z){vis[++tot]=y;nxt[tot]=head[x];head[x]=tot;c[tot]=z;}
	int siz[N],fa[N],hson[N],dep[N],pt[N];
	void dfs1(int v){
		pt[v]=siz[v]=1;
		for(int i=head[v];i;i=nxt[i]){
			int y=vis[i];
			if(pt[y]) continue;
			fa[y]=v;dep[y]=dep[v]+c[i];
			dfs1(y);
			siz[v]+=siz[y];
			if(siz[y]>siz[hson[v]]) hson[v]=y;
		}
	}
	int dfn[N],id[N],sign=0,top[N];
	void dfs2(int v){
		dfn[v]=++sign,sum[sign]=dep[v]-dep[fa[v]];
		if(hson[v]) top[hson[v]]=top[v],dfs2(hson[v]);
		for(int i=head[v];i;i=nxt[i])
			if(!top[vis[i]]) top[vis[i]]=vis[i],dfs2(vis[i]);
	}
	inline ll ask(int rt1,int v){
		ll res=0;
		while(top[v]!=1) res+=query(rt[rt1],1,n,dfn[top[v]],dfn[v]),v=fa[top[v]];
		return res+query(rt[rt1],1,n,1,dfn[v]);
	}
}
using namespace tree;
struct point{
	int age,id;
	point(){}
	point(int _age,int _id):age(_age),id(_id){}
}p[N];
inline bool operator < (point a,point b){return a.age==b.age?a.id<b.id:a.age<b.age;}
ll ans=0;
int main(){
	n=read();int q=read(),mod=read();
	for(int x,i=1;i<=n;i++) x=read(),p[i]=point(x,i);
	sort(p+1,p+n+1);
	for(int x,y,z,i=1;i<n;i++){
		x=read();y=read();z=read();
		add(x,y,z);add(y,x,z);
	}
	dfs1(1);top[1]=1;dfs2(1);
	for(int i=1;i<=n;i++) sum[i]+=sum[i-1],presum[i]=presum[i-1]+dep[p[i].id];
	build(rt[0],1,n);
	for(int i=1;i<=n;i++){
		int v=p[i].id;rt[i]=rt[i-1];
		while(top[v]!=1) ins(rt[i],1,n,dfn[top[v]],dfn[v]),v=fa[top[v]];
		ins(rt[i],1,n,1,dfn[v]);
	}
	while(q--){
		int u=read(),l=read(),r=read();
		l=(1ll*l+ans)%mod,r=(1ll*r+ans)%mod;
		if(l>r) swap(l,r);
		l=lower_bound(p+1,p+n+1,point(l,0))-p;
		r=upper_bound(p+1,p+n+1,point(r,n))-p-1;
		ans=1ll*(r-l+1)*dep[u]+presum[r]-presum[l-1]-2*(ask(r,u)-ask(l-1,u));
		cout<<ans<<"\n";
	}
	return 0;
}

Day2T2试验比较:实际上是给出一个森林,等号的条件用并查集缩起来,把每个森林的根连向0号节点就是一棵树,要给出一个序列使得父亲严格在儿子前面,考虑树形DP, f [ i ] [ j ] f[i][j] f[i][j]表示子树 i i i中的排列有恰好 j j j个小于号的方案数,每次加入一个v的一个子树y形成一个恰好 k k k个小于号的排列的转移就是
t m p [ k ] = ∑ i = 1 s i z [ v ] ∑ j = 1 s i z [ y ] f [ v ] [ i ] ∗ f [ y ] [ j ] ∗ C k i C i j − ( k − i ) tmp[k]=\sum_{i=1}^{siz[v]}\sum_{j=1}^{siz[y]}f[v][i]*f[y][j]*C_{k}^iC_{i}^{j-(k-i)} tmp[k]=i=1siz[v]j=1siz[y]f[v][i]f[y][j]CkiCij(ki)
其中tmp是一个中间数组, s i z [ v ] siz[v] siz[v] v v v的已经转移过的儿子的 s i z siz siz和,且 k k k满足 m a x ( i , j ) ≤ k ≤ i + j max(i,j)\le k \le i+j max(i,j)ki+j
转移的意义是将i个白球,j个黑球放入k个盒子中,同一个盒子中不能有同色球,且每个盒子不为空的方案数,即先选 i i i个盒子放白球,剩下的 k − i k-i ki个盒子放黑球,现在剩下 j − ( k − i ) j-(k-i) j(ki)个黑球,放到 i i i个放过白球的盒子中,然后就这样转移就完了

Code:

#include<bits/stdc++.h>
#define mod 1000000007
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
const int N=105;
inline int Add(int x,int y){x+=y;if(x>=mod) x-=mod;return x;}
inline void inc(int &x,int y){x+=y;if(x>=mod) x-=mod;}
inline int mul(int x,int y){return 1ll*x*y%mod;}
inline void Mul(int &x,int y){x=1ll*x*y%mod;}
int vis[N],nxt[N],head[N],tot=0;
inline void add(int x,int y){vis[++tot]=y;nxt[tot]=head[x];head[x]=tot;}
int f[N][N];
int siz[N],C[N][N],tmp[N];
void dp(int v){
	int flag=1;
	for(int e=head[v];e;e=nxt[e]){
		int y=vis[e];dp(y);
		if(flag){
			flag=0;siz[v]+=siz[y];
			for(int i=1;i<=siz[v];i++) f[v][i]=f[y][i];
		}
		else{
			memset(tmp,0,sizeof(tmp));
			for(int i=1;i<=siz[v];i++) if(f[v][i])
				for(int j=1;j<=siz[y];j++) if(f[y][j])
					for(int k=max(i,j);k<=i+j;k++)
						inc(tmp[k],mul(mul(f[v][i],f[y][j]),mul(C[k][i],C[i][j-(k-i)])));
			siz[v]+=siz[y];
			for(int i=1;i<=siz[v];i++) f[v][i]=tmp[i];
		}
	}
	if(flag) f[v][0]=1;++siz[v];
    for(int i=siz[v];i;i--) f[v][i]=f[v][i-1];
}
int fa[N],Fa[N],isrt[N];
int get(int x){return fa[x]?fa[x]=get(fa[x]):x;}
int pt[N];
bool check(int v){
	if(pt[v]) return false;
	pt[v]=1;
	for(int i=head[v];i;i=nxt[i]) if(!check(vis[i])) return false;
	return true;
}
char ch[5];
int main(){
	int n=read(),m=read();
	for(int i=0;i<=n;i++){
		C[i][0]=1;
		for(int j=1;j<=i;j++) C[i][j]=Add(C[i-1][j],C[i-1][j-1]);
	}
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%s%d",&x,ch,&y);
		if(ch[0]=='='){
			int xx=get(x),yy=get(y);
			if(xx!=yy) fa[xx]=yy,isrt[xx]=1,Fa[yy]=max(Fa[yy],Fa[xx]);
		}
		else Fa[y]=x;
	}
	for(int i=1;i<=n;i++) if(!isrt[i]) add(get(Fa[i]),i);
	for(int i=0;i<=n;i++) if(!pt[i]) if(!check(i)) {puts("0");return 0;}
	dp(0);
	int ans=0;
	for(int i=1;i<=siz[0];i++) inc(ans,f[0][i]);
	cout<<ans;
	return 0;
}

Day2T3亚瑟王:
考虑算概率最后乘上d,因为直接计算每张卡每轮的概率不现实(无法转移),所以考虑一个整体并且可以表示出每张卡的状态, f [ i ] [ j ] f[i][j] f[i][j]表示 m m m轮中前 i i i张卡用了 j j j张的概率,则要表示一张卡 i i i m m m轮中出现过的概率就是 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j]乘上必须选 i i i的概率,不选 i i i的概率就是在后面 m − j m-j mj轮中都跳过了 i i i的概率,就是 ( 1 − p [ i ] ) m − j (1-p[i])^{m-j} (1p[i])mj,用1减去不选的概率就是选的概率
然后转移就是考虑选不选 i i i,选不选 i i i的概率就如上面所述,方程为
f [ i ] [ j ] = f [ i − 1 ] [ j ] ∗ ( 1 − p [ i ] ) m − j + f [ i − 1 ] [ j − 1 ] ∗ ( 1 − ( 1 − p [ i ] ) m − j + 1 ) ) f[i][j]=f[i-1][j]*(1-p[i])^{m-j}+f[i-1][j-1]*(1-(1-p[i])^{m-j+1})) f[i][j]=f[i1][j](1p[i])mj+f[i1][j1](1(1p[i])mj+1))

Code:

#include<bits/stdc++.h>
#define db double
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
const int N=225;
inline db ksm(db a,int b){db res=1.0;for(;b;b>>=1,a*=a) if(b&1) res*=a;return res;}
db f[N][N],p[N];
int d[N];
int n,m;
int main(){
	int t=read();
	while(t--){
		memset(f,0,sizeof(f));
		n=read();m=read();
		for(int i=1;i<=n;++i){
			scanf("%lf",&p[i]);
			d[i]=read();
		}
		f[0][0]=1;
		for(int i=1;i<=n;++i)
			for(int j=0;j<=min(i,m);++j){
				if(!j) f[i][j]=f[i-1][j]*ksm(1-p[i],m-j);
				else f[i][j]=f[i-1][j]*ksm(1-p[i],m-j)+f[i-1][j-1]*(1-ksm(1-p[i],m-j+1));
			}
		db res=0;
		for(int i=1;i<=n;++i){
			db now=0;
			for(int j=0;j<=min(i-1,m);++j) if(j!=m) now+=f[i-1][j]*(1-ksm(1-p[i],m-j));
			res+=now*d[i];
		}
		printf("%.10lf\n",res);
	}
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值