NOIP2016提高组题解

DAY1

T1

很简单的一个模拟算法。
从0标号就可以用模,从1标号也可以通过特判(>n就减小于1就加)

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<iostream>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
typedef double db;
const int maxn=100000+10;
char s[maxn][20];
int a[maxn],b[maxn];
int i,j,k,l,t,n,m,ans;
char ch;
int read(){//if read a big number,need ll
    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;
}
char get(){
    char ch=getchar();
    while (ch<'a'||ch>'z') ch=getchar();//zi fu chuan jin bao han a-z
    return ch;
}
int main(){
    freopen("toy.in","r",stdin);freopen("toy.out","w",stdout);
    n=read();m=read();
    fo(i,1,n){
        a[i]=read();
        b[i]=1;
        s[i][1]=get();
        do{
            ch=getchar();
            if (ch<'a'||ch>'z') break;
            s[i][++b[i]]=ch;
        }while (1);
    }
    ans=1;
    fo(i,1,m){
        j=read();k=read();
        if (j==a[ans]) t=1;else t=0;//t biao shi shun hai shi ni
        if (t){
            ans-=k;
            if (ans<=0) ans+=n;
        }
        else{
            ans+=k;
            if (ans>n) ans-=n;
        }
    }
    fo(i,1,b[ans]) printf("%c",s[ans][i]);
    printf("\n");
    fclose(stdin);fclose(stdout);
    return 0;
}

T2

我们用d表示深度,a表示一条路线的长度。
对于路线S->T,若lca为V,拆成S->V以及V->T两条树链。
对于S->V上每一个点j,其答案可以加一的条件:
dSdj=wj
dS=dj+wj
右边只与j有关。
同样,对于V->T上每一个点j,其答案可以加一的条件:
dTdj=aiwj
dTai=djwj
右边只与j有关。
因此思考离线做法,对于一条路线,在S和T分别打两个tag,并在lca即V处回收tag。
用两颗线段树维护子树内目前还没被撤销掉的dS和dT-ai的权值线段树,权值可能为负数需要设置一个绝对值的最大值来加上,或者提前进行离散化。
这个线段树是单点修改和查询的,用线段树仅仅是因为线段树可以进行合并。
这个算法带log,如何线性知道一个子树内某权值的数量?
维护一个桶,进入一个节点时得到该节点子树所需权值的数量t1,退出该节点时在桶中得到该节点子树所需权值的数量t2,那么显然该子树内有t2-t1个所需权值。

T3

如果我们用2^n枚举每节课申请或不申请,那么我们需要知道如何计算期望。
给你一个DAG,入度为0的点只有一个,每条边有两个权值a和b,从入度为0的点走到另一点所有的路径中a的和乘b的积的和,这其实就是期望的模型。
已知一个点的E和P( E=(VP) ,这里的V和P表示一条路径上的权值和与概率积),现在该点有一条出边(a,b),对该边的终点的E和P的影响是?
对P的影响显然是 Pb ,加上这个即可。
E?
(V+a)Pb
Eb+Pab
用这个方法递推即可。
那么影响答案的就是我们的决策,也就是是否申请。
设f[i,j,0~1]表示上完第i节课,申请了j次,第i节课是否申请的最小期望。
以申请了为例,假如决策时下一节课也申请。根据上面的式子,你会发现当前的概率与前面的决策均无关,因为如果申请了,那么就有p[i]的概率在d[i],有1-p[i]的概率在c[i]。因此可以进行转移。
上面的讲法可能稍微复杂,其实知道期望的线性性也可以推一下。

DAY2

T1

