NOIP 2016 題解+代碼


感覺是一項大工程。。不知道能打多少。

玩具謎題
直接模擬就行。

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

const int N = 100000 + 10;
const int M = 10 + 2;

int n,m;
int a[N];
char s[N][M];

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);gets(s[i]);
    }
    int tail=1;
    while(m--){
        int x,s;scanf("%d%d",&x,&s);
        if(a[tail]==0&&x==0) tail-=s,tail=(tail%n+n)%n;
        else if(a[tail]==0&&x==1) tail+=s,tail%=n;
        else if(a[tail]==1&&x==0) tail+=s,tail%=n;
        else tail-=s,tail=(tail%n+n)%n;
        if(tail==0) tail=n;
    }
    for(int i=1;i<strlen(s[tail]);++i) printf("%c",s[tail][i]);
    printf("\n");
    return 0;
}

天天愛跑步。
雖然正解比暴力短。。。考場上還是寫暴力吧。
路徑拆分。
1.如果s在u的子樹中,且dep[u]+w[u]=dep[s],則以s為起點的這條路徑對u有1的貢獻。
2.如果t在u的子樹中,且dep[t]-dis(s,t)=dep[u]-w[u],則以t為終點的這條路徑對u有1的貢獻。
3.注意如果u是s和t的lca,則u會被統計到兩次,要注意減去。
所以其實這個可以直接用桶up[x],記錄深度為x的s有多少個,down[x]記錄dep[t]-dis(s,t)為x的t有多少個,所以每次便利到u的時候,就直接加上up[dep[u]+w[u]]和dep[dep[u]-w[u]].
1.不過還有一些細節:dep[t]-dis(s,t)可能為負,而數組下標不能為負,所以+n
2為了滿足統計到的路徑都是經過u這個節點的路徑,所以我們在離開u的子樹的時候,要把以u為lca的貢獻都減掉。
3.在進u這棵子樹的時候先記錄一個前驅,表示進入u之前的答案,然后現在的答案(up和down的和)減去進入u之前的答案才是u的答案。

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

const int N = 300000 + 10;
const int P = 20;

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

int n,m;
int w[N];
vector<int> qs[N],qt[N],ed[N];
int up[N],down[N];

struct node{
    int pre,v;
}e[N<<1];

int num=0,head[N];
void addedge(int from,int to){
    e[++num].pre=head[from],e[num].v=to;
    head[from]=num;
}

int anc[N][P],dep[N];
void dfs(int u,int f){
    anc[u][0]=f;
    for(int i=1;i<P;++i) anc[u][i]=anc[anc[u][i-1]][i-1];
    for(int i=head[u];i;i=e[i].pre){
        int v=e[i].v;
        if(v==f) continue;
        dep[v]=dep[u]+1;
        dfs(v,u);
    }
}

int query(int u,int v){
    if(dep[u]<dep[v]) swap(u,v);
    for(int i=0,t=dep[u]-dep[v];t;t>>=1,++i)
        if(t&1) u=anc[u][i];
    if(u==v) return u;
    for(int i=P-1;i>=0;--i)
        if(anc[u][i]!=anc[v][i]) u=anc[u][i],v=anc[v][i];
    return anc[u][0];
}

int cnts[N],cnt[N];
void dfs1(int u,int f){
    int pre=up[dep[u]+w[u]]+down[dep[u]-w[u]+n];
    for(int i=0;i<ed[u].size();++i) ++down[ed[u][i]];
    up[dep[u]]+=cnts[u];
    for(int i=head[u];i;i=e[i].pre){
        int v=e[i].v; 
        if(v==f) continue;
        dfs1(v,u);
    }
    cnt[u]=up[dep[u]+w[u]]+down[dep[u]-w[u]+n]-pre; 
    for(int i=0;i<qs[u].size();++i) {
        --up[qs[u][i]];
        if(qs[u][i]==dep[u]+w[u]) --cnt[u];
    }
    for(int i=0;i<qt[u].size();++i) --down[qt[u][i]];
}

