noip模拟39

\(\color{white}{\mathbb{百般红紫博众爱,正是芳菲斗艳时,名之以:牡丹}}\)


8.14.png

%%% szs巨佬AK

\(t1\)\(t4\) 都会做,剩下两道好像都不太会,再次扫描到知识盲区——竞赛图
特殊的 \(dp\) 题还是很难设计出一个标准的状态


(t1过水已隐藏)

B. 竞赛图

数据范围小一看是状压,然而并不知道竞赛图的性质,于是瞎推一阵后只打了个 \(tarjan\) 暴力,一测 \(19\) 的点跑了 \(2.3s\),以为能蹭过 \(60\) 的点(然而最后并没有)

竞赛图缩点后是链状,那么对于一个非强联通图,一定存在且仅存在一个子图 \(S\) 向补集的连边都是朝向补集方向的。那么对于每个集合 \(S\),对其所有 所有点的出边交集的子集标记为不合法即可

代码实现
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e7+5;
int n,all,op,ans,t,to[maxn];
bool flag[maxn];
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*f;
}
void init(){
	for(int i=0;i<=all;i++)flag[i]=1,to[i]=0;
	ans=0;
	return ;
}
int main(){
	t=read();
	for(int i=1;i<=2e7;i++)flag[i]=1;
	while(t--){
		init();
		n=read();
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				op=read();
				if(op)to[1<<(i-1)]|=(1<<(j-1));
			}
		}
		all=(1<<n)-1;
		to[0]=all;
		for(int S=1;S<=all;S++){
			int p=(S&-S);
			to[S]=(to[p]&to[S^p]);
//			cout<<S<<" "<<to[S]<<endl;
		}
		for(int S=1;S<=all;S++){
			if(flag[S]){
				for(int T=to[S];T;T=(T-1)&to[S]){
					flag[T|S]=false;
				}
			}
		}
		for(int S=0;S<=all;S++)if(flag[S])ans++;
		cout<<ans<<endl;
	}
	return 0;
}

C. 糖果

\(dp\) 神仙题

首先可以只关心 \(C\) 组选了哪些数,对于所选数的位置以及未选数的排列可以计算
考虑对于一个确定的数组排列,第一次一定是放第一个数,那么第一次前两个数的放置有 \((n-1)(n-2)\) 种,第二次摆放后有 \((n-4)(n-5)\) 种方法,那么最后答案只需要乘上 \(\prod (3i-1)(3i-2)\) 即可。

考虑 \(dp\) 统计,设 \(dp[i][j][k]\) 表示第一行扫到第 \(i\) 个位置,第二行扫到第 \(j\) 个位置,第三行有 \(k\) 个数在候选集合里的方案数。最后加一为维 \(0/1\) 表示当前更新是第几行,然后 \(dp\) 一列一列转移,先选第一行,再选第二行,用 \(0\) 转移到 \(1\),用 \(1\) 转移到下一个 \(0\)

考虑转移:

对于第一行的一个数可以选当且仅当第一行当前位置的数在第二行出现的位置晚于 \(j\),否则只能光移不选,于是有一下转移:

光移不选:

\[f[i+1][j][k][0]+=f[i][j][k][0] \]

选择当前,更新第二行:

\[f[i][j][k][1]+=f[i][j][k][0] \]

并更新第三行的备选集合(因为用了一个候选集合就少了一个):

\[f[i+1][j][k-1][0]+=f[i][j][k][0] \]

第二行类似,更新备选集合为:

\[f[i+1][j][k+1][0]+=f[i][j][k][1] \]

因为右移了一位,相当于候选集合里多了一个数

于是整个 \(dp\) 复杂度是 \(n^3\)

