NOI2015Day1 题解

2 篇文章 0 订阅

Problem A 程序自动分析

传送门

看到变量之间的相等关系具有传递性,所以不难想到思路:遇到 x i = x j x_i=x_j xi=xj的约束时用并查集把 x i x_i xi x j x_j xj代表的元素所在集合合并,遇到 x i ̸ = x j x_i\not=x_j xi̸=xj的约束时如果 x i x_i xi x j x_j xj代表的元素在同一个集合则矛盾。注意两点:

  • 先处理相等关系,再处理不等关系;
  • 由于下标范围很大,所以要离散化。

什么? T ≤ 10 , N ≤ 1 0 6 T\le 10,N\le 10^6 T10,N106?不用担心,经笔者实测,快读+压缩路径并查集可以AC。

#include<bits/stdc++.h>
using namespace std;
const int N=1000005,N2=N<<1;
int rd(){
	int a=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))a=(a<<1)+(a<<3)+(ch^48),ch=getchar();
	return a;
}
int n,tot,f[N2],lis[N2],t;
struct node{
	int l,r,op;
}nod[N];
bool cmp(node a,node b){return b.op<a.op;}
int find(int a){if(f[a])return f[a]=find(f[a]);return a;}
int main(){
	t=rd();
	while(t--){
		n=rd(),tot=0;
		for(int i=1;i<=n;i++)nod[i]=(node){rd(),rd(),rd()},lis[++tot]=nod[i].l,lis[++tot]=nod[i].r;
		sort(nod+1,nod+n+1,cmp),sort(lis+1,lis+tot+1),tot=unique(lis+1,lis+tot+1)-lis-1;
		for(int i=1;i<=n;i++)nod[i]=(node){lower_bound(lis+1,lis+tot+1,nod[i].l)-lis,lower_bound(lis+1,lis+tot+1,nod[i].r)-lis,nod[i].op};
		memset(f,0,sizeof(f));int flag=1;
		for(int i=1;i<=n;i++){
			if(nod[i].op){
				int v=find(nod[i].l),u=find(nod[i].r);
				if(v!=u)f[v]=u;
			}
			else if(find(nod[i].l)==find(nod[i].r)){puts("NO"),flag=0;break;}
		}
		if(flag)puts("YES");
	}
	return 0;
}

Problem B 软件包管理器

传送门

如果把软件之间的依赖关系抽象成树的关系,那么不难发现:

  • 安装一个软件时,要安装从它到根节点路径上的所有软件;
  • 卸载一个软件时,要卸载以它为根节点的子树中的所有软件。

用树剖+线段树即可AC。真是一道树剖入门好题啊

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
const int N=100005,N2=N<<2;
int n,q,s[N],sz[N],dfn[N],cnt,f[N],tp[N],last[N],tre[N2],lazy[N2],flag[N2];
vector<int>e[N];
void dfs1(int v){
	sz[v]=1;
	for(int i=0;i<(int)e[v].size();i++){
		int u=e[v][i];
		dfs1(u),sz[v]+=sz[u];
	}
}
void dfs2(int v,int top){
	dfn[v]=++cnt,tp[v]=top;
	int pre=0;
	for(int i=0;i<(int)e[v].size();i++){
		int u=e[v][i];
		if(sz[pre]<sz[u])pre=u;
	}
	if(pre)dfs2(pre,top);
	for(int i=0;i<(int)e[v].size();i++){
		int u=e[v][i];
		if(u!=pre)dfs2(u,u);
	}
	last[v]=cnt;
}
void pu(int c){tre[c]=tre[c<<1]+tre[c<<1|1];}
void paint(int l,int r,int v,int c){tre[c]=(r-l+1)*v,flag[c]=1,lazy[c]=v;}
void pd(int l,int r,int c){if(flag[c])paint(l,mid,lazy[c],c<<1),paint(mid+1,r,lazy[c],c<<1|1),flag[c]=0;}
void add(int l,int r,int L,int R,int v,int c){
	if(r<L||R<l)return;
	if(L<=l&&r<=R){paint(l,r,v,c);return;}
	pd(l,r,c);
	add(l,mid,L,R,v,c<<1),add(mid+1,r,L,R,v,c<<1|1);
	pu(c);
}
int main(){
	scanf("%d",&n);
	for(int i=2;i<=n;i++)scanf("%d",&f[i]),e[++f[i]].push_back(i);
	dfs1(1),dfs2(1,1);
	scanf("%d",&q);
	while(q--){
		char str[20];int lust=tre[1],a;
		scanf("%s%d",str,&a),++a;
		if(str[0]=='u')add(1,n,dfn[a],last[a],0,1);
		else{while(a)add(1,n,dfn[tp[a]],dfn[a],1,1),a=f[tp[a]];}
		printf("%d\n",abs(tre[1]-lust));
	}
	return 0;
}

Problem C 寿司晚宴

传送门

这题是这套题最难的题,也是我做的时候唯一没AC的题。

30分解法

首先要明白两个数互质的含义:两个数互质,当且仅当它们没有共同的质因子。