int main(){
    n=read(),m=read();
    for(int i=1;i<n;++i){
        int u=read(),v=read();
        addedge(u,v),addedge(v,u);
    }
    for(int i=1;i<=n;++i) w[i]=read();
    dfs(1,1);
    for(int i=1;i<=m;++i){
        int s=read(),t=read();
        int lca=query(s,t),dis=dep[s]+dep[t]-2*dep[lca];
        ++cnts[s];
        ed[t].push_back(dep[t]-dis+n);
        qs[lca].push_back(dep[s]);
        qt[lca].push_back(dep[t]-dis+n);
    }
    dfs1(1,1);
    for(int i=1;i<=n;++i) printf("%d ",cnt[i]);
    return 0;
}

換教室。
一道比較水的期望dp,dp[i][j][0]表示前i段時間有j段時間換課,且第i節課換/不換期望的最小勞累值,稍微理解一下期望應該就可以做了,存路徑什么的細節要注意。
大概是目前寫過最長的dp方程。

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

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

const int N = 300 + 10;
const int NN = 2000 + 10;
const int inf = 0x73f3f3f;

inline double Min(double a,double b) {return a<b?a:b;}

int n,m,v,e;
int dis[N][N],c[NN],d[NN];
double dp[NN][NN][2],k[NN];

int main(){
    n=read(),m=read(),v=read(),e=read();
    for(int i=1;i<=n;++i) c[i]=read();
    for(int i=1;i<=n;++i) d[i]=read();
    memset(dis,63,sizeof(dis));
    for(int i=1;i<=n;++i) scanf("%lf",&k[i]);
    for(int i=1;i<=e;++i){
        int u=read(),v=read();
        dis[v][u]=dis[u][v]=min(dis[u][v],read());
    }
    for(int i=1;i<=v;++i) dis[i][i]=0;

    for(int k=1;k<=v;++k)
    for(int i=1;i<=v;++i)
    for(int j=1;j<=v;++j)
    dis[j][i]=dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);

    for(int i=1;i<=n;++i){
        for(int j=0;j<=m;++j)
            dp[i][j][0]=dp[i][j][1]=1.0*inf;
    }
    dp[1][1][1]=0,dp[1][0][0]=0;

    for(int i=2;i<=n;++i){
        for(int j=0;j<=m;++j){
            if(j!=0) dp[i][j][1]=Min(dp[i][j][1],dp[i-1][j-1][1]+k[i-1]*k[i]*1.0*dis[d[i-1]][d[i]]+k[i-1]*(1-k[i])*1.0*dis[d[i-1]][c[i]]+(1-k[i-1])*k[i]*1.0*dis[c[i-1]][d[i]]+(1-k[i-1])*(1-k[i])*1.0*dis[c[i-1]][c[i]]);
            if(j!=0) dp[i][j][1]=Min(dp[i][j][1],dp[i-1][j-1][0]+k[i]*1.0*dis[c[i-1]][d[i]]+(1-k[i])*1.0*dis[c[i-1]][c[i]]);
            dp[i][j][0]=Min(dp[i][j][0],dp[i-1][j][1]+k[i-1]*1.0*dis[d[i-1]][c[i]]+(1-k[i-1])*1.0*dis[c[i-1]][c[i]]);
            dp[i][j][0]=Min(dp[i][j][0],dp[i-1][j][0]+dis[c[i-1]][c[i]]);
        }
    }
    double ans=1.0*inf;
    for(int i=0;i<=m;++i) ans=Min(ans,Min(dp[n][i][0],dp[n][i][1]));
    printf("%.2lf\n",ans);
    return 0;
}

組合數問題
遞推求組合數+前綴和。

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

const int N = 2000 + 10;

int k,t,n,m;
int c[N][N];
ll ans[N][N];

int main(){
    scanf("%d%d",&t,&k);
    for(int i=0;i<=2000;++i){
        for(int j=0;j<=i;++j){
            if(j==0||j==i) c[i][j]=1;
            else c[i][j]=(c[i-1][j-1]+c[i-1][j])%k;
            if(c[i][j]==0) ans[i][j]=ans[i][j-1]+1;
            else ans[i][j]=ans[i][j-1];
        }
    }
    while(t--){
        scanf("%d%d",&n,&m);
        ll cnt=0;
        for(int i=0;i<=n;++i) cnt+=ans[i][min(i,m)];
        printf("%d\n",cnt);
    }
    return 0;
}

蚯蚓
80分的堆
這個還是很好想的。
主要就是增長長度的處理,我是用一個flag記錄到目前,如果一次都沒有砍掉的蚯蚓應該增長的長度(其實可以直接由i推得),砍斷后將新得到的蚯蚓的長度減去目前的flag就行了,相對長度並不會改變。

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

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

