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 T≤10,N≤106?不用担心,经笔者实测,快读+压缩路径并查集可以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][S1∣ai][S2]+=f[i−1][S1][S2],其中 S 2 ⋂ a i = ∅ S_2\bigcap a_i=\emptyset S2⋂ai=∅
- 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][S2∣ai]+=f[i−1][S1][S2],其中 S 1 ⋂ a i = ∅ S_1\bigcap a_i=\emptyset S1⋂ai=∅
其中第一维可以压掉。
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][S1∣ai][S2]+=g[i−1][0][S1][S2],其中 S 2 ⋂ a i = ∅ S_2\bigcap a_i=\emptyset S2⋂ai=∅
- 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][S2∣ai]+=g[i−1][1][S1][S2],其中 S 1 ⋂ a i = ∅ S_1\bigcap a_i=\emptyset S1⋂ai=∅
特别的,不带大质因子的每个数单独分成一块。
如何处理 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;
}