Problem A 程序自動分析
看到變量之間的相等關係具有傳遞性,所以不難想到思路:遇到的約束時用並查集把和代表的元素所在集合合併,遇到的約束時如果和代表的元素在同一個集合則矛盾。注意兩點:
- 先處理相等關係,再處理不等關係;
- 由於下標範圍很大,所以要離散化。
什麼??不用擔心,經筆者實測,快讀+壓縮路徑並查集可以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(表示質因子的不重複集合):
- ,其中
- ,其中
其中第一維可以壓掉。
100分解法
上面的解法中當很大時可能的質因子也很多,所以肯定會TLE&MLE。那麼可不可以優化呢?答案是可以的。注意一個數最多有一個超過的質因子,所以可以在狀壓dp方程中的集合中只考慮的質因子。剩下的質因子如何計算呢?
注意到一個質因子至多分給一個人,所以大質因子相同的數我們分成一塊:設是這一塊之前的方案數,爲當前只分給第一/二個人或者不分的方案數,則轉移方程如下:
- ,其中
- ,其中
特別的,不帶大質因子的每個數單獨分成一塊。
如何處理數組?
- 一塊開始時把拷貝到與。
- 一塊結束時更新(要去掉誰都不給的情況)
其中第一維仍然可以壓掉。這樣我們就解決了本題!
#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;
}