NOI2015 题解

度过了神奇的学考时间,又回到了OI战场上了...

先刷了刷NOI2015,感觉好像不是很难的样子。

 

Day1

T1 程序自动分析

AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=4195

唔,给你相同的条件和不相同的条件,然后要你判断是不是有矛盾...好像只要先把所有的相同的用某个神奇的数据结构弄到一起,然后看一下要求不同的里面有没有在同一个联通快里的就行了...

这个神奇的数据结构,不就是并查集么?...

然后由于给的数字有点大,就需要离散化一下再并查集存起来...

好良心的送分题

//BZOJ 4195
 
#include<cstdio>
#include<cstring>
#include<algorithm>
 
using namespace std;
 
const int maxn=1000010;
 
inline int in(){
    int x=0;char ch=getchar();
    while(ch>'9' || ch<'0') ch=getchar();
    while(ch<='9' && ch>='0') x=x*10+ch-'0',ch=getchar();
    return x;
}
 
int n,sz,tp;
int tmp[maxn<<1],Hash[maxn<<1];
int p[maxn<<1];
 
struct Node{
    int x,y;
    bool tp;
}s[maxn];
 
int Find_Hash(int x){
    int l=1,r=tp,mid;
    while(r-l>1){
        mid=(l+r)>>1;
        if(Hash[mid]>x) r=mid;
        else if(Hash[mid]<x) l=mid;
        else if(Hash[mid]==x) return mid;
    }
    if(Hash[l]==x) return l;
    return r;
}
 
bool cmp(const Node &A,const Node &B){
    return A.tp>B.tp;
}
 
inline int find(int x){
    int r=x,pre;
    while(p[r]!=r) r=p[r];
    while(x!=r)
        pre=p[x],p[x]=r,x=pre;
    return r;
}
 
int main(){
#ifndef ONLINE_JUDGE
    freopen("prog.in","r",stdin);
    freopen("prog.out","w",stdout);
#endif
 
    int Kase=in();
    int fx,fy;
     
    while(Kase--){
        n=in();
         
        sz=0;
        for(int i=1;i<=n;i++){
            s[i].x=in(),s[i].y=in(),s[i].tp=in();
            tmp[++sz]=s[i].x,tmp[++sz]=s[i].y;
        }
        sort(tmp+1,tmp+sz+1);
        Hash[tp=1]=tmp[1];
        for(int i=2;i<=sz;i++)
            if(tmp[i]!=tmp[i-1]) Hash[++tp]=tmp[i];
        sort(s+1,s+n+1,cmp);
         
        for(int i=1;i<=tp;i++) p[i]=i;
        int rec=0,fool=0;
        for(int i=1;s[i].tp==1 && i<=n;i++){
            fx=find(Find_Hash(s[i].x)),fy=find(Find_Hash(s[i].y));
            p[fx]=fy;
            rec=i;
        }
        for(int i=rec+1;i<=n;i++){
            fx=find(Find_Hash(s[i].x)),fy=find(Find_Hash(s[i].y));
            if(fx==fy) {fool=1;puts("NO");break;}
        }
         
        if(!fool) puts("YES");
    }
     
    return 0;
}
View Code


T2 软件包管理器

AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=4196

给你一颗树,刚开始所有节点都是0状态,有两种操作:

操作1:把点x到根的所有点全部改成1,问需要改动几个

操作2:把点x的子树全部改成0,问需要改动几个

唔,一个问一条链,一个问一棵子树,而且都是统一赋值,感觉就差不告诉你要打树链剖分了...然后改动几个也就是问一下改动前后的树上值的总和的变化...

好良心的模板题

//BZOJ 4196
 
#include<cstdio>
#include<cstring>
#include<algorithm>
 
using namespace std;
 
const int maxn=100010;
 
inline int in(){
    int x=0;char ch=getchar();
    while(ch>'9' || ch<'0') ch=getchar();
    while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}
 
struct Node{
    int data,next;
}node[maxn];
 
#define now node[point].data
#define then node[point].next
 