const int N = 10000000 + 10;

int n,m,q,u,v,t;
int cnt;
ll a[N];

bool cmp(int a,int b){
    return a>b;
}

int main(){
    n=read(),m=read(),q=read(),u=read(),v=read(),t=read();
    for(register int i=1;i<=n;++i) a[i]=read();
    make_heap(a+1,a+n+1);
    cnt=n;ll flag=0;
    for(int i=1;i<=m;++i){
        if(i%t==0) printf("%lld ",a[1]+flag);
        ll x=(a[1]+flag)*u/v,y=(a[1]+flag)-x;flag+=q;x-=flag,y-=flag;
        pop_heap(a+1,a+cnt+1);
        a[cnt]=x;push_heap(a+1,a+cnt+1);
        a[++cnt]=y;push_heap(a+1,a+cnt+1);
    }
    printf("\n");
    sort(a+1,a+cnt+1,cmp);
    for(int i=1;i<=cnt;++i){
        if(i%t==0) printf("%lld ",a[i]+flag);
    }
    return 0;
}

100分
維護三個單調隊列q1,q2,q3。
其中q1存蚯蚓原始的長度,q2存px,q3存x-px。
每次從三個隊列中取出最長的蚯蚓,然后把它直接排在q2和q3的隊尾。
為什么可以直接排在隊尾?這就需要證明砍掉后的蚯蚓仍然具有單調性。
設蚯蚓原始的長度為x,y,且x>y,顯然x會比y先砍,所以x砍完后會比y先入隊。
則砍斷后的長度分別為px+qt,x-px+qt,yp+qtp,y+qt-yp-qtp
顯然后面的兩個比前面的兩個小,滿足單調。

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

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

const int N = 100000 + 10;
const ll inf = 233333333333333LL;

int n,m,q,u,v,t;
ll a[N];

deque<ll> q1,q2,q3;

int getans(){
    ll mmax=-inf;
    if(!q1.empty()) mmax=max(mmax,q1.front());
    if(!q2.empty()) mmax=max(mmax,q2.front());
    if(!q3.empty()) mmax=max(mmax,q3.front());
    if(!q1.empty()&&q1.front()==mmax) q1.pop_front();
    else if(!q2.empty()&&q2.front()==mmax) q2.pop_front();
    else q3.pop_front();
    return mmax;
}

int main(){
    n=read(),m=read(),q=read(),u=read(),v=read(),t=read();
    for(int i=1;i<=n;++i) a[i]=read();
    sort(a+1,a+n+1);
    for(int i=n;i>=1;--i) q1.push_back(a[i]);
    ll flag=0;
    for(int i=1;i<=m;++i){
        ll mmax=getans();
        if(i%t==0) printf("%lld ",mmax+flag);
        ll x=(mmax+flag)*u/v,y=mmax+flag-x;
        flag+=q,x-=flag,y-=flag;
        q2.push_back(x),q3.push_back(y);
    }
    printf("\n");
    for(int i=1;i<=n+m;++i){
        ll ans=getans();
        if(i%t==0) printf("%lld ",ans+flag);
    }
    return 0;
}

憤怒的小鳥
65
其實這個做法有問題
我是枚舉d,j,k(都在集合i中),如果它們可以用一條拋物線全部擊倒,則相當於i集合的答案可以和從i這個集合中除開d/j/k的答案一樣,但如果本來在i除開k的那個集合中,用一條拋物線擊中i和j並不是最優的,(即i和j被兩條拋物線分別擊中),就會少算答案。

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

const int N = 18;
const double eps = 1e-10;

int n,m;
double x[N+5],y[N+5];
int dp[(1<<N)+5];
double a[(1<<N)+5],b[(1<<N)+5];

void getab(double &a,double &b,int i,int j){
    double x1=x[i],x2=x[j],y1=y[i],y2=y[j];
    a=1.0*y1*x2-1.0*y2*x1;
    if(1.0*x1*x1*x2-1.0*x2*x2*x1==0.0) a=0;
    else a=1.0*a/(1.0*x1*x1*x2-x2*x2*x1);
    b=1.0*(y1-1.0*a*x1*x1)/(1.0*x1);
}