代码实现
#include<bits/stdc++.h>
using namespace std;
const int maxn=405;
const int mod=1e9+7;
int n,a[maxn],posa[maxn],b[maxn],posb[maxn],f[maxn][maxn][maxn][2],ans;
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*f;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++)a[i]=read(),posa[a[i]]=i;
	for(int i=1;i<=n;i++)b[i]=read(),posb[b[i]]=i;
	f[1][1][0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			for(int k=0;k<=n/3;k++){
				if(f[i][j][k][0]){
					if(posb[a[i]]<j){
						f[i+1][j][k][0]=(f[i+1][j][k][0]+f[i][j][k][0])%mod;
					}
					else {
						f[i][j][k][1]=(f[i][j][k][1]+f[i][j][k][0])%mod;
						if(k)f[i+1][j][k-1][0]=(f[i+1][j][k-1][0]+1ll*f[i][j][k][0]*k%mod)%mod;
					}	
				}
				if(f[i][j][k][1]){
					if(posa[b[j]]<i){
						f[i][j+1][k][1]=(f[i][j+1][k][1]+f[i][j][k][1])%mod;
					}
					else if(b[j]!=a[i]){
						if(k)f[i][j+1][k-1][1]=(f[i][j+1][k-1][1]+1ll*f[i][j][k][1]*k%mod)%mod;
						f[i+1][j+1][k+1][0]=(f[i+1][j+1][k+1][0]+f[i][j][k][1])%mod;
					}
				}
			}
		}
		
	}
	for(int i=1;i<=n;i++)ans=(ans+f[n+1][i][0][0])%mod;
	for(int i=1;i<=n/3;i++)ans=1ll*ans*(3*i-1)%mod*(3*i-2)%mod;
	cout<<ans;
	return 0;
}

D. 树

国赛 \(d1t1\) 原题,一条黑边相当于两端点更新时间不同,那么每次修改把路径上的节点时间戳都更新即可