struct Tree{
    int sm,pt;
    Tree(){pt=-1;}
}s[maxn*17];
 
int n,cnt,Idex;
int head[maxn],Sz[maxn],Son[maxn],fa[maxn];
int id[maxn],el[maxn],hd[maxn];
 
void addedge(int u,int v){
    node[cnt].data=v,node[cnt].next=head[u],head[u]=cnt++;
}
 
void dfs1(int x){
    Sz[x]=1;Son[x]=-1;
    for(int point=head[x];point!=-1;point=then){
        dfs1(now);Sz[x]+=Sz[now];
        if(Son[x]<0 || Sz[now]>Sz[Son[x]]) Son[x]=now;
    }
}
 
void dfs2(int x,int tp){
    id[x]=++Idex;hd[x]=tp;
    if(Son[x]<0) {el[x]=Idex;return;}
    dfs2(Son[x],tp);
    for(int point=head[x];point!=-1;point=then){
        if(now!=Son[x])
            dfs2(now,now);
    }
    el[x]=Idex;
}
 
void Push_down(int o,int l,int r){
    if(s[o].pt!=-1){
        int mid=(l+r)>>1;
        s[o<<1].pt=s[o].pt;s[o<<1].sm=(mid-l+1)*s[o].pt;
        s[o<<1|1].pt=s[o].pt;s[o<<1|1].sm=(r-mid)*s[o].pt;
        s[o].pt=-1;
    }
}
 
void Update(int o){
    s[o].sm=s[o<<1].sm+s[o<<1|1].sm;
}
 
void Modify(int o,int l,int r,int al,int ar,int d){
    Push_down(o,l,r);
     
    if(l==al && r==ar){s[o].pt=d,s[o].sm=(r-l+1)*d;return ;}
     
    int mid=(l+r)>>1;
     
    if(al>mid) Modify(o<<1|1,mid+1,r,al,ar,d);
    else if(ar<=mid) Modify(o<<1,l,mid,al,ar,d);
    else Modify(o<<1|1,mid+1,r,mid+1,ar,d),Modify(o<<1,l,mid,al,mid,d);
     
    Update(o);
}
 
int Ask(int o,int l,int r,int x){
    Push_down(o,l,r);
    if(l==r) return s[o].sm;
    int mid=(l+r)>>1;
    if(x>mid) return Ask(o<<1|1,mid+1,r,x);
    return Ask(o<<1,l,mid,x);
}
 
void Add(int x){
    int last;
    while(x){
        Modify(1,1,n,id[hd[x]],id[x],1);
        x=fa[hd[x]];
    }
}
 
void Del(int x){
    Modify(1,1,n,id[x],el[x],0);
}
 
int main(){
#ifndef ONLINE_JUDGE
    freopen("manager.in","r",stdin);
    freopen("manager.out","w",stdout);
#endif
 
    int x,Kase,sum1,sum2;
    char ord[10];
 
    n=in();
    for(int i=1;i<=n;i++) head[i]=-1;
 
    for(int i=2;i<=n;i++)
        x=in(),addedge(x+1,i),fa[i]=x+1;
 
    dfs1(1);
    dfs2(1,1);
     
    Kase=in();
     
    while(Kase--){

        scanf("%s%d",ord,&x);x++;
        if(ord[0]=='i'){
            if(Ask(1,1,n,id[x])==1){puts("0");}
            else{
                sum1=s[1].sm;
                Add(x);
                printf("%d\n",s[1].sm-sum1);
            }
        }
        else{
            if(Ask(1,1,n,id[x])==0){puts("0");}
            else{
                sum1=s[1].sm;
                Del(x);
                printf("%d\n",sum1-s[1].sm);
            }
        }
    }
     
    return 0;
}
View Code


T3 寿司晚宴

AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=4197

有一些数字[2...n],你需要从中选出两个子集A[a1..ak],B[b1..bl],使得这两个子集中没有任何一组i,j满足gcd(ai,bj)!=1,求方案数对p的模值

我们可以对每一个数字考虑,看能不能放到集合A或者集合B。

