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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值