int t;

int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;++i) scanf("%lf%lf",&x[i],&y[i]);
        memset(dp,63,sizeof(dp));
        dp[0]=0;
        for(int i=1;i<=n;++i){
            dp[1<<i-1]=1;
            for(int j=i+1;j<=n;++j){
                a[(1<<i-1)|(1<<j-1)]=b[(1<<i-1)|(1<<j-1)]=0;
                if(fabs(x[i]-x[j])<eps) continue;
                double aa,bb;getab(aa,bb,i,j);
                if(aa<(-eps)) dp[(1<<i-1)|(1<<j-1)]=1,a[(1<<i-1)|(1<<j-1)]=aa,b[(1<<i-1)|(1<<j-1)]=bb;
                else dp[(1<<i-1)|(1<<j-1)]=2;
            }
        }
        int tot=(1<<n)-1;
        for(int i=1;i<=tot;++i){
            for(int j=1;j<=n;++j){
                if((i|(1<<j-1))!=i) continue;
                for(int d=j+1;d<=n;++d){
                    if((i|(1<<d-1))!=i) continue;
                    for(int zz=d+1;zz<=n;++zz){
                        if((i|(1<<zz-1))!=i) continue;
                        if(a[(1<<d-1)|(1<<j-1)]<(-eps)&&fabs(a[(1<<d-1)|(1<<j-1)]*x[zz]*x[zz]+b[(1<<d-1)|(1<<j-1)]*x[zz]-y[zz])<eps) 
                            dp[i]=min(dp[i],min(dp[i^(1<<d-1)],dp[i^(1<<j-1)])),dp[i]=min(dp[i],dp[i^(1<<zz-1)]);
                        else {
                            dp[i]=min(dp[i],min(dp[i^(1<<j-1)^(1<<zz-1)]+dp[(1<<zz-1)|(1<<j-1)],dp[i^(1<<d-1)^(1<<j-1)]+dp[(1<<d-1)|(1<<j-1)]));
                            dp[i]=min(dp[i],dp[i^(1<<d-1)^(1<<zz-1)]+dp[(1<<d-1)|(1<<zz-1)]);
                        }
                    }
                }
            }
        }
        printf("%d\n",dp[tot]);
    }
    return 0;
}

100分
我們再用一個數組f[i]表示用某一條拋物線可以擊中的小鳥的集合。
所以dp[i|f[j]]=min(dp[i|f[j]],dp[i]+1).
這個做法好機智。。可以讓cnt輸出看一下,其實f數組的范圍遠不到n^2,所以可過。

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

const int N = 18;
const double eps = 1e-10;

int n,m,cnt=0;
double x[N+5],y[N+5];
int dp[(1<<N)+5],f[(1<<N)+5];

void getab(double &a,double &b,int i,int j){
    double x1=x[i],x2=x[j],y1=y[i],y2=y[j];
    a=1.0*y1*x2-1.0*y2*x1;
    if(1.0*x1*x1*x2-1.0*x2*x2*x1==0.0) a=0;
    else a=1.0*a/(1.0*x1*x1*x2-x2*x2*x1);
    b=1.0*(y1-1.0*a*x1*x1)/(1.0*x1);
}

int t;

void update(){
    cnt=0;memset(dp,63,sizeof(dp));
}

int main(){
    scanf("%d",&t);
    while(t--){
        update();
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;++i) scanf("%lf%lf",&x[i],&y[i]);
        for(int i=1;i<=n;++i){
            f[++cnt]=(1<<i-1);
            for(int j=i+1;j<=n;++j){
                if(fabs(x[i]-x[j])<eps) continue;
                double a,b;getab(a,b,i,j);
                if(a<-eps) {
                    int tot=((1<<i-1)|(1<<j-1));
                    for(int k=j+1;k<=n;++k)
                        if(fabs(1.0*a*x[k]*x[k]+b*x[k]-y[k])<eps) tot|=(1<<k-1);
                    f[++cnt]=tot;
                }
            }
        }
        int tot=(1<<n)-1;
        dp[0]=0;
        for(int i=0;i<=tot;++i){
            for(int j=1;j<=cnt;++j){
                dp[i|f[j]]=min(dp[i|f[j]],dp[i]+1);
            }
        }
        printf("%d\n",dp[tot]);
    }
    return 0;
}

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com