如果要放到A,显然它的所有因子都不能出现在B,反之亦然。

那么我们可以用状压表示A集合已经有了哪些质因子以及B集合已经有了哪些质因子,然后用f[i][j]表示两边的质因子控制情况,其中i&j==0

然后对于目前元素x,如果x的质因子表示bit(x)&j==0就可以并到i中去,然后大概就是这样子的递推...

当然这样是不行的,因为质因子个数太多了...但是我们发现小于sqrt(500)的质因子只有8个...于是我们就可以状压表示这八个质因子的掌握情况。

首先将所有的数拆分成两个部分,一个是>sqrt(500)的质因子kind,一个是剩下的质因子所构成的一个状压表示。

把所有数按kind分类,如果不存在>sqrt(500)的质因子那就两边都可以放,如果存在的话,对于同一个kind,只能有一个集合取,但是可以取若干个。

那我们就可以设一个p[0][S1][S2],一个p[1][S1][S2]分别表示当前kind由A集合选和由B集合选的两种方案下,同时A集合8个质因子掌握情况为S1,B集合8个质因子掌握情况为S2的方案数。

总的又是一个f[S1][S2]表示A集合和B集合对于8个质因子的掌握情况。

当前加入一个新的数,若其没有>sqrt(500)的质因子或者没有他是一个新的kind分类的开端,就将p[0],p[1]先设置为没有加入这个元素前的状态f[]。然后分别枚举讨论加入后的影响。

若其所在的kind是正在讨论的部分,那么就沿着之前留下的p[0],p[1]接着讨论。因为如果说一个kind考虑完了,f[]需要将两边的元素整合起来,也就是不同kind的考虑是独立的。所以在这个kind下两边的讨论就可以归总到f[]中去。但是p[0]和p[1]都建立在没有选这一类之前,所以要是要整合的话,需要减去在这一类kind之前的选择方案数,否则就被加了两遍。即f[]=p[0]+p[1]-f[];

感觉思路还是挺神奇的.想到分成两个部分的质因子然后用状压的思路值得积累.

#include<cstdio>
#include<cstring>
#include<algorithm>
 
using namespace std;
 
const int maxn=510;
const int Lim=(1<<8);
const int maxl=(1<<8)+10;
const int Small=19;
 
int n,mod;
int Prime[9]={0,2,3,5,7,11,13,17,19};
int f[maxl][maxl];
int p[2][maxl][maxl];
 
struct Node{
    int kind,bit;
}s[maxn];
 
bool cmp(const Node &A,const Node &B){
    return A.kind<B.kind;
}
 
int main(){
#ifndef ONLINE_JUDGE
    freopen("dinner.in","r",stdin);
    freopen("dinner.out","w",stdout);
#endif
 
    scanf("%d%d",&n,&mod);
     
    for(int i=2;i<=n;i++){
        s[i].kind=i;
        for(int j=1;j<=8;j++){
            if(i%Prime[j]==0){
                s[i].bit|=(1<<j-1);
                while(s[i].kind%Prime[j]==0) s[i].kind/=Prime[j];
            }
        }
    }
    sort(s+2,s+n+1,cmp);
     
    f[0][0]=1;
    for(int i=2;i<=n;i++){
        if(s[i].kind!=s[i-1].kind || s[i].kind==1){
            memcpy(p[0],f,sizeof(f));
            memcpy(p[1],f,sizeof(f));
        }
        for(int S1=Lim-1;S1>=0;S1--)
            for(int S2=Lim-1;S2>=0;S2--)
            if(!(S1&S2)){
                if(!(s[i].bit&S2)) p[0][S1|s[i].bit][S2]=(p[0][S1|s[i].bit][S2]+p[0][S1][S2])%mod;
                if(!(s[i].bit&S1)) p[1][S1][S2|s[i].bit]=(p[1][S1][S2|s[i].bit]+p[1][S1][S2])%mod; 
            }
        if(s[i].kind!=s[i+1].kind || s[i].kind==1){
            for(int S1=0;S1<Lim;S1++)
                for(int S2=0;S2<Lim;S2++)
                    if(!(S1&S2))
                    f[S1][S2]=((p[0][S1][S2]+p[1][S1][S2]-f[S1][S2])%mod+mod)%mod;
        }
    }
     
    int ans=0;
    for(int S1=0;S1<Lim;S1++)
        for(int S2=0;S2<Lim;S2++)
            if(!(S1&S2))
            ans=(ans+f[S1][S2])%mod;
     
    printf("%d",ans);
    return 0;
}
View Code

 

 