代码实现
#include<bits/stdc++.h>
using namespace std;
const int maxn=6e5+5;
const int maxm=1e6+5;
int n,q,x,y,hd[maxn],cnt,siz[maxn],fa[maxn],tp[maxn],son[maxn],op,ans,re[maxn],tot,id[maxn],dep[maxn];
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*f;
}
struct Edge{
	int nxt,to;
}edge[maxm];
void add(int u,int v){
	edge[++cnt].nxt=hd[u];
	edge[cnt].to=v;
	hd[u]=cnt;
	return ;
}
void dfs(int u){
	siz[u]=1;
	for(int i=hd[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(v==fa[u])continue;
		fa[v]=u;
		dep[v]=dep[u]+1;
		dfs(v);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
	return ;
}
void dfs1(int u,int top){
	tp[u]=top;
	id[u]=++tot;
	re[tot]=u;
	if(son[u])dfs1(son[u],top);
	for(int i=hd[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(v==fa[u]||v==son[u])continue;
		dfs1(v,v);
	}
	return ;
}
namespace p1{
	int tim,dfn[maxn],d;
	int lca(int x,int y){
		while(tp[x]!=tp[y]){
			if(dep[tp[x]]<dep[tp[y]])swap(x,y);
			x=fa[tp[x]];
		}
		if(dep[x]>dep[y])swap(x,y);
		return x;
	}
	void start(){
		for(int i=1;i<=n;i++)dfn[i]=i;
		tim=n;
		for(int i=1;i<=q;i++){
			op=read();
			x=read();
			y=read();
			d=lca(x,y);
			if(op==1){
				tim++;
				while(1){
//					cout<<x<<" ";
					dfn[x]=tim;
					if(x==d)break;
					x=fa[x];
				}
				while(y!=d){
					dfn[y]=tim;
					y=fa[y];
				}
			}
			else{
				ans=0;
				while(x!=d){
					ans+=(dfn[x]!=dfn[fa[x]]);
					x=fa[x];
				}
				while(y!=d){
					ans+=(dfn[y]!=dfn[fa[y]]);
					y=fa[y];
				}
				printf("%d\n",ans);
			}
		}
		return ;
	}
}
namespace p2{
	int dfn[maxn],tim;
	struct Seg{
		int l,r,lc,rc,num,lazy;
	}t[maxn*4];
	void merge(Seg &p,Seg x,Seg y){
		if(!x.num){
			p=y;
			return ;
		}
		if(!y.num){
			p=x;
			return ;
		}
		p.num=x.num+y.num-(x.rc==y.lc);
		p.lc=x.lc;
		p.rc=y.rc;
		return ;
	}
	void build(int p,int l,int r){
		t[p].l=l;
		t[p].r=r;
		if(l==r){
			t[p].num=1;
			t[p].lc=t[p].rc=dfn[re[l]];
			return ;
		}
		int mid=t[p].l+t[p].r>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		merge(t[p],t[p<<1],t[p<<1|1]);
		return ;
	}
	void dospread(int p,int w){
		t[p].num=1;
		t[p].lc=t[p].rc=w;
		t[p].lazy=w;
		return ;
	}
	void spread(int p){
		dospread(p<<1,t[p].lazy);
		dospread(p<<1|1,t[p].lazy);
		t[p].lazy=0;
		return ;
	}
	void change(int p,int l,int r,int w){
		if(t[p].l>=l&&t[p].r<=r){
			dospread(p,w);
			return ;
		}
		if(t[p].lazy)spread(p);
		int mid=t[p].l+t[p].r>>1;
		if(l<=mid)change(p<<1,l,r,w);
		if(r>mid)change(p<<1|1,l,r,w);
		merge(t[p],t[p<<1],t[p<<1|1]);
		return ;
	}
	void modi(int x,int y){
		while(tp[x]!=tp[y]){
			if(dep[tp[x]]<dep[tp[y]])swap(x,y);
			change(1,id[tp[x]],id[x],tim);
			x=fa[tp[x]];
		}
		if(dep[x]<dep[y])swap(x,y);
		change(1,id[y],id[x],tim);
//		cout<<"ppp "<<x<<" "<<y<<" "<<id[y]<<" "<<id[x]<<" "<<t[1].num<<endl;
		return ;
	}
	Seg ask(int p,int l,int r){
//		cout<<l<<" "<<r<<" "<<t[p].l<<" "<<t[p].r<<endl;
		if(t[p].l>=l&&t[p].r<=r)return t[p];
		if(t[p].lazy)spread(p);
		int mid=t[p].l+t[p].r>>1;
		Seg ans;
		ans.num=ans.lc=ans.rc=0;
		if(l<=mid)ans=ask(p<<1,l,r);
		if(r>mid)merge(ans,ans,ask(p<<1|1,l,r));
		return ans;
	}
	int que(int x,int y){
		Seg ansx,ansy;
		ansx.num=ansx.lc=ansx.rc=ansy.num=ansy.lc=ansy.rc=0;
		while(tp[x]!=tp[y]){
			if(dep[tp[x]]<dep[tp[y]])swap(x,y),swap(ansx,ansy);
			merge(ansx,ask(1,id[tp[x]],id[x]),ansx);
//			cout<<x<<" "<<tp[tp[x]]<<" "<<id[x]<<" "<<id[x]<<" "<<ansx.num<<endl;
			x=fa[tp[x]];
		}
		if(dep[x]<dep[y])swap(x,y),swap(ansx,ansy);
		merge(ansx,ask(1,id[y],id[x]),ansx);
//		cout<<x<<" "<<y<<" "<<id[y]<<" "<<id[x]<<" "<<ansx.num<<" "<<ansy.num<<endl;
//		merge(ansx,ansx,ansy);
		return ansx.num+ansy.num-(ansx.lc==ansy.lc)-1;//ansx.num-1;
	}
	void start(){
		for(int i=1;i<=n;i++)dfn[i]=i;
		tim=n;
		build(1,1,n);
//		cout<<t[1].num<<endl;
//		cout<<"hhh"<<endl;
		for(int i=1;i<=q;i++){
			op=read();
			x=read();
			y=read();
			if(op==1){唯有牡丹真国色,花开时节动京城。
				tim++;
				modi(x,y);
			}
			else{
				printf("%d\n",que(x,y));
			}
		}
		return ;
	}
}
int main(){
//	freopen("ex_tree3.in","r",stdin);
//	freopen("my.out","w",stdout);
	n=read();
	for(int i=1;i<=n-1;i++){
		x=read();
		y=read();
		add(x,y);
		add(y,x);
	}
	dfs(1);
	dfs1(1,1);
	q=read();
//	if(n<=1000)p1::start();
//	else 
	p2::start();
	return 0;
}

\(\color{white}{\mathbb{唯有牡丹真国色,开花时节动京城。}}\)

posted @ 2021-08-14 20:45  y_cx  阅读(79)  评论(0编辑  收藏  举报