1. 程式人生 > >NOIP2017提高組解題報告

NOIP2017提高組解題報告

D1:

T1:這題考場推1個半小時結論也不知道打表找規律。我還是太菜了。NOIP有史以來的最短程式碼如下:

#include<bits/stdc++.h>
using namespace std;
long long a,b;
int main(){
    scanf("%lld%lld",&a,&b);
    printf("%lld",a*b-a-b);
    return 0;
}

T2:究極大模擬。。考場上心態炸瞭然後找出答案後寫了break,後面的輸入不進去了。。真是GG。AC程式碼其實也特別短,一點也沒有鬥地主難。注意n相同的判斷,用個棧維護就好了。

#include<bits/stdc++.h>
using namespace std;

int t,hang,zhishu,wuxiao=0,ans,qzhishu;

struct chuan{
    char bianliang;
    int zhishu_changshu;//0無效 1指數 2常數 
}aaa;

stack<chuan>q;
bool changshu,shiyong[10000],flag;
char fuzadu[100],kaishi[100],jieshu[100];



void xvnhuan(){
    cin>>aaa.bianliang>>kaishi>>jieshu;
    if
(!shiyong[int(aaa.bianliang)])shiyong[int(aaa.bianliang)]=1;//是否銷燬 else{ if(!flag)printf("ERR\n"); flag=1; } if(kaishi[0]==jieshu[0]&&jieshu[0]=='n'){ q.push((chuan){aaa.bianliang,2}); return ; } int lena=strlen
(kaishi),shua=0; int lenb=strlen(jieshu),shub=0; for(int i=0;i<lena;i++){//第一個串 if(kaishi[i]!='n'){ shua*=10; shua+=(int(kaishi[i])-48); } else { q.push((chuan){aaa.bianliang,0}); wuxiao++;//無效。 return ; } } for(int i=0;i<lenb;i++){//第二個串 if(jieshu[i]!='n'){ shub*=10; shub+=(int(jieshu[i])-48); } else if(jieshu[i]=='n'){ q.push((chuan){aaa.bianliang,1}); if(!wuxiao){ qzhishu++; ans=max(ans,qzhishu); } return; } } if(shua<=shub)q.push((chuan){aaa.bianliang,2}); else { q.push((chuan){aaa.bianliang,0}); wuxiao++; }//無效 } void mem(){ qzhishu=0; ans=0; wuxiao=0; flag=0; changshu=0; zhishu=0; memset(fuzadu,0,sizeof(fuzadu)); memset(shiyong,0,sizeof(shiyong)); while(!q.empty())q.pop(); } void Print(){ if(!flag){ if(!q.empty()){printf("ERR\n");return;} if(changshu){ if(!ans)printf("Yes\n"); else printf("No\n"); return ; } else if(!changshu){ if(ans==zhishu)printf("Yes\n"); else printf("No\n"); return ; } } } int main(){ // freopen("tle.in","r",stdin); // freopen("tle.out","w",stdout); scanf("%d",&t); for(int ll=1;ll<=t;ll++){ mem(); scanf("%d%s",&hang,fuzadu); int changdu=strlen(fuzadu); if(fuzadu[2]=='1'){ changshu=1; } else { for(int i=4;i<changdu-1;i++){ zhishu*=10; zhishu=zhishu+int(fuzadu[i])-48; } } for(int i=1;i<=hang;i++){ char leixing; scanf("%s",&leixing); if(leixing=='F')xvnhuan(); else{ if(q.empty()){if(!flag)printf("ERR\n");flag=1;} else{ chuan aaaa=q.top();q.pop(); shiyong[int(aaaa.bianliang)]=0; if(aaaa.zhishu_changshu==0)wuxiao--; if(aaaa.zhishu_changshu==1&&wuxiao==0)qzhishu--; } } } Print(); } return 0; }

T3:考場上沒有每組資料memset啊。。真是GG看來爆0了。這題的k只有50,因此我們可以DP刷表。dp【i】【j】表示到i點超出j長度的方案數。 已知dp【u】【0】=1,然後遍歷每條邊去刷表。這樣能拿70分。

100分:拓撲排序判0環 如果0環存在於dis【n】+k以內的路徑上輸出-1。
我們在DP轉移時,因為最短路或0邊 是橫向轉移的(dp【u】【j】=dp【v】【j】)所以我們可以對這些邊拓撲排序再DP。

#include<bits/stdc++.h>
using namespace std;

const int MAXN=1e5+5;

int n,m,k,p;

struct edge{
    int to,next,w;
}e[MAXN<<1],fe[MAXN<<1],ling[MAXN<<1];

int head[MAXN],fhead[MAXN],linghead[MAXN],cnt=0,fcnt=0,lingcnt=0;
bool flag=0;

inline void add(int u,int v,int w){e[++cnt]=(edge){v,head[u],w},head[u]=cnt;}
inline void addf(int u,int v,int w){fe[++fcnt]=(edge){v,fhead[u],w},fhead[u]=fcnt;}
inline void addling(int u,int v){ling[++lingcnt]=(edge){v,linghead[u],0},linghead[u]=lingcnt;}

struct hnd{
    int u,d;
    bool operator<(const hnd&rhs)const{
        return d>rhs.d;
    }
};

int dis[MAXN],dis2[MAXN];
priority_queue<hnd>q;

void dij(int n){
    dis[n]=0;
    q.push((hnd){n,dis[n]});
    while(q.size()){
        hnd x=q.top();q.pop();
        int u=x.u;
        if(x.d!=dis[u])continue;
        for(int i=head[u];i;i=e[i].next){
            int v=e[i].to,w=e[i].w;
            if(dis[u]+w<dis[v]){
                dis[v]=dis[u]+w;
                q.push((hnd){v,dis[v]});
            }
        }
    }
}

void dij2(int n){
    dis2[n]=0;
    q.push((hnd){n,dis2[n]});
    while(q.size()){
        hnd x=q.top();q.pop();
        int u=x.u;
        if(x.d!=dis2[u])continue;
        for(int i=fhead[u];i;i=fe[i].next){
            int v=fe[i].to,w=fe[i].w;
            if(dis2[u]+w<dis2[v]){
                dis2[v]=dis2[u]+w;
                q.push((hnd){v,dis2[v]});
            }
        }
    }
}

int que[MAXN],num=0,in[MAXN];

void topsort(){
    for(int i=1;i<=n;i++){
        for(int j=head[i];j;j=e[j].next){
            int v=e[j].to,w=e[j].w;
            if(!w){
                addling(i,v);in[v]++;   
            }
        }
    }
    for(int i=1;i<=n;i++){
        if(!in[i])que[++num]=i;
    }
    for(int i=1;i<=num;i++){
        int u=que[i];
        for(int j=linghead[u];j;j=ling[j].next){
            int v=ling[j].to;
            in[v]--;if(!in[v])que[++num]=v;
        }
    }
    for(int i=1;i<=n;i++){
        if(in[i]&&dis[i]+dis2[i]<=dis[n]+k){
            printf("-1\n");
            flag=1;
            return;
        }
    }
}

int f[60][MAXN];

int dp(){
    memset(ling,0,sizeof(ling));
    memset(linghead,0,sizeof(linghead));
    memset(que,0,sizeof(que));
    lingcnt=0,num=0;
    for(int i=1;i<=n;i++)
        for(int j=head[i];j;j=e[j].next){
            int v=e[j].to,w=e[j].w;
            if(w+dis[i]==dis[v]){
                in[v]++;addling(i,v);
            }
        }
    for(int i=1;i<=n;i++){
        if(!in[i]){que[++num]=i;}
    }
    for(int i=1;i<=num;i++){
        int u=que[i];
        for(int j=linghead[u];j;j=ling[j].next){
            int v=ling[j].to;
            in[v]--;if(!in[v])que[++num]=v;
        }
    }
    f[0][1]=1;
    for(int i=0;i<=k;i++){
        for(int x=1;x<=num;x++){
            int u=que[x];
            for(int z=head[u];z;z=e[z].next) {
                int v=e[z].to,w=e[z].w;
                if(i+dis[u]+w-dis[v]<=k) (f[i+dis[u]+w-dis[v]][v]+=f[i][u])%=p;
            }
        }
    }
    int ans=0;
    for(int i=0;i<=k;i++) (ans+=f[i][n])%=p;
    return ans;
}

void mem(){
    memset(e,0,sizeof(e));
    memset(fe,0,sizeof(fe));
    memset(dis,0x3f,sizeof(dis));
    memset(dis2,0x3f,sizeof(dis2));
    memset(f,0,sizeof(f));
    memset(head,0,sizeof(head));
    memset(fhead,0,sizeof(fhead));
    memset(ling,0,sizeof(ling));
    memset(linghead,0,sizeof(linghead));
    memset(que,0,sizeof(que));
    memset(in,0,sizeof(in));
    lingcnt=0,cnt=0,fcnt=0,num=0;
    flag=0;
}

void work(){
    mem();
    scanf("%d%d%d%d",&n,&m,&k,&p);
    for(int i=1;i<=m;i++){
        int tem1,tem2,tem3;
        scanf("%d%d%d",&tem1,&tem2,&tem3);
        add(tem1,tem2,tem3);
        addf(tem2,tem1,tem3);
    }
    dij(1);
    dij2(n);
    topsort();
    if(!flag)printf("%d\n",dp());
}

int t;
int main(){
    freopen("1.in","r",stdin);
    freopen("1.out","w",stdout);
    scanf("%d",&t);
    while(t--)work();
    return 0; 
}

D2 :

T1:考場上一眼就秒的題,可以並查集,可以建圖求連通性,聽說LL不會被卡,不用開ULL?

#include<bits/stdc++.h>
using namespace std;

const int MAXN=1e4+5;

long long a,b,ccfgao,ccfbanjing;

int ccffa[MAXN],ccfrk[MAXN],t,ccfdian;

struct ccfpoint{
    long long chang,kuan,gao;
}ppp[MAXN];

inline long long ccfcalc(long long ccfx,long long ccfy ,long long ccfz,long long ccfxx,long long ccfyy,long long ccfzz){
    long long ccftem1=abs(ccfx-ccfxx)*abs(ccfx-ccfxx);
    long long ccftem2=abs(ccfy-ccfyy)*abs(ccfy-ccfyy);
    long long ccftem3=abs(ccfz-ccfzz)*abs(ccfz-ccfzz);
    return ccftem1+ccftem2+ccftem3;
}

void ccfmakeuu(int n){for(int i=0;i<=n;i++)ccffa[i]=i;ccffa[4000]=4000;ccffa[5000]=5000;}

int ccffind(int x){
    return (x==ccffa[x])?x:ccffa[x]=ccffind(ccffa[x]);
}

void ccfunset(int x,int y){
    if((x=ccffind(x))==(y=ccffind(y)))return;
    if(ccfrk[x]>ccfrk[y])ccffa[y]=x;
    else{
        ccffa[x]=y;
        if(ccfrk[x]==ccfrk[y])ccfrk[y]++;
    }
}

int main(){
//  freopen("cheese.in","r",stdin);
//  freopen("cheese.out","w",stdout);
    scanf("%d",&t);
    for(int l=1;l<=t;l++){
        memset(ppp,0,sizeof(ppp));
        memset(ccfrk,0,sizeof(ccfrk));
        memset(ccffa,0,sizeof(ccffa));
        scanf("%d%lld%lld",&ccfdian,&ccfgao,&ccfbanjing);
        ccfmakeuu(ccfdian);
        for(int i=1;i<=ccfdian;i++){
            scanf("%lld%lld%lld",&ppp[i].chang,&ppp[i].kuan,&ppp[i].gao);
            if(ppp[i].gao<=ccfbanjing){
                ccfunset(i,4000);
            }
            if(ccfgao-ppp[i].gao<=ccfbanjing){
                ccfunset(i,5000);
            }
        }
        long long ccfbanjing2=(ccfbanjing*ccfbanjing)*4;
        for(int i=1;i<ccfdian;i++)
            for(int j=i+1;j<=ccfdian;j++){
                if(i==j)continue;
                if(ccfcalc(ppp[i].chang,ppp[i].kuan,ppp[i].gao,ppp[j].chang,ppp[j].kuan,ppp[j].gao)<=ccfbanjing2)
                ccfunset(i,j);
            }
        if(ccffind(4000)==ccffind(5000))printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}


T2:這題其實看出狀壓DP了。。可是狀態沒有設對(之前也不會列舉子集。 對於第i層操作,狀態為【狀壓表示的二進位制碼】。 我們可以列舉他的子集。 提前預處理出各種狀態(二進位制碼)到某一個點的最短邊。 一種狀態可以通過他子集的補集轉移來,要走的點就是子集裡的點。這題還是很有趣的qaq~注意陣列的初始化,別炸long long了。

#include<bits/stdc++.h>
using namespace std;

const int MAXN=13;
const int INF=1<<29;

long long n,m,w[MAXN][1<<MAXN],f[MAXN][1<<MAXN];
long long dis[MAXN][MAXN],tem1,tem2,tem3;

int main(){
    memset(dis,0x3f,sizeof(dis));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&tem1,&tem2,&tem3);
        dis[tem1][tem2]=min(dis[tem1][tem2],tem3);
        dis[tem2][tem1]=min(dis[tem2][tem1],tem3);
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<(1<<n);j++){
            w[i][j]=INF;
            for(int k=1;k<=n;k++){
                if(1<<(k-1)&j)w[i][j]=min(w[i][j],dis[k][i]);
            }
        }
    for(int i=1;i<(1<<n);i++){
        if(i&(i-1))for(int ll=0;ll<=n;ll++)f[ll][i]=INF;
        for(int j=i&(i-1);j;j=i&(j-1)){
            long long diss=0;
            for(int k=1;k<=n;k++)if((1<<(k-1))&j){
                diss+=w[k][i-j];
            //  cout<<diss<<" "<<k<<" "<<"  "<<i-j<<" "<<j<<endl;   
            }   
            for(int k=1;k<=n;k++)f[k][i]=min(f[k][i],f[k-1][i-j]+diss*k);
        }
    }
    long long ans=0x7fffffffffffffff;
    for(int i=1;i<=n;i++)ans=min(ans,f[i][(1<<n)-1]);
    printf("%lld",ans);
    return 0;
}

T3:這題考場上寫了個大模擬,聽說正解是線段樹/樹狀陣列(我怎麼看不出來怎麼寫啊qaq)。用平衡樹做比較簡單(平衡樹不會寫啊。。所以我先去學平衡樹吧qaq

//平衡樹學完了,然後這道題確實挺難的qaq。
我們可以用無旋treap來做這道題,建n+1個treap,會爆空間,怎麼辦?
我們初始狀態每行有一個節點,節點有一個區間從第1人到第m-1人。第n+1個treap維護最後一列,一共n個節點。
這樣每個節點都是一個連續的區間,當我們操作一個點他在一個連續區間內時,我們把它拆成三個點。因為詢問只有3e5次,這樣不會爆空間。然後我們操作的時候那一行和最後一列的treap刪除一個點,插入一個點。如果詢問的是第m列,那我們特判一下就好了(之前zcy的板子不夠簡潔我改了一下)

今天(2017年11月21日)才弄懂這道題,還沒來得及寫程式碼,程式碼明天上傳。

//2017年11月22日更新
這個題有毒吧qaq 我調了好久啊。。注意pushup!pushup!pushup!重要的事情說三遍,我之前的模板可能是有毒哦qaq。程式碼裡有註釋還是很好懂的。這道題一定要注意細節,不要出些奇怪的錯。節點資訊要記得更新,然後區間長度別算錯。連續區間壓成一個點,需要時拆開,這是這道題最關鍵的。然後就沒什麼了。好的距離比賽時隔10天qaq,我終於寫完了今年NOIP pj+tg一共10道題的解題報告。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mp make_pair 

typedef pair<ll,ll>par;
const int MAXN=3e5+5;

ll n,m;int q;

struct treap{
    int rt[MAXN],cnt;
    int lson[MAXN*6],rson[MAXN*6],prio[MAXN*6];
    int size[MAXN*6];
    struct que{
        ll l,r;
    }qu[MAXN*6];
    inline ll getz(int p){return p==0?0:qu[p].r-qu[p].l+1;}
    inline void pushup(int p){size[p]=size[lson[p]]+size[rson[p]]+getz(p);}
    par split(int p,int x){
        if(!x)return mp(0,p);
        int l=lson[p],r=rson[p];
    //  cout<<size[l]<<endl;
        if(x<=size[l]){
            par tem=split(l,x);
            lson[p]=tem.second;pushup(p);return mp(tem.first,p);
        }
        else if(x>=size[l]+getz(p)){
            par tem=split(r,x-size[l]-getz(p));
            rson[p]=tem.first;pushup(p);return mp(p,tem.second);
        }
        else {
            x-=size[l]; 
            ll t3=qu[p].r;
            qu[p].r=qu[p].l+x-1;
            lson[++cnt]=0;rson[cnt]=0;prio[cnt]=rand();//拆開 
            qu[cnt].l=qu[p].r+1;qu[cnt].r=t3;
            int y=cnt;
            y=merge(y,r);rson[p]=0;//注意把原來區間的右兒子接到cnt上,很容易忘。
            pushup(p),pushup(y); 
            return mp(p,y);
        }
    }
    int merge(int x,int y){
        if(!x||!y){
            pushup(x);pushup(y);return x+y; //zcy模板裡沒寫pushup調了半天啊GG。。。 
        }
        if(prio[x]<prio[y]){
            rson[x]=merge(rson[x],y);pushup(x);return x;
        }
        else {
            lson[y]=merge(x,lson[y]);pushup(y);return y;
        }
    }
    int work(int x,int y){
        par t1=split(rt[x],y-1);
        //cout<<t1.first<<t1.second;
        par t2=split(t1.second,1);//t2.first是拆出來的點。 
        rt[x]=merge(t1.first,t2.second);
        par t3=split(rt[0],x-1);//分離前x-1個 
        par t4=split(t3.second,1);//取出第x個作為first準備插入。 
    //  cout<<t4.first<<"ok"<<endl;
        rt[0]=merge(t3.first,t4.second);
        rt[x]=merge(rt[x],t4.first);
        rt[0]=merge(rt[0],t2.first);
        return t2.first;
    }
    int work2(int x){
        par t1=split(rt[0],x-1);//x-1x到結束 
        par t2=split(t1.second,1);//第一個取出的   後面結束 
        rt[0]=merge(t1.first,t2.second);
        rt[0]=merge(rt[0],t2.first);
        return t2.first;
    }
}T;



inline void make(ll x){
    T.rt[++T.cnt]=T.cnt;
    T.lson[T.cnt]=0;T.rson[T.cnt]=0;T.prio[T.cnt]=rand();
    T.qu[T.cnt].l=m*(x-1)+1,T.qu[T.cnt].r=x*m-1;T.pushup(T.cnt);
}

void dfs(ll x){
    if(!x)return;
    dfs(T.lson[x]);
    printf("%lld~~~%lld ",x,T.size[x]);
    dfs(T.rson[x]);
}

int main(){
    scanf("%lld%lld%d",&n,&m,&q);
    for(int i=1;i<=n;i++)make(i);
    T.rt[0]=++T.cnt;
    T.lson[T.cnt]=0;T.rson[T.cnt]=0;T.prio[T.cnt]=rand();T.qu[T.cnt].l=m;T.qu[T.cnt].r=m;T.pushup(T.cnt);
    for(int i=2;i<=n;i++){
        T.lson[++T.cnt]=0;T.rson[T.cnt]=0;T.prio[T.cnt]=rand();T.qu[T.cnt].l=m*i;T.qu[T.cnt].r=m*i;
        T.pushup(T.cnt);//千萬別忘更新,當時因為size不對,序列就亂了,調了很久。 
        T.rt[0]=T.merge(T.rt[0],T.cnt);//merge必須返回給根。
    }
   // for(int i=1;i<=n;i++){
    //  cout<<T.qu[T.rt[i]].l<<"->"<<T.qu[T.rt[i]].r<<endl;
//  }
//  par p=T.split(T.rt[5],17);
//  cout<<T.qu[T.rt[5]].l<<"->"<<T.qu[T.rt[5]].r<<endl;
//  cout<<T.qu[p.second].l<<"->"<<T.qu[p.second].r<<endl;
    //  dfs(T.rt[0]);
    //  printf("%lld\n",T.qu[T.work(3,3)].l);
    int tem1,tem2;
    for(int i=1;i<=q;i++){
        scanf("%d%d",&tem1,&tem2);
          if(tem2!=m)printf("%lld\n",T.qu[T.work(tem1,tem2)].l);
          else printf("%lld\n",T.qu[T.work2(tem1)].l);
    }
}