Day2

T1 荷马史诗

AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=4198

让你用一个k进制表示n个数,它们分别出现的次数为a1...an,问怎样设计这n个数既使得没有一个是另一个的前缀,又使得这个总的使用字母最短,满足前面两个的基础上,还使得最长的字母最短.

容易让人想起哈夫曼编码.然后这个就是k进制下的,那就是k叉哈夫曼树了...

然后要加几个0保证这棵树长得比较好看呢?...

首先肯定是和k-1有关对吧...因为每k个变成1个,其实就是消失了k-1个.

然后这样理解了之后,就很好弄了,首先设A=n%(k-1)看一下剩几个.因为最后要剩下一个,所以A=1正好,然后其它的就是凑成剩下一个了...

如果A>1,那就是[(n-1)-A]+1=n-A个.如果是A<1,那就是1-A个.

其实也不是没有把上面三个概括起来的方法: 需要+0的个数为: (n-A)%(n-1)个.

模板题+1,良心送分.

#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
 
using namespace std;
 
typedef long long ll;
 
struct Node{
    ll dt;
    int lv;
     
    bool operator < (const Node &A) const{
        if(A.dt!=dt) return A.dt<dt;
        return A.lv<lv;
    }
};
 
int n,k;
priority_queue<Node>q;
 
int main(){
#ifndef ONLINE_JUDGE
    freopen("epic.in","r",stdin);
    freopen("epic.out","w",stdout);
#endif
 
    ll x;
 
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
        scanf("%lld",&x),q.push((Node){x,1});
     
    x=n%(k-1);
    if(x==1) x=0;
    else if(x==0) x=1;
    else x=k-x;
     
    if(k==2) x=0;
     
    for(int i=1;i<=x;i++) q.push((Node){0,1});
     
    ll ans=0;
    Node now;
     
    while(!q.empty()){
        now=q.top();q.pop();
        if(q.empty()){
            printf("%lld\n%d",ans,now.lv-1);
            break;
        }
        for(int i=1;i<k;i++){
            now.dt+=q.top().dt;
            now.lv=max(now.lv,q.top().lv);
            q.pop();
        }
        now.lv++;
        ans+=now.dt;
        q.push(now);
    }
 
    return 0;
}
View Code

 

T2 品酒大会

AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=4199

给一个串,设定两个位置i,j为r相似是说,从它们俩开始记为1,往后走r个构成的串都是相同的.每个位置有权值,一个点对的值=它们两个位置的权值之积.

有两问,第一问问你所有的r相似分别有多少个.第二问问你r相似的点对中最大的权值是多少.

然后就联想到了差异这道题...感觉十分的类似.由lca的选择也就可以统计到mx[lca]相似当中去.

这题的计数和那题也是一样的.那么求最大值和最小值联想一下也是可以想出来的.都是树形dp的一部分吧.

但是注意每次计数就只要考虑跨过LCA的贡献,然后就能得到最长为mx[lca]相似的,

最后再累加一下就能得到总共的了.

原题的积累还是比较重要的.

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