所以可以求出每个数有哪些质因子,然后用状压dp( a i a_i ai表示 i i i质因子的不重复集合):

  • f [ i ] [ S 1 ∣ a i ] [ S 2 ] + = f [ i − 1 ] [ S 1 ] [ S 2 ] f[i][S_1\mid a_i][S_2]+=f[i-1][S_1][S_2] f[i][S1ai][S2]+=f[i1][S1][S2],其中 S 2 ⋂ a i = ∅ S_2\bigcap a_i=\emptyset S2ai=
  • f [ i ] [ S 1 ] [ S 2 ∣ a i ] + = f [ i − 1 ] [ S 1 ] [ S 2 ] f[i][S_1][S_2\mid a_i]+=f[i-1][S_1][S_2] f[i][S1][S2ai]+=f[i1][S1][S2],其中 S 1 ⋂ a i = ∅ S_1\bigcap a_i=\emptyset S1ai=

其中第一维可以压掉。

100分解法

上面的解法中当 n n n很大时可能的质因子也很多,所以肯定会TLE&MLE。那么可不可以优化呢?答案是可以的。注意一个数 n n n最多有一个超过 n \sqrt n n 的质因子,所以可以在状压dp方程中的集合中只考虑 ≤ 500 ≈ 22.3 \le \sqrt{500}\approx22.3 500 22.3的质因子。剩下的质因子如何计算呢?

注意到一个质因子至多分给一个人,所以大质因子相同的数我们分成一块:设 f [ i ] [ S 1 ] [ S 2 ] f[i][S_1][S_2] f[i][S1][S2]是这一块之前的方案数, g [ i ] [ 0 / 1 ] [ S 1 ] [ S 2 ] g[i][0/1][S_1][S_2] g[i][0/1][S1][S2]为当前只分给第一/二个人或者不分的方案数,则转移方程如下:

  • g [ i ] [ 0 ] [ S 1 ∣ a i ] [ S 2 ] + = g [ i − 1 ] [ 0 ] [ S 1 ] [ S 2 ] g[i][0][S_1\mid a_i][S_2]+=g[i-1][0][S_1][S_2] g[i][0][S1ai][S2]+=g[i1][0][S1][S2],其中 S 2 ⋂ a i = ∅ S_2\bigcap a_i=\emptyset S2ai=
  • g [ i ] [ 1 ] [ S 1 ] [ S 2 ∣ a i ] + = g [ i − 1 ] [ 1 ] [ S 1 ] [ S 2 ] g[i][1][S_1][S_2\mid a_i]+=g[i-1][1][S_1][S_2] g[i][1][S1][S2ai]+=g[i1][1][S1][S2],其中 S 1 ⋂ a i = ∅ S_1\bigcap a_i=\emptyset S1ai=

特别的,不带大质因子的每个数单独分成一块。

如何处理 f f f数组?

  • 一块开始时把 f [ i ] [ S 1 ] [ S 2 ] f[i][S_1][S_2] f[i][S1][S2]拷贝到 g [ i ] [ 0 ] [ S 1 ] [ S 2 ] g[i][0][S_1][S_2] g[i][0][S1][S2] g [ i ] [ 1 ] [ S 1 ] [ S 2 ] g[i][1][S_1][S_2] g[i][1][S1][S2]
  • 一块结束时更新 f [ i ] [ S 1 ] [ S 2 ] = g [ i ] [ 0 ] [ S 1 ] [ S 2 ] + g [ i ] [ 1 ] [ S 1 ] [ S 2 ] − f [ i ] [ S 1 ] [ S 2 ] f[i][S_1][S_2]=g[i][0][S_1][S_2]+g[i][1][S_1][S_2]-f[i][S_1][S_2] f[i][S1][S2]=g[i][0][S1][S2]+g[i][1][S1][S2]f[i][S1][S2]要去掉谁都不给的情况

其中第一维仍然可以压掉。这样我们就解决了本题!

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=505,prime[8]={2,3,5,7,11,13,17,19},n2=256;
int n;
ll p,f[N][N],g[2][N][N],ans;
struct node{
	int p,q;
}nd[N];
bool cmp(node a,node b){if(a.p==b.p)return a.q<b.q;return a.p<b.p;}
int main(){
	scanf("%d%lld",&n,&p);
	for(int i=2;i<=n;i++){
		int i1=i;
		for(int j=0;j<8;j++)if(!(i1%prime[j])){
			nd[i].q|=1<<j;
			while(!(i1%prime[j]))i1/=prime[j];
		}
		nd[i].p=i1;
	}
	sort(nd+2,nd+n+1,cmp);
	f[0][0]=1;
	for(int i=2;i<=n;i++){
		if(nd[i].p==1||nd[i].p!=nd[i-1].p)for(int j=0;j<n2;j++)for(int k=0;k<n2;k++)g[0][j][k]=g[1][j][k]=f[j][k];
		for(int j=n2-1;~j;j--)for(int k=n2-1;~k;k--){
			if(!(k&nd[i].q))(g[0][j|nd[i].q][k]+=g[0][j][k])%=p;
			if(!(j&nd[i].q))(g[1][j][k|nd[i].q]+=g[1][j][k])%=p;
		}
		if(nd[i].p==1||nd[i].p!=nd[i+1].p)for(int j=0;j<n2;j++)for(int k=0;k<n2;k++)f[j][k]=(g[0][j][k]+g[1][j][k]-f[j][k]+p)%p;
	}
	for(int i=0;i<n2;i++)for(int j=0;j<n2;j++)if(!(i&j))(ans+=f[i][j])%=p;
	printf("%lld",ans);
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值