预处理一个阶乘包含了多少个某质数。
然后求一个组合数是否是k的倍数可以分解质因数。
最后再根据组合数是否为k的倍数产生的01贡献矩阵做二维前缀和,可以o(1)回答每个询问。
这个方法不知道能不能跑过老爷机。
实际上,可以用组合数的公式
Cji=Cji1+Cj1i1
n^2的在模k意义下求组合数是否为k的倍数。

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
typedef double db;
const int maxn=2000+10;
int a[maxn][maxn],c[maxn][maxn],sum[maxn][maxn];
int i,j,k,l,t,n,m,ca,ans;
int read(){//if read a big number,gai ll
    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 main(){
    freopen("problem.in","r",stdin);freopen("problem.out","w",stdout);
    ca=read();k=read();
    c[0][0]=1;
    fo(i,1,2000){
        c[i][0]=1;
        fo(j,1,i)
            c[i][j]=(c[i-1][j-1]+c[i-1][j])%k;
    }
    fo(i,0,2000)
        fo(j,0,2000){
            if (i<j) a[i][j]=0;
            else a[i][j]=(c[i][j]==0);
        }
    fo(i,0,2000)
        fo(j,0,2000){
            sum[i][j]=a[i][j];
            if (i>0) t=sum[i-1][j];else t=0;
            sum[i][j]+=t;
            if (j>0) t=sum[i][j-1];else t=0;
            sum[i][j]+=t;
            if (i>0&&j>0) t=sum[i-1][j-1];else t=0;
            sum[i][j]-=t;
        }
    while (ca--){
        n=read();m=read();
        ans=sum[n][m];
        printf("%d\n",ans);
    }
    fclose(stdin);fclose(stdout);
    return 0;
}

T2

结论一:长的一定先切。
证明:显然。
结论二:对于相邻两次切的长度i和j(i>=j),切出来的ip和jp,将来一定是先切ip再切jp。
证明:
假如在t时刻切i,t+1时刻切j,那么从t+2时刻开始两者增长速度一致,因此可以只探究两者在t+1时刻的长度。
前者: ip+q=ip+q
后者: (j+q)p=jp+qp
显然前者大于后者。
再根据结论一,前者会先切。

根据结论二可以类推出i-ip比j-jp先切。
因此开三个队列维护。
第一个队列维护排序后的原队列。
第二个队列维护切出来的乘p。
第三个队列维护切出来的乘1-p。
每次从三个队列队首取一个最大的就是要切的(结论一),然后切出的两份分别加入后两个队列的队尾(结论二)
这是线性的。
整体+q的影响可以考虑变成单个-q。

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
typedef double db;
const ll inf=10000000000000000;
const int maxn=8000000+10;
int sta[100];
ll a[maxn],b[maxn],c[maxn],k,l,t,ans,x,y;
int i,j,n,m,u,v,tot,top,q,tt,root,h1,e1,h2,e2,h3,e3;
db p;
int read(){//if read a big number,gai ll
    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;
}
void write(ll x){
    if (!x){
        putchar('0');
        putchar(' ');
        return;
    }
    top=0;
    while (x){
        sta[++top]=x%10;
        x/=10;
    }
    while (top){
        putchar('0'+sta[top]);
        top--;
    }
    putchar(' ');
}
bool cmp(int a,int b){
    return a>b;
}
int main(){
    //freopen("earthworm.in","r",stdin);freopen("earthworm.out","w",stdout);
    n=read();m=read();q=read();u=read();v=read();tt=read();
    p=(db)u/v;
    fo(i,1,n) a[i]=read();
    sort(a+1,a+n+1,cmp);
    h1=h2=h3=1;e1=n;
    fo(i,1,m){
        if (h1<=e1) k=a[h1];else k=-inf;
        if (h2<=e2) l=b[h2];else l=-inf;
        if (h3<=e3) t=c[h3];else t=-inf;
        if (k>=l&&k>=t) ans=k,h1++;
        else if (l>=k&&l>=t) ans=l,h2++;
        else ans=t,h3++;
        x=floor((ans+(ll)(i-1)*q)*p);y=ans+(ll)(i-1)*q-x;
        b[++e2]=x-(ll)i*q;
        c[++e3]=y-(ll)i*q;
        if (i%tt==0) write(ans+(ll)(i-1)*q);
    }
    printf("\n");
    fo(i,1,n+m){
        if (h1<=e1) k=a[h1];else k=-inf;
        if (h2<=e2) l=b[h2];else l=-inf;
        if (h3<=e3) t=c[h3];else t=-inf;
        if (k>=l&&k>=t) ans=k,h1++;
        else if (l>=k&&l>=t) ans=l,h2++;
        else ans=t,h3++;
        if (i%tt==0) write(ans+(ll)m*q);
    }
    printf("\n");
    fclose(stdin);fclose(stdout);
    return 0;
}

T3

三点确定一条抛物线(注意三点共线以及两点横坐标相同的情况)
状压DP,每次枚举两只猪确定发射一只鸟的抛物线(也可以只射一只猪,注意合法抛物线必须a<0)
这样是2^n*n*n。
注意标号最小的猪一定要死,每次可以解决最小的猪,只枚举另一只猪来确定抛物线。
2^n*n。
搜索+最优性剪枝+记忆化也可以。

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
typedef double db;
const int maxn=18+10;
const db eps=0.000000001;
db dx[maxn],dy[maxn];
int bz[maxn];
int f[(1<<19)+50];
bool vis[(1<<19)+50];
int i,j,k,l,t,n,m,ans,ca;
int read(){//if read a big number,gai ll
    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;
}
void dfs(int x,int y,int z){
    if (vis[z]){
        if (y+f[z]<ans) ans=y+f[z];
        return;
    }
    if (y>=ans) return;
    if (x==n+1){
        f[z]=0;
        ans=y;
        return;
    }
    if ((z&(1<<(x-1)))==0){
        dfs(x+1,y,z);
        return;
    }
    int i,j,t;
    bool pd[maxn];
    db a,b;
    fo(i,x,n) pd[i]=0;
    fo(i,x+1,n)
        if (!pd[i]&&(z&(1<<(i-1)))){
            pd[i]=1;
            t=0;
            if (dx[i]==dx[x]) continue;
            if (dy[i]/dx[i]==dy[x]/dx[x]) continue;
            b=(dy[i]*dx[x]*dx[x]-dy[x]*dx[i]*dx[i])/(dx[x]*dx[x]*dx[i]-dx[i]*dx[i]*dx[x]);
            a=(dy[i]-b*dx[i])/(dx[i]*dx[i]);
            if (a>=0) continue;
            t+=(1<<(x-1));t+=(1<<(i-1));
            fo(j,i+1,n)
                if (fabs(dy[j]-dx[j]*dx[j]*a-dx[j]*b)<eps){
                    if (z&(1<<(j-1))) t+=(1<<(j-1));
                    pd[j]=1;
                }
            dfs(x+1,y+1,z-t);
            if (f[z-t]+1<f[z]) f[z]=f[z-t]+1;
        }
    dfs(x+1,y+1,z-(1<<(x-1)));
    if (f[z-(1<<(x-1))]+1<f[z]) f[z]=f[z-(1<<(x-1))]+1;
    vis[z]=1;
}
int main(){
    freopen("angrybirds.in","r",stdin);freopen("angrybirds.out","w",stdout);
    ca=read();
    while (ca--){
        n=read();m=read();
        fo(i,0,(1<<n)-1) f[i]=n+1,vis[i]=0;
        fo(i,1,n) scanf("%lf%lf",&dx[i],&dy[i]);
        ans=n;
        dfs(1,0,(1<<n)-1);
        printf("%d\n",ans);
    }
    fclose(stdin);fclose(stdout);
    return 0;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值