inline int in(){
    int x=0,f=1;char ch=getchar();
    while((ch>'9' || ch<'0') && ch!='-') ch=getchar();
    if(ch=='-') ch=getchar(),f=-1;
    while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const int maxn=600010;
const int INF=0x3f3f3f3f;
typedef long long ll;

int n,last,cnt;
int v[maxn];
int a[maxn][26],mx[maxn],fa[maxn];
int rz[maxn],bg[maxn],sm[maxn],mk[maxn];
int T[maxn],Seq[maxn];
int mk_sm[maxn],mk_bg[maxn];
ll rec1[maxn],rec2[maxn];
char ch[maxn];

void extend(int c,int x){
    int p=last,np=last=++cnt;
    mx[np]=mx[p]+1,rz[np]=mk[np]=1,bg[np]=sm[np]=mk_sm[np]=mk_bg[np]=v[x];
    while(!a[p][c] && p) a[p][c]=np,p=fa[p];
    if(!p) fa[np]=1;
    else{
        int q=a[p][c];
        if(mx[q]==mx[p]+1) fa[np]=q;
        else{
            int nq=++cnt; mx[nq]=mx[p]+1; bg[nq]=mk_bg[nq]=-INF; sm[nq]=mk_sm[nq]=INF;
            memcpy(a[nq],a[q],sizeof(a[q]));
            fa[nq]=fa[q];
            fa[np]=fa[q]=nq;
            while(a[p][c]==q) a[p][c]=nq,p=fa[p];
        }
    }
}

void get_order(){
    for(int i=1;i<=cnt;i++) T[mx[i]]++;
    for(int i=2;i<=n;i++) T[i]+=T[i-1];
    for(int i=1;i<=cnt;i++) Seq[T[mx[i]]--]=i;
}

void Dp(){
    int x;
    for(int i=cnt;i>=1;i--){
        x=Seq[i];
        rz[fa[x]]+=rz[x];
        bg[fa[x]]=max(bg[fa[x]],bg[x]);
        sm[fa[x]]=min(sm[fa[x]],sm[x]);
    }
    for(int i=1;i<=n;i++) rec2[i]=-0x3f3f3f3f3f3f3f3f;
    for(int i=cnt;i>=1;i--){
        x=Seq[i];
        rec1[mx[fa[x]]]+=(ll)rz[x]*mk[fa[x]];
        mk[fa[x]]+=rz[x];
        
        if(mk_bg[fa[x]]!=-INF)
            rec2[mx[fa[x]]]=max(rec2[mx[fa[x]]],(ll)mk_bg[fa[x]]*bg[x]);
        if(mk_sm[fa[x]]!=INF)
            rec2[mx[fa[x]]]=max(rec2[mx[fa[x]]],(ll)mk_sm[fa[x]]*sm[x]);
        mk_bg[fa[x]]=max(mk_bg[fa[x]],bg[x]);
        mk_sm[fa[x]]=min(mk_sm[fa[x]],sm[x]);
    }
    
    int max1=-INF,max2=-INF,min1=INF,min2=INF;
    ll ans1=(ll)n*(n-1)/2,ans2;
    for(int i=1;i<=n;i++){
        if(v[i]>=max1) max2=max1,max1=v[i];
        else if(v[i]>max2) max2=v[i];
        if(v[i]<=min1) min2=min1,min1=v[i];
        else if(v[i]<min2) min2=v[i];
    }
    
    if(max2==-INF && min2!=INF) ans2=(ll)min1*min2;
    else if(max2!=-INF && min2==INF) ans2=(ll)max1*max2;
    else ans2=max((ll)min1*min2,(ll)max1*max2);
    
    printf("%lld %lld\n",ans1,ans2);
    for(int i=n-1;i>=1;i--)
        rec1[i]+=rec1[i+1],rec2[i]=max(rec2[i],rec2[i+1]);
    for(int i=1;i<n;i++){
        if(!rec1[i]){printf("0 0\n");}
        else
            printf("%lld %lld\n",rec1[i],rec2[i]);
    }
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("savour.in","r",stdin);
    freopen("savour.out","w",stdout);
#endif

    last=cnt=1;

    n=in();
    scanf("%s",ch);
    for(int i=0;i<n;i++) v[i]=in();
    for(int i=n-1;i>=0;i--) extend(ch[i]-'a',i);
    
    get_order();    
    Dp();

    return 0;
}
View Code

 

T3 小园丁与老司机

AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=4200

空白让人思考。

posted @ 2016-06-14 20:15  诚叙  阅读(516)  评论(0编辑  收藏  举报